91250e095eef4f3405a942b14385d8ebbd1f5cd2
[yaffs-website] / vendor / gabordemooij / redbean / RedBeanPHP / Repository / Fluid.php
1 <?php
2
3 namespace RedBeanPHP\Repository;
4
5 use RedBeanPHP\OODBBean as OODBBean;
6 use RedBeanPHP\QueryWriter as QueryWriter;
7 use RedBeanPHP\RedException as RedException;
8 use RedBeanPHP\BeanHelper as BeanHelper;
9 use RedBeanPHP\RedException\SQL as SQLException;
10 use RedBeanPHP\Repository as Repository;
11
12 /**
13  * Fluid Repository.
14  * OODB manages two repositories, a fluid one that
15  * adjust the database schema on-the-fly to accomodate for
16  * new bean types (tables) and new properties (columns) and
17  * a frozen one for use in a production environment. OODB
18  * allows you to swap the repository instances using the freeze()
19  * method.
20  *
21  * @file    RedBeanPHP/Repository/Fluid.php
22  * @author  Gabor de Mooij and the RedBeanPHP community
23  * @license BSD/GPLv2
24  *
25  * @copyright
26  * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
27  * This source file is subject to the BSD/GPLv2 License that is bundled
28  * with this source code in the file license.txt.
29  */
30 class Fluid extends Repository
31 {
32         /**
33          * Figures out the desired type given the cast string ID.
34          * Given a cast ID, this method will return the associated
35          * type (INT(10) or VARCHAR for instance). The returned type
36          * can be processed by the Query Writer to build the specified
37          * column for you in the database. The Cast ID is actually just
38          * a superset of the QueryWriter types. In addition to default
39          * Query Writer column types you can pass the following 'cast types':
40          * 'id' and 'string'. These will map to Query Writer specific
41          * column types (probably INT and VARCHAR).
42          *
43          * @param string $cast cast identifier
44          *
45          * @return integer
46          */
47         private function getTypeFromCast( $cast )
48         {
49                 if ( $cast == 'string' ) {
50                         $typeno = $this->writer->scanType( 'STRING' );
51                 } elseif ( $cast == 'id' ) {
52                         $typeno = $this->writer->getTypeForID();
53                 } elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
54                         $typeno = $this->writer->sqltype_typeno[$cast];
55                 } else {
56                         throw new RedException( 'Invalid Cast' );
57                 }
58
59                 return $typeno;
60         }
61
62         /**
63          * Orders the Query Writer to create a table if it does not exist already and
64          * adds a note in the build report about the creation.
65          *
66          * @param OODBBean $bean bean to update report of
67          * @param string         $table table to check and create if not exists
68          *
69          * @return void
70          */
71         private function createTableIfNotExists( OODBBean $bean, $table )
72         {
73                 //Does table exist? If not, create
74                 if ( !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
75                         $this->writer->createTable( $table );
76                         $bean->setMeta( 'buildreport.flags.created', TRUE );
77                 }
78         }
79
80         /**
81          * Modifies the table to fit the bean data.
82          * Given a property and a value and the bean, this method will
83          * adjust the table structure to fit the requirements of the property and value.
84          * This may include adding a new column or widening an existing column to hold a larger
85          * or different kind of value. This method employs the writer to adjust the table
86          * structure in the database. Schema updates are recorded in meta properties of the bean.
87          *
88          * This method will also apply indexes, unique constraints and foreign keys.
89          *
90          * @param OODBBean $bean     bean to get cast data from and store meta in
91          * @param string   $property property to store
92          * @param mixed    $value    value to store
93          *
94          * @return void
95          */
96         private function modifySchema( OODBBean $bean, $property, $value )
97         {
98                 $doFKStuff = FALSE;
99                 $table   = $bean->getMeta( 'type' );
100                 $columns = $this->writer->getColumns( $table );
101                 $columnNoQ = $this->writer->esc( $property, TRUE );
102                 if ( !$this->oodb->isChilled( $bean->getMeta( 'type' ) ) ) {
103                         if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
104                                 $cast   = $bean->getMeta( "cast.$property" );
105                                 $typeno = $this->getTypeFromCast( $cast );
106                         } else {
107                                 $cast   = FALSE;
108                                 $typeno = $this->writer->scanType( $value, TRUE );
109                         }
110                         if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
111                                 if ( !$cast ) { //rescan without taking into account special types >80
112                                         $typeno = $this->writer->scanType( $value, FALSE );
113                                 }
114                                 $sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
115                                 if ( $typeno > $sqlt ) { //no, we have to widen the database column type
116                                         $this->writer->widenColumn( $table, $property, $typeno );
117                                         $bean->setMeta( 'buildreport.flags.widen', TRUE );
118                                         $doFKStuff = TRUE;
119                                 }
120                         } else {
121                                 $this->writer->addColumn( $table, $property, $typeno );
122                                 $bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
123                                 $doFKStuff = TRUE;
124                         }
125                         if ($doFKStuff) {
126                                 if (strrpos($columnNoQ, '_id')===(strlen($columnNoQ)-3)) {
127                                         $destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ)-3);
128                                         $indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}";
129                                         $this->writer->addIndex($table, $indexName, $columnNoQ);
130                                         $typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ);
131                                         $isLink = $bean->getMeta( 'sys.buildcommand.unique', FALSE );
132                                         //Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean
133                                         $isDep = ( $bean->moveMeta( 'sys.buildcommand.fkdependson' ) === $typeof || is_array( $isLink ) );
134                                         $result = $this->writer->addFK( $table, $typeof, $columnNoQ, 'id', $isDep );
135                                         //If this is a link bean and all unique columns have been added already, then apply unique constraint
136                                         if ( is_array( $isLink ) && !count( array_diff( $isLink, array_keys( $this->writer->getColumns( $table ) ) ) ) ) {
137                                                 $this->writer->addUniqueConstraint( $table, $bean->moveMeta('sys.buildcommand.unique') );
138                                                 $bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL);
139                                         }
140                                 }
141                         }
142                 }
143         }
144
145         /**
146          * Part of the store() functionality.
147          * Handles all new additions after the bean has been saved.
148          * Stores addition bean in own-list, extracts the id and
149          * adds a foreign key. Also adds a constraint in case the type is
150          * in the dependent list.
151          *
152          * Note that this method raises a custom exception if the bean
153          * is not an instance of OODBBean. Therefore it does not use
154          * a type hint. This allows the user to take action in case
155          * invalid objects are passed in the list.
156          *
157          * @param OODBBean $bean         bean to process
158          * @param array    $ownAdditions list of addition beans in own-list
159          *
160          * @return void
161          */
162         protected function processAdditions( $bean, $ownAdditions )
163         {
164                 $beanType = $bean->getMeta( 'type' );
165
166                 foreach ( $ownAdditions as $addition ) {
167                         if ( $addition instanceof OODBBean ) {
168
169                                 $myFieldLink = $beanType . '_id';
170                                 $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
171                                 if ( $alias ) $myFieldLink = $alias . '_id';
172
173                                 $addition->$myFieldLink = $bean->id;
174                                 $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
175
176                                 if ($alias) {
177                                         $addition->setMeta( "sys.typeof.{$alias}", $beanType );
178                                 } else {
179                                         $addition->setMeta( "sys.typeof.{$beanType}", $beanType );
180                                 }
181
182                                 $this->store( $addition );
183                         } else {
184                                 throw new RedException( 'Array may only contain OODBBeans' );
185                         }
186                 }
187         }
188
189         /**
190          * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
191          * method. When all lists and embedded beans (parent objects) have been processed and
192          * removed from the original bean the bean is passed to this method to be stored
193          * in the database.
194          *
195          * @param OODBBean $bean the clean bean
196          *
197          * @return void
198          */
199         protected function storeBean( OODBBean $bean )
200         {
201                 if ( $bean->getMeta( 'changed' ) ) {
202                         $this->check( $bean );
203                         $table = $bean->getMeta( 'type' );
204                         $this->createTableIfNotExists( $bean, $table );
205
206                         $updateValues = array();
207                         foreach ( $bean as $property => $value ) {
208                                 if ( $property !== 'id' ) {
209                                         $this->modifySchema( $bean, $property, $value );
210                                 }
211                                 if ( $property !== 'id' ) {
212                                         $updateValues[] = array( 'property' => $property, 'value' => $value );
213                                 }
214                         }
215
216                         $bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
217                         $bean->setMeta( 'changed', FALSE );
218                 }
219                 $bean->setMeta( 'tainted', FALSE );
220         }
221
222         /**
223          * Handles exceptions. Suppresses exceptions caused by missing structures.
224          *
225          * @param Exception $exception exception
226          *
227          * @return void
228          */
229         protected function handleException( \Exception $exception )
230         {
231                 if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
232                         array(
233                                 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
234                                 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) )
235                 ) {
236                         throw $exception;
237                 }
238         }
239
240         /**
241          * Dispenses a new bean (a OODBBean Bean Object)
242          * of the specified type. Always
243          * use this function to get an empty bean object. Never
244          * instantiate a OODBBean yourself because it needs
245          * to be configured before you can use it with RedBean. This
246          * function applies the appropriate initialization /
247          * configuration for you.
248          *
249          * @param string  $type              type of bean you want to dispense
250          * @param string  $number            number of beans you would like to get
251          * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
252          *
253          * @return OODBBean
254          */
255         public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
256         {
257                 $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
258                 $beans = array();
259                 for ( $i = 0; $i < $number; $i++ ) {
260                         $bean = new $OODBBEAN;
261                         $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
262                         $this->check( $bean );
263                         $this->oodb->signal( 'dispense', $bean );
264                         $beans[] = $bean;
265                 }
266
267                 return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
268         }
269
270         /**
271          * Loads a bean from the object database.
272          * It searches for a OODBBean Bean Object in the
273          * database. It does not matter how this bean has been stored.
274          * RedBean uses the primary key ID $id and the string $type
275          * to find the bean. The $type specifies what kind of bean you
276          * are looking for; this is the same type as used with the
277          * dispense() function. If RedBean finds the bean it will return
278          * the OODB Bean object; if it cannot find the bean
279          * RedBean will return a new bean of type $type and with
280          * primary key ID 0. In the latter case it acts basically the
281          * same as dispense().
282          *
283          * Important note:
284          * If the bean cannot be found in the database a new bean of
285          * the specified type will be generated and returned.
286          *
287          * @param string  $type type of bean you want to load
288          * @param integer $id   ID of the bean you want to load
289          *
290          * @return OODBBean
291          */
292         public function load( $type, $id )
293         {
294                 $bean = $this->dispense( $type );
295                 if ( isset( $this->stash[$this->nesting][$id] ) ) {
296                         $row = $this->stash[$this->nesting][$id];
297                 } else {
298                         try {
299                                 $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
300                         } catch ( SQLException $exception ) {
301                                 if ( $this->writer->sqlStateIn( $exception->getSQLState(),
302                                         array(
303                                                 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
304                                                 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
305                                 )
306                                 ) {
307                                         $rows = 0;
308                                 }
309                         }
310                         if ( empty( $rows ) ) {
311                                 return $bean;
312                         }
313                         $row = array_pop( $rows );
314                 }
315                 $bean->importRow( $row );
316                 $this->nesting++;
317                 $this->oodb->signal( 'open', $bean );
318                 $this->nesting--;
319
320                 return $bean->setMeta( 'tainted', FALSE );
321         }
322 }