1<?php
2
3namespace Doctrine\DBAL\Query\Expression;
4
5use Countable;
6use Doctrine\Deprecations\Deprecation;
7use ReturnTypeWillChange;
8
9use function array_merge;
10use function count;
11use function implode;
12
13/**
14 * Composite expression is responsible to build a group of similar expression.
15 */
16class CompositeExpression implements Countable
17{
18    /**
19     * Constant that represents an AND composite expression.
20     */
21    public const TYPE_AND = 'AND';
22
23    /**
24     * Constant that represents an OR composite expression.
25     */
26    public const TYPE_OR = 'OR';
27
28    /**
29     * The instance type of composite expression.
30     *
31     * @var string
32     */
33    private $type;
34
35    /**
36     * Each expression part of the composite expression.
37     *
38     * @var self[]|string[]
39     */
40    private $parts = [];
41
42    /**
43     * @internal Use the and() / or() factory methods.
44     *
45     * @param string          $type  Instance type of composite expression.
46     * @param self[]|string[] $parts Composition of expressions to be joined on composite expression.
47     */
48    public function __construct($type, array $parts = [])
49    {
50        $this->type = $type;
51
52        $this->addMultiple($parts);
53
54        Deprecation::triggerIfCalledFromOutside(
55            'doctrine/dbal',
56            'https://github.com/doctrine/dbal/pull/3864',
57            'Do not use CompositeExpression constructor directly, use static and() and or() factory methods.'
58        );
59    }
60
61    /**
62     * @param self|string $part
63     * @param self|string ...$parts
64     */
65    public static function and($part, ...$parts): self
66    {
67        return new self(self::TYPE_AND, array_merge([$part], $parts));
68    }
69
70    /**
71     * @param self|string $part
72     * @param self|string ...$parts
73     */
74    public static function or($part, ...$parts): self
75    {
76        return new self(self::TYPE_OR, array_merge([$part], $parts));
77    }
78
79    /**
80     * Adds multiple parts to composite expression.
81     *
82     * @deprecated This class will be made immutable. Use with() instead.
83     *
84     * @param self[]|string[] $parts
85     *
86     * @return CompositeExpression
87     */
88    public function addMultiple(array $parts = [])
89    {
90        Deprecation::triggerIfCalledFromOutside(
91            'doctrine/dbal',
92            'https://github.com/doctrine/dbal/issues/3844',
93            'CompositeExpression::addMultiple() is deprecated, use CompositeExpression::with() instead.'
94        );
95
96        foreach ($parts as $part) {
97            $this->add($part);
98        }
99
100        return $this;
101    }
102
103    /**
104     * Adds an expression to composite expression.
105     *
106     * @deprecated This class will be made immutable. Use with() instead.
107     *
108     * @param mixed $part
109     *
110     * @return CompositeExpression
111     */
112    public function add($part)
113    {
114        Deprecation::triggerIfCalledFromOutside(
115            'doctrine/dbal',
116            'https://github.com/doctrine/dbal/issues/3844',
117            'CompositeExpression::add() is deprecated, use CompositeExpression::with() instead.'
118        );
119
120        if (empty($part)) {
121            return $this;
122        }
123
124        if ($part instanceof self && count($part) === 0) {
125            return $this;
126        }
127
128        $this->parts[] = $part;
129
130        return $this;
131    }
132
133    /**
134     * Returns a new CompositeExpression with the given parts added.
135     *
136     * @param self|string $part
137     * @param self|string ...$parts
138     */
139    public function with($part, ...$parts): self
140    {
141        $that = clone $this;
142
143        $that->parts[] = $part;
144
145        foreach ($parts as $part) {
146            $that->parts[] = $part;
147        }
148
149        return $that;
150    }
151
152    /**
153     * Retrieves the amount of expressions on composite expression.
154     *
155     * @return int
156     */
157    #[ReturnTypeWillChange]
158    public function count()
159    {
160        return count($this->parts);
161    }
162
163    /**
164     * Retrieves the string representation of this composite expression.
165     *
166     * @return string
167     */
168    public function __toString()
169    {
170        if ($this->count() === 1) {
171            return (string) $this->parts[0];
172        }
173
174        return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')';
175    }
176
177    /**
178     * Returns the type of this composite expression (AND/OR).
179     *
180     * @return string
181     */
182    public function getType()
183    {
184        return $this->type;
185    }
186}
187