1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Db\Sql\Predicate;
11
12use Countable;
13use Zend\Db\Sql\Exception;
14
15class PredicateSet implements PredicateInterface, Countable
16{
17    const COMBINED_BY_AND = 'AND';
18    const OP_AND          = 'AND';
19
20    const COMBINED_BY_OR  = 'OR';
21    const OP_OR           = 'OR';
22
23    protected $defaultCombination = self::COMBINED_BY_AND;
24    protected $predicates         = array();
25
26    /**
27     * Constructor
28     *
29     * @param  null|array $predicates
30     * @param  string $defaultCombination
31     */
32    public function __construct(array $predicates = null, $defaultCombination = self::COMBINED_BY_AND)
33    {
34        $this->defaultCombination = $defaultCombination;
35        if ($predicates) {
36            foreach ($predicates as $predicate) {
37                $this->addPredicate($predicate);
38            }
39        }
40    }
41
42    /**
43     * Add predicate to set
44     *
45     * @param  PredicateInterface $predicate
46     * @param  string $combination
47     * @return PredicateSet
48     */
49    public function addPredicate(PredicateInterface $predicate, $combination = null)
50    {
51        if ($combination === null || !in_array($combination, array(self::OP_AND, self::OP_OR))) {
52            $combination = $this->defaultCombination;
53        }
54
55        if ($combination == self::OP_OR) {
56            $this->orPredicate($predicate);
57            return $this;
58        }
59
60        $this->andPredicate($predicate);
61        return $this;
62    }
63
64    /**
65     * Add predicates to set
66     *
67     * @param PredicateInterface|\Closure|string|array $predicates
68     * @param string $combination
69     * @return PredicateSet
70     */
71    public function addPredicates($predicates, $combination = self::OP_AND)
72    {
73        if ($predicates === null) {
74            throw new Exception\InvalidArgumentException('Predicate cannot be null');
75        }
76        if ($predicates instanceof PredicateInterface) {
77            $this->addPredicate($predicates, $combination);
78            return $this;
79        }
80        if ($predicates instanceof \Closure) {
81            $predicates($this);
82            return $this;
83        }
84        if (is_string($predicates)) {
85            // String $predicate should be passed as an expression
86            $predicates = (strpos($predicates, Expression::PLACEHOLDER) !== false)
87                ? new Expression($predicates) : new Literal($predicates);
88            $this->addPredicate($predicates, $combination);
89            return $this;
90        }
91        if (is_array($predicates)) {
92            foreach ($predicates as $pkey => $pvalue) {
93                // loop through predicates
94                if (is_string($pkey)) {
95                    if (strpos($pkey, '?') !== false) {
96                        // First, process strings that the abstraction replacement character ?
97                        // as an Expression predicate
98                        $predicates = new Expression($pkey, $pvalue);
99                    } elseif ($pvalue === null) { // Otherwise, if still a string, do something intelligent with the PHP type provided
100                        // map PHP null to SQL IS NULL expression
101                        $predicates = new IsNull($pkey);
102                    } elseif (is_array($pvalue)) {
103                        // if the value is an array, assume IN() is desired
104                        $predicates = new In($pkey, $pvalue);
105                    } elseif ($pvalue instanceof PredicateInterface) {
106                        throw new Exception\InvalidArgumentException(
107                            'Using Predicate must not use string keys'
108                        );
109                    } else {
110                        // otherwise assume that array('foo' => 'bar') means "foo" = 'bar'
111                        $predicates = new Operator($pkey, Operator::OP_EQ, $pvalue);
112                    }
113                } elseif ($pvalue instanceof PredicateInterface) {
114                    // Predicate type is ok
115                    $predicates = $pvalue;
116                } else {
117                    // must be an array of expressions (with int-indexed array)
118                    $predicates = (strpos($pvalue, Expression::PLACEHOLDER) !== false)
119                        ? new Expression($pvalue) : new Literal($pvalue);
120                }
121                $this->addPredicate($predicates, $combination);
122            }
123        }
124        return $this;
125    }
126
127    /**
128     * Return the predicates
129     *
130     * @return PredicateInterface[]
131     */
132    public function getPredicates()
133    {
134        return $this->predicates;
135    }
136
137    /**
138     * Add predicate using OR operator
139     *
140     * @param  PredicateInterface $predicate
141     * @return PredicateSet
142     */
143    public function orPredicate(PredicateInterface $predicate)
144    {
145        $this->predicates[] = array(self::OP_OR, $predicate);
146        return $this;
147    }
148
149    /**
150     * Add predicate using AND operator
151     *
152     * @param  PredicateInterface $predicate
153     * @return PredicateSet
154     */
155    public function andPredicate(PredicateInterface $predicate)
156    {
157        $this->predicates[] = array(self::OP_AND, $predicate);
158        return $this;
159    }
160
161    /**
162     * Get predicate parts for where statement
163     *
164     * @return array
165     */
166    public function getExpressionData()
167    {
168        $parts = array();
169        for ($i = 0, $count = count($this->predicates); $i < $count; $i++) {
170            /** @var $predicate PredicateInterface */
171            $predicate = $this->predicates[$i][1];
172
173            if ($predicate instanceof PredicateSet) {
174                $parts[] = '(';
175            }
176
177            $parts = array_merge($parts, $predicate->getExpressionData());
178
179            if ($predicate instanceof PredicateSet) {
180                $parts[] = ')';
181            }
182
183            if (isset($this->predicates[$i+1])) {
184                $parts[] = sprintf(' %s ', $this->predicates[$i+1][0]);
185            }
186        }
187        return $parts;
188    }
189
190    /**
191     * Get count of attached predicates
192     *
193     * @return int
194     */
195    public function count()
196    {
197        return count($this->predicates);
198    }
199}
200