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\Validator\Db;
11
12use Traversable;
13use Zend\Db\Adapter\Adapter as DbAdapter;
14use Zend\Db\Sql\Select;
15use Zend\Db\Sql\Sql;
16use Zend\Db\Sql\TableIdentifier;
17use Zend\Stdlib\ArrayUtils;
18use Zend\Validator\AbstractValidator;
19use Zend\Validator\Exception;
20
21/**
22 * Class for Database record validation
23 */
24abstract class AbstractDb extends AbstractValidator
25{
26    /**
27     * Error constants
28     */
29    const ERROR_NO_RECORD_FOUND = 'noRecordFound';
30    const ERROR_RECORD_FOUND    = 'recordFound';
31
32    /**
33     * @var array Message templates
34     */
35    protected $messageTemplates = array(
36        self::ERROR_NO_RECORD_FOUND => "No record matching the input was found",
37        self::ERROR_RECORD_FOUND    => "A record matching the input was found",
38    );
39
40    /**
41     * Select object to use. can be set, or will be auto-generated
42     *
43     * @var Select
44     */
45    protected $select;
46
47    /**
48     * @var string
49     */
50    protected $schema = null;
51
52    /**
53     * @var string
54     */
55    protected $table = '';
56
57    /**
58     * @var string
59     */
60    protected $field = '';
61
62    /**
63     * @var mixed
64     */
65    protected $exclude = null;
66
67    /**
68     * Database adapter to use. If null isValid() will throw an exception
69     *
70     * @var \Zend\Db\Adapter\Adapter
71     */
72    protected $adapter = null;
73
74    /**
75     * Provides basic configuration for use with Zend\Validator\Db Validators
76     * Setting $exclude allows a single record to be excluded from matching.
77     * Exclude can either be a String containing a where clause, or an array with `field` and `value` keys
78     * to define the where clause added to the sql.
79     * A database adapter may optionally be supplied to avoid using the registered default adapter.
80     *
81     * The following option keys are supported:
82     * 'table'   => The database table to validate against
83     * 'schema'  => The schema keys
84     * 'field'   => The field to check for a match
85     * 'exclude' => An optional where clause or field/value pair to exclude from the query
86     * 'adapter' => An optional database adapter to use
87     *
88     * @param array|Traversable|Select $options Options to use for this validator
89     * @throws \Zend\Validator\Exception\InvalidArgumentException
90     */
91    public function __construct($options = null)
92    {
93        parent::__construct($options);
94
95        if ($options instanceof Select) {
96            $this->setSelect($options);
97            return;
98        }
99
100        if ($options instanceof Traversable) {
101            $options = ArrayUtils::iteratorToArray($options);
102        } elseif (func_num_args() > 1) {
103            $options       = func_get_args();
104            $firstArgument = array_shift($options);
105            if (is_array($firstArgument)) {
106                $temp = ArrayUtils::iteratorToArray($firstArgument);
107            } else {
108                $temp['table'] = $firstArgument;
109            }
110
111            $temp['field'] = array_shift($options);
112
113            if (!empty($options)) {
114                $temp['exclude'] = array_shift($options);
115            }
116
117            if (!empty($options)) {
118                $temp['adapter'] = array_shift($options);
119            }
120
121            $options = $temp;
122        }
123
124        if (!array_key_exists('table', $options) && !array_key_exists('schema', $options)) {
125            throw new Exception\InvalidArgumentException('Table or Schema option missing!');
126        }
127
128        if (!array_key_exists('field', $options)) {
129            throw new Exception\InvalidArgumentException('Field option missing!');
130        }
131
132        if (array_key_exists('adapter', $options)) {
133            $this->setAdapter($options['adapter']);
134        }
135
136        if (array_key_exists('exclude', $options)) {
137            $this->setExclude($options['exclude']);
138        }
139
140        $this->setField($options['field']);
141        if (array_key_exists('table', $options)) {
142            $this->setTable($options['table']);
143        }
144
145        if (array_key_exists('schema', $options)) {
146            $this->setSchema($options['schema']);
147        }
148    }
149
150    /**
151     * Returns the set adapter
152     *
153     * @throws \Zend\Validator\Exception\RuntimeException When no database adapter is defined
154     * @return DbAdapter
155     */
156    public function getAdapter()
157    {
158        return $this->adapter;
159    }
160
161    /**
162     * Sets a new database adapter
163     *
164     * @param  DbAdapter $adapter
165     * @return self Provides a fluent interface
166     */
167    public function setAdapter(DbAdapter $adapter)
168    {
169        $this->adapter = $adapter;
170        return $this;
171    }
172
173    /**
174     * Returns the set exclude clause
175     *
176     * @return string|array
177     */
178    public function getExclude()
179    {
180        return $this->exclude;
181    }
182
183    /**
184     * Sets a new exclude clause
185     *
186     * @param string|array $exclude
187     * @return self Provides a fluent interface
188     */
189    public function setExclude($exclude)
190    {
191        $this->exclude = $exclude;
192        $this->select  = null;
193        return $this;
194    }
195
196    /**
197     * Returns the set field
198     *
199     * @return string|array
200     */
201    public function getField()
202    {
203        return $this->field;
204    }
205
206    /**
207     * Sets a new field
208     *
209     * @param string $field
210     * @return AbstractDb
211     */
212    public function setField($field)
213    {
214        $this->field  = (string) $field;
215        $this->select = null;
216        return $this;
217    }
218
219    /**
220     * Returns the set table
221     *
222     * @return string
223     */
224    public function getTable()
225    {
226        return $this->table;
227    }
228
229    /**
230     * Sets a new table
231     *
232     * @param string $table
233     * @return self Provides a fluent interface
234     */
235    public function setTable($table)
236    {
237        $this->table  = (string) $table;
238        $this->select = null;
239        return $this;
240    }
241
242    /**
243     * Returns the set schema
244     *
245     * @return string
246     */
247    public function getSchema()
248    {
249        return $this->schema;
250    }
251
252    /**
253     * Sets a new schema
254     *
255     * @param string $schema
256     * @return self Provides a fluent interface
257     */
258    public function setSchema($schema)
259    {
260        $this->schema = $schema;
261        $this->select = null;
262        return $this;
263    }
264
265    /**
266     * Sets the select object to be used by the validator
267     *
268     * @param  Select $select
269     * @return self Provides a fluent interface
270     */
271    public function setSelect(Select $select)
272    {
273        $this->select = $select;
274        return $this;
275    }
276
277    /**
278     * Gets the select object to be used by the validator.
279     * If no select object was supplied to the constructor,
280     * then it will auto-generate one from the given table,
281     * schema, field, and adapter options.
282     *
283     * @return Select The Select object which will be used
284     */
285    public function getSelect()
286    {
287        if ($this->select instanceof Select) {
288            return $this->select;
289        }
290
291        // Build select object
292        $select          = new Select();
293        $tableIdentifier = new TableIdentifier($this->table, $this->schema);
294        $select->from($tableIdentifier)->columns(array($this->field));
295        $select->where->equalTo($this->field, null);
296
297        if ($this->exclude !== null) {
298            if (is_array($this->exclude)) {
299                $select->where->notEqualTo(
300                    $this->exclude['field'],
301                    $this->exclude['value']
302                );
303            } else {
304                $select->where($this->exclude);
305            }
306        }
307
308        $this->select = $select;
309
310        return $this->select;
311    }
312
313    /**
314     * Run query and returns matches, or null if no matches are found.
315     *
316     * @param  string $value
317     * @return array when matches are found.
318     */
319    protected function query($value)
320    {
321        $sql = new Sql($this->getAdapter());
322        $select = $this->getSelect();
323        $statement = $sql->prepareStatementForSqlObject($select);
324        $parameters = $statement->getParameterContainer();
325        $parameters['where1'] = $value;
326        $result = $statement->execute();
327
328        return $result->current();
329    }
330}
331