Yaffs site version 1.1
[yaffs-website] / vendor / gabordemooij / redbean / RedBeanPHP / AssociationManager.php
1 <?php
2
3 namespace RedBeanPHP;
4
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;
9
10 /**
11  * Association Manager.
12  * Manages simple bean associations.
13  *
14  * @file    RedBeanPHP/AssociationManager.php
15  * @author  Gabor de Mooij and the RedBeanPHP Community
16  * @license BSD/GPLv2
17  *
18  * @copyright
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.
22  */
23 class AssociationManager extends Observable
24 {
25         /**
26          * @var OODB
27          */
28         protected $oodb;
29
30         /**
31          * @var DBAdapter
32          */
33         protected $adapter;
34
35         /**
36          * @var QueryWriter
37          */
38         protected $writer;
39
40         /**
41          * Handles exceptions. Suppresses exceptions caused by missing structures.
42          *
43          * @param \Exception $exception exception to handle
44          *
45          * @return void
46          */
47         private function handleException( \Exception $exception )
48         {
49                 if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
50                         array(
51                                 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
52                                 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN )
53                         )
54                 ) {
55                         throw $exception;
56                 }
57         }
58
59         /**
60          * Internal method.
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.
63          *
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
68          *
69          * @return array
70          */
71         private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
72         {
73                 $ids = array( $bean->id );
74                 $sourceType = $bean->getMeta( 'type' );
75                 try {
76                         return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
77                 } catch ( SQLException $exception ) {
78                         $this->handleException( $exception );
79                         return array();
80                 }
81         }
82
83         /**
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.
88          *
89          * @param OODBBean $bean1 first bean
90          * @param OODBBean $bean2 second bean
91          * @param OODBBean $bean  base bean (association record)
92          *
93          * @return mixed
94          */
95         protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
96         {
97                 $type      = $bean->getMeta( 'type' );
98                 $property1 = $bean1->getMeta( 'type' ) . '_id';
99                 $property2 = $bean2->getMeta( 'type' ) . '_id';
100
101                 if ( $property1 == $property2 ) {
102                         $property2 = $bean2->getMeta( 'type' ) . '2_id';
103                 }
104
105                 $this->oodb->store( $bean1 );
106                 $this->oodb->store( $bean2 );
107
108                 $bean->setMeta( "cast.$property1", "id" );
109                 $bean->setMeta( "cast.$property2", "id" );
110                 $bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
111
112                 $bean->$property1 = $bean1->id;
113                 $bean->$property2 = $bean2->id;
114
115                 $results   = array();
116
117                 try {
118                         $id = $this->oodb->store( $bean );
119                         $results[] = $id;
120                 } catch ( SQLException $exception ) {
121                         if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
122                                 array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ) )
123                         ) {
124                                 throw $exception;
125                         }
126                 }
127
128                 return $results;
129         }
130
131         /**
132          * Constructor
133          *
134          * @param ToolBox $tools toolbox
135          */
136         public function __construct( ToolBox $tools )
137         {
138                 $this->oodb    = $tools->getRedBean();
139                 $this->adapter = $tools->getDatabaseAdapter();
140                 $this->writer  = $tools->getWriter();
141                 $this->toolbox = $tools;
142         }
143
144         /**
145          * Creates a table name based on a types array.
146          * Manages the get the correct name for the linking table for the
147          * types provided.
148          *
149          * @param array $types 2 types as strings
150          *
151          * @return string
152          */
153         public function getTable( $types )
154         {
155                 return $this->writer->getAssocTable( $types );
156         }
157
158         /**
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.
163          *
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
166          *
167          * @return array
168          */
169         public function associate( $beans1, $beans2 )
170         {
171                 if ( !is_array( $beans1 ) ) {
172                         $beans1 = array( $beans1 );
173                 }
174
175                 if ( !is_array( $beans2 ) ) {
176                         $beans2 = array( $beans2 );
177                 }
178
179                 $results = array();
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 );
185                         }
186                 }
187
188                 return ( count( $results ) > 1 ) ? $results : reset( $results );
189         }
190
191         /**
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.
196          *
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
201          *
202          * @return integer
203          */
204         public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
205         {
206                 if ( !( $bean instanceof OODBBean ) ) {
207                         throw new RedException(
208                                 'Expected array or OODBBean but got:' . gettype( $bean )
209                         );
210                 }
211
212                 if ( !$bean->id ) {
213                         return 0;
214                 }
215
216                 $beanType = $bean->getMeta( 'type' );
217
218                 try {
219                         return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
220                 } catch ( SQLException $exception ) {
221                         $this->handleException( $exception );
222
223                         return 0;
224                 }
225         }
226
227         /**
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.
235          *
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
239          *
240          * @return void
241          */
242         public function unassociate( $beans1, $beans2, $fast = NULL )
243         {
244                 $beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
245                 $beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
246
247                 foreach ( $beans1 as $bean1 ) {
248                         foreach ( $beans2 as $bean2 ) {
249                                 try {
250                                         $this->oodb->store( $bean1 );
251                                         $this->oodb->store( $bean2 );
252
253                                         $type1 = $bean1->getMeta( 'type' );
254                                         $type2 = $bean2->getMeta( 'type' );
255
256                                         $row      = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
257                                         $linkType = $this->getTable( array( $type1, $type2 ) );
258
259                                         if ( $fast ) {
260                                                 $this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
261
262                                                 return;
263                                         }
264
265                                         $beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
266
267                                         if ( count( $beans ) > 0 ) {
268                                                 $bean = reset( $beans );
269                                                 $this->oodb->trash( $bean );
270                                         }
271                                 } catch ( SQLException $exception ) {
272                                         $this->handleException( $exception );
273                                 }
274                         }
275                 }
276         }
277
278         /**
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)
284          *
285          * @param OODBBean $bean reference bean
286          * @param string   $type type of beans that need to be unassociated
287          *
288          * @return void
289          */
290         public function clearRelations( OODBBean $bean, $type )
291         {
292                 $this->oodb->store( $bean );
293                 try {
294                         $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
295                 } catch ( SQLException $exception ) {
296                         $this->handleException( $exception );
297                 }
298         }
299
300         /**
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
307          * related beans.
308          *
309          * Don't try to make use of subqueries, a subquery using IN() seems to
310          * be slower than two queries!
311          *
312          * Since 3.2, you can now also pass an array of beans instead just one
313          * bean as the first parameter.
314          *
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
319          *
320          * @return array
321          */
322         public function related( $bean, $type, $sql = '', $bindings = array() )
323         {
324                 $sql   = $this->writer->glueSQLCondition( $sql );
325                 $rows  = $this->relatedRows( $bean, $type, $sql, $bindings );
326                 $links = array();
327
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'] );
332                 }
333
334                 $beans = $this->oodb->convertToBeans( $type, $rows );
335                 foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
336
337                 return $beans;
338         }
339 }