Yaffs site version 1.1
[yaffs-website] / vendor / gabordemooij / redbean / RedBeanPHP / Finder.php
1 <?php
2
3 namespace RedBeanPHP;
4
5
6 /**
7  * RedBeanPHP Finder.
8  * Service class to find beans. For the most part this class
9  * offers user friendly utility methods for interacting with the
10  * OODB::find() method, which is rather complex. This class can be
11  * used to find beans using plain old SQL queries.
12  *
13  * @file    RedBeanPHP/Finder.php
14  * @author  Gabor de Mooij and the RedBeanPHP Community
15  * @license BSD/GPLv2
16  *
17  * @copyright
18  * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
19  * This source file is subject to the BSD/GPLv2 License that is bundled
20  * with this source code in the file license.txt.
21  */
22 class Finder
23 {
24         /**
25          * @var ToolBox
26          */
27         protected $toolbox;
28
29         /**
30          * @var OODB
31          */
32         protected $redbean;
33
34         /**
35          * Constructor.
36          * The Finder requires a toolbox.
37          *
38          * @param ToolBox $toolbox
39          */
40         public function __construct( ToolBox $toolbox )
41         {
42                 $this->toolbox = $toolbox;
43                 $this->redbean = $toolbox->getRedBean();
44         }
45
46         /**
47          * Finds a bean using a type and a where clause (SQL).
48          * As with most Query tools in RedBean you can provide values to
49          * be inserted in the SQL statement by populating the value
50          * array parameter; you can either use the question mark notation
51          * or the slot-notation (:keyname).
52          *
53          * @param string $type     type   the type of bean you are looking for
54          * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
55          * @param array  $bindings values array of values to be bound to parameters in query
56          *
57          * @return array
58          */
59         public function find( $type, $sql = NULL, $bindings = array() )
60         {
61                 if ( !is_array( $bindings ) ) {
62                         throw new RedException(
63                                 'Expected array, ' . gettype( $bindings ) . ' given.'
64                         );
65                 }
66
67                 return $this->redbean->find( $type, array(), $sql, $bindings );
68         }
69
70         /**
71          * Like find() but also exports the beans as an array.
72          * This method will perform a find-operation. For every bean
73          * in the result collection this method will call the export() method.
74          * This method returns an array containing the array representations
75          * of every bean in the result set.
76          *
77          * @see Finder::find
78          *
79          * @param string $type     type   the type of bean you are looking for
80          * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
81          * @param array  $bindings values array of values to be bound to parameters in query
82          *
83          * @return array
84          */
85         public function findAndExport( $type, $sql = NULL, $bindings = array() )
86         {
87                 $arr = array();
88                 foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
89                         $arr[] = $item->export();
90                 }
91
92                 return $arr;
93         }
94
95         /**
96          * Like find() but returns just one bean instead of an array of beans.
97          * This method will return only the first bean of the array.
98          * If no beans are found, this method will return NULL.
99          *
100          * @see Finder::find
101          *
102          * @param string $type     type   the type of bean you are looking for
103          * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
104          * @param array  $bindings values array of values to be bound to parameters in query
105          *
106          * @return OODBBean
107          */
108         public function findOne( $type, $sql = NULL, $bindings = array() )
109         {
110                 $sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
111
112                 $items = $this->find( $type, $sql, $bindings );
113
114                 if ( empty($items) ) {
115                         return NULL;
116                 }
117
118                 return reset( $items );
119         }
120
121         /**
122          * Like find() but returns the last bean of the result array.
123          * Opposite of Finder::findLast().
124          * If no beans are found, this method will return NULL.
125          *
126          * @see Finder::find
127          *
128          * @param string $type     the type of bean you are looking for
129          * @param string $sql      SQL query to find the desired bean, starting right after WHERE clause
130          * @param array  $bindings values array of values to be bound to parameters in query
131          *
132          * @return OODBBean
133          */
134         public function findLast( $type, $sql = NULL, $bindings = array() )
135         {
136                 $items = $this->find( $type, $sql, $bindings );
137
138                 if ( empty($items) ) {
139                         return NULL;
140                 }
141
142                 return end( $items );
143         }
144
145         /**
146          * Tries to find beans of a certain type,
147          * if no beans are found, it dispenses a bean of that type.
148          * Note that this function always returns an array.
149          *
150          * @see Finder::find
151          *
152          * @param  string $type     the type of bean you are looking for
153          * @param  string $sql      SQL query to find the desired bean, starting right after WHERE clause
154          * @param  array  $bindings values array of values to be bound to parameters in query
155          *
156          * @return array
157          */
158         public function findOrDispense( $type, $sql = NULL, $bindings = array() )
159         {
160                 $foundBeans = $this->find( $type, $sql, $bindings );
161
162                 if ( empty( $foundBeans ) ) {
163                         return array( $this->redbean->dispense( $type ) );
164                 } else {
165                         return $foundBeans;
166                 }
167         }
168
169         /**
170          * Finds a BeanCollection using the repository.
171          * A bean collection can be used to retrieve one bean at a time using
172          * cursors - this is useful for processing large datasets. A bean collection
173          * will not load all beans into memory all at once, just one at a time.
174          *
175          * @param  string $type     the type of bean you are looking for
176          * @param  string $sql      SQL query to find the desired bean, starting right after WHERE clause
177          * @param  array  $bindings values array of values to be bound to parameters in query
178          *
179          * @return BeanCollection
180          */
181         public function findCollection( $type, $sql, $bindings = array() )
182         {
183                 return $this->redbean->findCollection( $type, $sql, $bindings );
184         }
185
186         /**
187          * Finds or creates a bean.
188          * Tries to find a bean with certain properties specified in the second
189          * parameter ($like). If the bean is found, it will be returned.
190          * If multiple beans are found, only the first will be returned.
191          * If no beans match the criteria, a new bean will be dispensed,
192          * the criteria will be imported as properties and this new bean
193          * will be stored and returned.
194          *
195          * Format of criteria set: property => value
196          * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
197          *
198          * @param string $type type of bean to search for
199          * @param array  $like criteria set describing bean to search for
200          *
201          * @return OODBBean
202          */
203         public function findOrCreate( $type, $like = array() )
204         {
205                         $beans = $this->findLike( $type, $like );
206                         if ( count( $beans ) ) {
207                                 $bean = reset( $beans );
208                                 return $bean;
209                         }
210
211                         $bean = $this->redbean->dispense( $type );
212                         $bean->import( $like );
213                         $this->redbean->store( $bean );
214                         return $bean;
215         }
216
217         /**
218          * Finds beans by its type and a certain criteria set.
219          *
220          * Format of criteria set: property => value
221          * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
222          *
223          * If the additional SQL is a condition, this condition will be glued to the rest
224          * of the query using an AND operator. Note that this is as far as this method
225          * can go, there is no way to glue additional SQL using an OR-condition.
226          * This method provides access to an underlying mechanism in the RedBeanPHP architecture
227          * to find beans using criteria sets. However, please do not use this method
228          * for complex queries, use plain SQL instead ( the regular find method ) as it is
229          * more suitable for the job. This method is
230          * meant for basic search-by-example operations.
231          *
232          * @param string $type       type of bean to search for
233          * @param array  $conditions criteria set describing the bean to search for
234          * @param string $sql        additional SQL (for sorting)
235          *
236          * @return array
237          */
238         public function findLike( $type, $conditions = array(), $sql = '' )
239         {
240                 if ( count( $conditions ) > 0 ) {
241                         foreach( $conditions as $key => $condition ) {
242                                 if ( !count( $condition ) ) unset( $conditions[$key] );
243                         }
244                 }
245
246                 return $this->redbean->find( $type, $conditions, $sql );
247         }
248
249         /**
250          * Returns a hashmap with bean arrays keyed by type using an SQL
251          * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
252          * this method will return movie and review beans.
253          *
254          * Example:
255          *
256          * <code>
257          * $stuff = $finder->findMulti('movie,review', '
258          *          SELECT movie.*, review.* FROM movie
259          *          LEFT JOIN review ON review.movie_id = movie.id');
260          * </code>
261          *
262          * After this operation, $stuff will contain an entry 'movie' containing all
263          * movies and an entry named 'review' containing all reviews (all beans).
264          * You can also pass bindings.
265          *
266          * If you want to re-map your beans, so you can use $movie->ownReviewList without
267          * having RedBeanPHP executing an SQL query you can use the fourth parameter to
268          * define a selection of remapping closures.
269          *
270          * The remapping argument (optional) should contain an array of arrays.
271          * Each array in the remapping array should contain the following entries:
272          *
273          * <code>
274          * array(
275          *      'a'       => TYPE A
276          *    'b'       => TYPE B
277          *    'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
278          *    'do'      => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
279          * )
280          * </code>
281          *
282          * Using this mechanism you can build your own 'preloader' with tiny function
283          * snippets (and those can be re-used and shared online of course).
284          *
285          * Example:
286          *
287          * <code>
288          * array(
289          *      'a'       => 'movie'     //define A as movie
290          *    'b'       => 'review'    //define B as review
291          *    'matcher' => function( $a, $b ) {
292          *       return ( $b->movie_id == $a->id );  //Perform action if review.movie_id equals movie.id
293          *    }
294          *    'do'      => function( $a, $b ) {
295          *       $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
296          *       $a->clearHistory();                 //optional, act 'as if these beans have been loaded through ownReviewList'.
297          *    }
298          * )
299          * </code>
300          *
301          * The Query Template parameter is optional as well but can be used to
302          * set a different SQL template (sprintf-style) for processing the original query.
303          *
304          * @note the SQL query provided IS NOT THE ONE used internally by this function,
305          * this function will pre-process the query to get all the data required to find the beans.
306          *
307          * @note if you use the 'book.*' notation make SURE you're
308          * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
309          * it's actually an SQL-like template SLOT, not real SQL.
310          *
311          * @note instead of an SQL query you can pass a result array as well.
312          *
313          * @param string|array $types         a list of types (either array or comma separated string)
314          * @param string|array $sqlOrArr      an SQL query or an array of prefetched records
315          * @param array        $bindings      optional, bindings for SQL query
316          * @param array        $remappings    optional, an array of remapping arrays
317          * @param string       $queryTemplate optional, query template
318          *
319          * @return array
320          */
321         public function findMulti( $types, $sql, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
322         {
323                 if ( !is_array( $types ) ) $types = explode( ',', $types );
324                 if ( !is_array( $sql ) ) {
325                         $writer = $this->toolbox->getWriter();
326                         $adapter = $this->toolbox->getDatabaseAdapter();
327
328                         //Repair the query, replace book.* with book.id AS book_id etc..
329                         foreach( $types as $type ) {
330                                 $pattern = " {$type}.*";
331                                 if ( strpos( $sql, $pattern ) !== FALSE ) {
332                                         $newSelectorArray = array();
333                                         $columns = $writer->getColumns( $type );
334                                         foreach( $columns as $column => $definition ) {
335                                                 $newSelectorArray[] = sprintf( $queryTemplate, $type, $column, $type, $column );
336                                         }
337                                         $newSelector = implode( ',', $newSelectorArray );
338                                         $sql = str_replace( $pattern, $newSelector, $sql );
339                                 }
340                         }
341
342                         $rows = $adapter->get( $sql, $bindings );
343                 } else {
344                         $rows = $sql;
345                 }
346
347                 //Gather the bean data from the query results using the prefix
348                 $wannaBeans = array();
349                 foreach( $types as $type ) {
350                         $wannaBeans[$type] = array();
351                         $prefix            = "{$type}__";
352                         foreach( $rows as $rowkey=>$row ) {
353                                 $wannaBean = array();
354                                 foreach( $row as $cell => $value ) {
355                                         if ( strpos( $cell, $prefix ) === 0 ) {
356                                                 $property = substr( $cell, strlen( $prefix ) );
357                                                 unset( $rows[$rowkey][$cell] );
358                                                 $wannaBean[$property] = $value;
359                                         }
360                                 }
361                                 if ( !isset( $wannaBean['id'] ) ) continue;
362                                 if ( is_null( $wannaBean['id'] ) ) continue;
363                                 $wannaBeans[$type][$wannaBean['id']] = $wannaBean;
364                         }
365                 }
366
367                 //Turn the rows into beans
368                 $beans = array();
369                 foreach( $wannaBeans as $type => $wannabees ) {
370                         $beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
371                 }
372
373                 //Apply additional re-mappings
374                 foreach($remappings as $remapping) {
375                         $a       = $remapping['a'];
376                         $b       = $remapping['b'];
377                         $matcher = $remapping['matcher'];
378                         $do      = $remapping['do'];
379                         foreach( $beans[$a] as $bean ) {
380                                 foreach( $beans[$b] as $putBean ) {
381                                         if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping );
382                                 }
383                         }
384                 }
385                 return $beans;
386         }
387 }