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.
13 * @file RedBeanPHP/Finder.php
14 * @author Gabor de Mooij and the RedBeanPHP Community
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.
36 * The Finder requires a toolbox.
38 * @param ToolBox $toolbox
40 public function __construct( ToolBox $toolbox )
42 $this->toolbox = $toolbox;
43 $this->redbean = $toolbox->getRedBean();
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).
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
59 public function find( $type, $sql = NULL, $bindings = array() )
61 if ( !is_array( $bindings ) ) {
62 throw new RedException(
63 'Expected array, ' . gettype( $bindings ) . ' given.'
67 return $this->redbean->find( $type, array(), $sql, $bindings );
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.
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
85 public function findAndExport( $type, $sql = NULL, $bindings = array() )
88 foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
89 $arr[] = $item->export();
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.
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
108 public function findOne( $type, $sql = NULL, $bindings = array() )
110 $sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
112 $items = $this->find( $type, $sql, $bindings );
114 if ( empty($items) ) {
118 return reset( $items );
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.
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
134 public function findLast( $type, $sql = NULL, $bindings = array() )
136 $items = $this->find( $type, $sql, $bindings );
138 if ( empty($items) ) {
142 return end( $items );
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.
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
158 public function findOrDispense( $type, $sql = NULL, $bindings = array() )
160 $foundBeans = $this->find( $type, $sql, $bindings );
162 if ( empty( $foundBeans ) ) {
163 return array( $this->redbean->dispense( $type ) );
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.
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
179 * @return BeanCollection
181 public function findCollection( $type, $sql, $bindings = array() )
183 return $this->redbean->findCollection( $type, $sql, $bindings );
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.
195 * Format of criteria set: property => value
196 * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
198 * @param string $type type of bean to search for
199 * @param array $like criteria set describing bean to search for
203 public function findOrCreate( $type, $like = array() )
205 $beans = $this->findLike( $type, $like );
206 if ( count( $beans ) ) {
207 $bean = reset( $beans );
211 $bean = $this->redbean->dispense( $type );
212 $bean->import( $like );
213 $this->redbean->store( $bean );
218 * Finds beans by its type and a certain criteria set.
220 * Format of criteria set: property => value
221 * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
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.
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)
238 public function findLike( $type, $conditions = array(), $sql = '' )
240 if ( count( $conditions ) > 0 ) {
241 foreach( $conditions as $key => $condition ) {
242 if ( !count( $condition ) ) unset( $conditions[$key] );
246 return $this->redbean->find( $type, $conditions, $sql );
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.
257 * $stuff = $finder->findMulti('movie,review', '
258 * SELECT movie.*, review.* FROM movie
259 * LEFT JOIN review ON review.movie_id = movie.id');
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.
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.
270 * The remapping argument (optional) should contain an array of arrays.
271 * Each array in the remapping array should contain the following entries:
277 * 'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
278 * 'do' => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
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).
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
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'.
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.
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.
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.
311 * @note instead of an SQL query you can pass a result array as well.
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
321 public function findMulti( $types, $sql, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
323 if ( !is_array( $types ) ) $types = explode( ',', $types );
324 if ( !is_array( $sql ) ) {
325 $writer = $this->toolbox->getWriter();
326 $adapter = $this->toolbox->getDatabaseAdapter();
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 );
337 $newSelector = implode( ',', $newSelectorArray );
338 $sql = str_replace( $pattern, $newSelector, $sql );
342 $rows = $adapter->get( $sql, $bindings );
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;
361 if ( !isset( $wannaBean['id'] ) ) continue;
362 if ( is_null( $wannaBean['id'] ) ) continue;
363 $wannaBeans[$type][$wannaBean['id']] = $wannaBean;
367 //Turn the rows into beans
369 foreach( $wannaBeans as $type => $wannabees ) {
370 $beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
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 );