5 use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6 use RedBeanPHP\QueryWriter as QueryWriter;
7 use RedBeanPHP\RedException as RedException;
8 use RedBeanPHP\RedException\SQL as SQLException;
11 * Association Manager.
12 * Manages simple bean associations.
14 * @file RedBeanPHP/AssociationManager.php
15 * @author Gabor de Mooij and the RedBeanPHP Community
19 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
20 * This source file is subject to the BSD/GPLv2 License that is bundled
21 * with this source code in the file license.txt.
23 class AssociationManager extends Observable
41 * Handles exceptions. Suppresses exceptions caused by missing structures.
43 * @param \Exception $exception exception to handle
47 private function handleException( \Exception $exception )
49 if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
51 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
52 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN )
61 * Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
62 * $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
64 * @param OODBBean $bean reference bean instance
65 * @param string $type target bean type
66 * @param string $sql additional SQL snippet
67 * @param array $bindings bindings for query
71 private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
73 $ids = array( $bean->id );
74 $sourceType = $bean->getMeta( 'type' );
76 return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
77 } catch ( SQLException $exception ) {
78 $this->handleException( $exception );
84 * Associates a pair of beans. This method associates two beans, no matter
85 * what types. Accepts a base bean that contains data for the linking record.
86 * This method is used by associate. This method also accepts a base bean to be used
87 * as the template for the link record in the database.
89 * @param OODBBean $bean1 first bean
90 * @param OODBBean $bean2 second bean
91 * @param OODBBean $bean base bean (association record)
95 protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
97 $type = $bean->getMeta( 'type' );
98 $property1 = $bean1->getMeta( 'type' ) . '_id';
99 $property2 = $bean2->getMeta( 'type' ) . '_id';
101 if ( $property1 == $property2 ) {
102 $property2 = $bean2->getMeta( 'type' ) . '2_id';
105 $this->oodb->store( $bean1 );
106 $this->oodb->store( $bean2 );
108 $bean->setMeta( "cast.$property1", "id" );
109 $bean->setMeta( "cast.$property2", "id" );
110 $bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
112 $bean->$property1 = $bean1->id;
113 $bean->$property2 = $bean2->id;
118 $id = $this->oodb->store( $bean );
120 } catch ( SQLException $exception ) {
121 if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
122 array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ) )
134 * @param ToolBox $tools toolbox
136 public function __construct( ToolBox $tools )
138 $this->oodb = $tools->getRedBean();
139 $this->adapter = $tools->getDatabaseAdapter();
140 $this->writer = $tools->getWriter();
141 $this->toolbox = $tools;
145 * Creates a table name based on a types array.
146 * Manages the get the correct name for the linking table for the
149 * @param array $types 2 types as strings
153 public function getTable( $types )
155 return $this->writer->getAssocTable( $types );
159 * Associates two beans in a many-to-many relation.
160 * This method will associate two beans and store the connection between the
161 * two in a link table. Instead of two single beans this method also accepts
162 * two sets of beans. Returns the ID or the IDs of the linking beans.
164 * @param OODBBean|array $beans1 one or more beans to form the association
165 * @param OODBBean|array $beans2 one or more beans to form the association
169 public function associate( $beans1, $beans2 )
171 if ( !is_array( $beans1 ) ) {
172 $beans1 = array( $beans1 );
175 if ( !is_array( $beans2 ) ) {
176 $beans2 = array( $beans2 );
180 foreach ( $beans1 as $bean1 ) {
181 foreach ( $beans2 as $bean2 ) {
182 $table = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
183 $bean = $this->oodb->dispense( $table );
184 $results[] = $this->associateBeans( $bean1, $bean2, $bean );
188 return ( count( $results ) > 1 ) ? $results : reset( $results );
192 * Counts the number of related beans in an N-M relation.
193 * This method returns the number of beans of type $type associated
194 * with reference bean(s) $bean. The query can be tuned using an
195 * SQL snippet for additional filtering.
197 * @param OODBBean|array $bean a bean object or an array of beans
198 * @param string $type type of bean you're interested in
199 * @param string $sql SQL snippet (optional)
200 * @param array $bindings bindings for your SQL string
204 public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
206 if ( !( $bean instanceof OODBBean ) ) {
207 throw new RedException(
208 'Expected array or OODBBean but got:' . gettype( $bean )
216 $beanType = $bean->getMeta( 'type' );
219 return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
220 } catch ( SQLException $exception ) {
221 $this->handleException( $exception );
228 * Breaks the association between two beans. This method unassociates two beans. If the
229 * method succeeds the beans will no longer form an association. In the database
230 * this means that the association record will be removed. This method uses the
231 * OODB trash() method to remove the association links, thus giving FUSE models the
232 * opportunity to hook-in additional business logic. If the $fast parameter is
233 * set to boolean TRUE this method will remove the beans without their consent,
234 * bypassing FUSE. This can be used to improve performance.
236 * @param OODBBean $bean1 first bean in target association
237 * @param OODBBean $bean2 second bean in target association
238 * @param boolean $fast if TRUE, removes the entries by query without FUSE
242 public function unassociate( $beans1, $beans2, $fast = NULL )
244 $beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
245 $beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
247 foreach ( $beans1 as $bean1 ) {
248 foreach ( $beans2 as $bean2 ) {
250 $this->oodb->store( $bean1 );
251 $this->oodb->store( $bean2 );
253 $type1 = $bean1->getMeta( 'type' );
254 $type2 = $bean2->getMeta( 'type' );
256 $row = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
257 $linkType = $this->getTable( array( $type1, $type2 ) );
260 $this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
265 $beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
267 if ( count( $beans ) > 0 ) {
268 $bean = reset( $beans );
269 $this->oodb->trash( $bean );
271 } catch ( SQLException $exception ) {
272 $this->handleException( $exception );
279 * Removes all relations for a bean. This method breaks every connection between
280 * a certain bean $bean and every other bean of type $type. Warning: this method
281 * is really fast because it uses a direct SQL query however it does not inform the
282 * models about this. If you want to notify FUSE models about deletion use a foreach-loop
283 * with unassociate() instead. (that might be slower though)
285 * @param OODBBean $bean reference bean
286 * @param string $type type of beans that need to be unassociated
290 public function clearRelations( OODBBean $bean, $type )
292 $this->oodb->store( $bean );
294 $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
295 } catch ( SQLException $exception ) {
296 $this->handleException( $exception );
301 * Returns all the beans associated with $bean.
302 * This method will return an array containing all the beans that have
303 * been associated once with the associate() function and are still
304 * associated with the bean specified. The type parameter indicates the
305 * type of beans you are looking for. You can also pass some extra SQL and
306 * values for that SQL to filter your results after fetching the
309 * Don't try to make use of subqueries, a subquery using IN() seems to
310 * be slower than two queries!
312 * Since 3.2, you can now also pass an array of beans instead just one
313 * bean as the first parameter.
315 * @param OODBBean|array $bean the bean you have
316 * @param string $type the type of beans you want
317 * @param string $sql SQL snippet for extra filtering
318 * @param array $bindings values to be inserted in SQL slots
322 public function related( $bean, $type, $sql = '', $bindings = array() )
324 $sql = $this->writer->glueSQLCondition( $sql );
325 $rows = $this->relatedRows( $bean, $type, $sql, $bindings );
328 foreach ( $rows as $key => $row ) {
329 if ( !isset( $links[$row['id']] ) ) $links[$row['id']] = array();
330 $links[$row['id']][] = $row['linked_by'];
331 unset( $rows[$key]['linked_by'] );
334 $beans = $this->oodb->convertToBeans( $type, $rows );
335 foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );