Asterian

Untitled

Oct 21st, 2016
473
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 366.21 KB | None | 0 0
  1. <?php
  2.  
  3. namespace RedBeanPHP {
  4.  
  5. /**
  6. * RedBean Logging interface.
  7. * Provides a uniform and convenient logging
  8. * interface throughout RedBeanPHP.
  9. *
  10. * @file RedBean/Logging.php
  11. * @author Gabor de Mooij and the RedBeanPHP Community
  12. * @license BSD/GPLv2
  13. *
  14. * @copyright
  15. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  16. * This source file is subject to the BSD/GPLv2 License that is bundled
  17. * with this source code in the file license.txt.
  18. */
  19. interface Logger
  20. {
  21. /**
  22. * A logger (for PDO or OCI driver) needs to implement the log method.
  23. * The log method will receive logging data. Note that the number of parameters is 0, this means
  24. * all parameters are optional and the number may vary. This way the logger can be used in a very
  25. * flexible way. Sometimes the logger is used to log a simple error message and in other
  26. * situations sql and bindings are passed.
  27. * The log method should be able to accept all kinds of parameters and data by using
  28. * functions like func_num_args/func_get_args.
  29. *
  30. * @param string $message, ...
  31. * @return void
  32. */
  33. public function log();
  34. }
  35. }
  36.  
  37. namespace RedBeanPHP\Logger {
  38.  
  39. use RedBeanPHP\Logger as Logger;
  40. use RedBeanPHP\RedException as RedException;
  41.  
  42. /**
  43. * Logger. Provides a basic logging function for RedBeanPHP.
  44. *
  45. * @file RedBeanPHP/Logger.php
  46. * @author Gabor de Mooij and the RedBeanPHP Community
  47. * @license BSD/GPLv2
  48. *
  49. * @copyright
  50. * copyright (c) G.J.G.T. (Gabor) de Mooij
  51. * This source file is subject to the BSD/GPLv2 License that is bundled
  52. * with this source code in the file license.txt.
  53. */
  54. class RDefault implements Logger
  55. {
  56. /**
  57. * Logger modes
  58. */
  59. const C_LOGGER_ECHO = 0;
  60. const C_LOGGER_ARRAY = 1;
  61.  
  62. /**
  63. * @var integer
  64. */
  65. protected $mode = 0;
  66.  
  67. /**
  68. * @var array
  69. */
  70. protected $logs = array();
  71.  
  72. /**
  73. * Default logger method logging to STDOUT.
  74. * This is the default/reference implementation of a logger.
  75. * This method will write the message value to STDOUT (screen) unless
  76. * you have changed the mode of operation to C_LOGGER_ARRAY.
  77. *
  78. * @param $message (optional) message to log (might also be data or output)
  79. *
  80. * @return void
  81. */
  82. public function log()
  83. {
  84. if ( func_num_args() < 1 ) return;
  85.  
  86. foreach ( func_get_args() as $argument ) {
  87. if ( is_array( $argument ) ) {
  88. $log = print_r( $argument, TRUE );
  89. if ( $this->mode === self::C_LOGGER_ECHO ) {
  90. echo $log;
  91. } else {
  92. $this->logs[] = $log;
  93. }
  94. } else {
  95. if ( $this->mode === self::C_LOGGER_ECHO ) {
  96. echo $argument;
  97. } else {
  98. $this->logs[] = $argument;
  99. }
  100. }
  101.  
  102. if ( $this->mode === self::C_LOGGER_ECHO ) echo "<br>" . PHP_EOL;
  103. }
  104. }
  105.  
  106. /**
  107. * Returns the internal log array.
  108. * The internal log array is where all log messages are stored.
  109. *
  110. * @return array
  111. */
  112. public function getLogs()
  113. {
  114. return $this->logs;
  115. }
  116.  
  117. /**
  118. * Clears the internal log array, removing all
  119. * previously stored entries.
  120. *
  121. * @return self
  122. */
  123. public function clear()
  124. {
  125. $this->logs = array();
  126. return $this;
  127. }
  128.  
  129. /**
  130. * Selects a logging mode.
  131. * There are several options available.
  132. *
  133. * * C_LOGGER_ARRAY - log silently, stores entries in internal log array only
  134. * * C_LOGGER_ECHO - also forward log messages directly to STDOUT
  135. *
  136. * @param integer $mode mode of operation for logging object
  137. *
  138. * @return self
  139. */
  140. public function setMode( $mode )
  141. {
  142. if ($mode !== self::C_LOGGER_ARRAY && $mode !== self::C_LOGGER_ECHO ) {
  143. throw new RedException( 'Invalid mode selected for logger, use C_LOGGER_ARRAY or C_LOGGER_ECHO.' );
  144. }
  145. $this->mode = $mode;
  146. return $this;
  147. }
  148.  
  149. /**
  150. * Searches for all log entries in internal log array
  151. * for $needle and returns those entries.
  152. * This method will return an array containing all matches for your
  153. * search query.
  154. *
  155. * @param string $needle phrase to look for in internal log array
  156. *
  157. * @return array
  158. */
  159. public function grep( $needle )
  160. {
  161. $found = array();
  162. foreach( $this->logs as $logEntry ) {
  163. if ( strpos( $logEntry, $needle ) !== FALSE ) $found[] = $logEntry;
  164. }
  165. return $found;
  166. }
  167. }
  168. }
  169.  
  170. namespace RedBeanPHP\Logger\RDefault {
  171.  
  172. use RedBeanPHP\Logger as Logger;
  173. use RedBeanPHP\Logger\RDefault as RDefault;
  174. use RedBeanPHP\RedException as RedException;
  175.  
  176. /**
  177. * Debug logger.
  178. * A special logger for debugging purposes.
  179. * Provides debugging logging functions for RedBeanPHP.
  180. *
  181. * @file RedBeanPHP/Logger/RDefault/Debug.php
  182. * @author Gabor de Mooij and the RedBeanPHP Community
  183. * @license BSD/GPLv2
  184. *
  185. * @copyright
  186. * copyright (c) G.J.G.T. (Gabor) de Mooij
  187. * This source file is subject to the BSD/GPLv2 License that is bundled
  188. * with this source code in the file license.txt.
  189. */
  190. class Debug extends RDefault implements Logger
  191. {
  192. /**
  193. * @var integer
  194. */
  195. private $strLen = 40;
  196.  
  197. /**
  198. * Writes a query for logging with all bindings / params filled
  199. * in.
  200. *
  201. * @param string $newSql the query
  202. * @param array $bindings the bindings to process (key-value pairs)
  203. *
  204. * @return string
  205. */
  206. private function writeQuery( $newSql, $newBindings )
  207. {
  208. //avoid str_replace collisions: slot1 and slot10 (issue 407).
  209. uksort( $newBindings, function( $a, $b ) {
  210. return ( strlen( $b ) - strlen( $a ) );
  211. } );
  212.  
  213. $newStr = $newSql;
  214. foreach( $newBindings as $slot => $value ) {
  215. if ( strpos( $slot, ':' ) === 0 ) {
  216. $newStr = str_replace( $slot, $this->fillInValue( $value ), $newStr );
  217. }
  218. }
  219. return $newStr;
  220. }
  221.  
  222. /**
  223. * Fills in a value of a binding and truncates the
  224. * resulting string if necessary.
  225. *
  226. * @param mixed $value bound value
  227. *
  228. * @return string
  229. */
  230. protected function fillInValue( $value )
  231. {
  232. if ( is_null( $value ) ) $value = 'NULL';
  233.  
  234. $value = strval( $value );
  235. if ( strlen( $value ) > ( $this->strLen ) ) {
  236. $value = substr( $value, 0, ( $this->strLen ) ).'... ';
  237. }
  238.  
  239. if ( !is_numeric( $value ) && $value !== 'NULL') {
  240. $value = '\''.$value.'\'';
  241. }
  242.  
  243. return $value;
  244. }
  245.  
  246. /**
  247. * Dependending on the current mode of operation,
  248. * this method will either log and output to STDIN or
  249. * just log.
  250. *
  251. * Depending on the value of constant PHP_SAPI this function
  252. * will format output for console or HTML.
  253. *
  254. * @param string $str string to log or output and log
  255. *
  256. * @return void
  257. */
  258. protected function output( $str )
  259. {
  260. $this->logs[] = $str;
  261. if ( !$this->mode ) {
  262. $highlight = FALSE;
  263. /* just a quick heuritsic to highlight schema changes */
  264. if ( strpos( $str, 'CREATE' ) === 0
  265. || strpos( $str, 'ALTER' ) === 0
  266. || strpos( $str, 'DROP' ) === 0) {
  267. $highlight = TRUE;
  268. }
  269. if (PHP_SAPI === 'cli') {
  270. if ($highlight) echo "\e[91m";
  271. echo $str, PHP_EOL;
  272. echo "\e[39m";
  273. } else {
  274. if ($highlight) {
  275. echo "<b style=\"color:red\">{$str}</b>";
  276. } else {
  277. echo $str;
  278. }
  279. echo '<br />';
  280. }
  281. }
  282. }
  283.  
  284. /**
  285. * Normalizes the slots in an SQL string.
  286. * Replaces question mark slots with :slot1 :slot2 etc.
  287. *
  288. * @param string $sql sql to normalize
  289. *
  290. * @return string
  291. */
  292. protected function normalizeSlots( $sql )
  293. {
  294. $i = 0;
  295. $newSql = $sql;
  296. while($i < 20 && strpos($newSql, '?') !== FALSE ){
  297. $pos = strpos( $newSql, '?' );
  298. $slot = ':slot'.$i;
  299. $begin = substr( $newSql, 0, $pos );
  300. $end = substr( $newSql, $pos+1 );
  301. if (PHP_SAPI === 'cli') {
  302. $newSql = "{$begin}\e[32m{$slot}\e[39m{$end}";
  303. } else {
  304. $newSql = "{$begin}<b style=\"color:green\">$slot</b>{$end}";
  305. }
  306. $i++;
  307. }
  308. return $newSql;
  309. }
  310.  
  311. /**
  312. * Normalizes the bindings.
  313. * Replaces numeric binding keys with :slot1 :slot2 etc.
  314. *
  315. * @param array $bindings bindings to normalize
  316. *
  317. * @return array
  318. */
  319. protected function normalizeBindings( $bindings )
  320. {
  321. $i = 0;
  322. $newBindings = array();
  323. foreach( $bindings as $key => $value ) {
  324. if ( is_numeric($key) ) {
  325. $newKey = ':slot'.$i;
  326. $newBindings[$newKey] = $value;
  327. $i++;
  328. } else {
  329. $newBindings[$key] = $value;
  330. }
  331. }
  332. return $newBindings;
  333. }
  334.  
  335. /**
  336. * Logger method.
  337. *
  338. * Takes a number of arguments tries to create
  339. * a proper debug log based on the available data.
  340. *
  341. * @return void
  342. */
  343. public function log()
  344. {
  345. if ( func_num_args() < 1 ) return;
  346.  
  347. $sql = func_get_arg( 0 );
  348.  
  349. if ( func_num_args() < 2) {
  350. $bindings = array();
  351. } else {
  352. $bindings = func_get_arg( 1 );
  353. }
  354.  
  355. if ( !is_array( $bindings ) ) {
  356. return $this->output( $sql );
  357. }
  358.  
  359. $newSql = $this->normalizeSlots( $sql );
  360. $newBindings = $this->normalizeBindings( $bindings );
  361. $newStr = $this->writeQuery( $newSql, $newBindings );
  362. $this->output( $newStr );
  363. }
  364.  
  365. /**
  366. * Sets the max string length for the parameter output in
  367. * SQL queries. Set this value to a reasonable number to
  368. * keep you SQL queries readable.
  369. *
  370. * @param integer $len string length
  371. *
  372. * @return self
  373. */
  374. public function setParamStringLength( $len = 20 )
  375. {
  376. $this->strLen = max(0, $len);
  377. return $this;
  378. }
  379. }
  380. }
  381.  
  382. namespace RedBeanPHP {
  383.  
  384. /**
  385. * Interface for database drivers.
  386. * The Driver API conforms to the ADODB pseudo standard
  387. * for database drivers.
  388. *
  389. * @file RedBeanPHP/Driver.php
  390. * @author Gabor de Mooij and the RedBeanPHP Community
  391. * @license BSD/GPLv2
  392. *
  393. * @copyright
  394. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  395. * This source file is subject to the BSD/GPLv2 License that is bundled
  396. * with this source code in the file license.txt.
  397. */
  398. interface Driver
  399. {
  400. /**
  401. * Runs a query and fetches results as a multi dimensional array.
  402. *
  403. * @param string $sql SQL query to execute
  404. * @param array $bindings list of values to bind to SQL snippet
  405. *
  406. * @return array
  407. */
  408. public function GetAll( $sql, $bindings = array() );
  409.  
  410. /**
  411. * Runs a query and fetches results as a column.
  412. *
  413. * @param string $sql SQL query to execute
  414. * @param array $bindings list of values to bind to SQL snippet
  415. *
  416. * @return array
  417. */
  418. public function GetCol( $sql, $bindings = array() );
  419.  
  420. /**
  421. * Runs a query and returns results as a single cell.
  422. *
  423. * @param string $sql SQL query to execute
  424. * @param array $bindings list of values to bind to SQL snippet
  425. *
  426. * @return mixed
  427. */
  428. public function GetOne( $sql, $bindings = array() );
  429.  
  430. /**
  431. * Runs a query and returns results as an associative array
  432. * indexed by the first column.
  433. *
  434. * @param string $sql SQL query to execute
  435. * @param array $bindings list of values to bind to SQL snippet
  436. *
  437. * @return mixed
  438. */
  439. public function GetAssocRow( $sql, $bindings = array() );
  440.  
  441. /**
  442. * Runs a query and returns a flat array containing the values of
  443. * one row.
  444. *
  445. * @param string $sql SQL query to execute
  446. * @param array $bindings list of values to bind to SQL snippet
  447. *
  448. * @return array
  449. */
  450. public function GetRow( $sql, $bindings = array() );
  451.  
  452. /**
  453. * Executes SQL code and allows key-value binding.
  454. * This function allows you to provide an array with values to bind
  455. * to query parameters. For instance you can bind values to question
  456. * marks in the query. Each value in the array corresponds to the
  457. * question mark in the query that matches the position of the value in the
  458. * array. You can also bind values using explicit keys, for instance
  459. * array(":key"=>123) will bind the integer 123 to the key :key in the
  460. * SQL. This method has no return value.
  461. *
  462. * @param string $sql SQL query to execute
  463. * @param array $bindings list of values to bind to SQL snippet
  464. *
  465. * @return array Affected Rows
  466. */
  467. public function Execute( $sql, $bindings = array() );
  468.  
  469. /**
  470. * Returns the latest insert ID if driver does support this
  471. * feature.
  472. *
  473. * @return integer
  474. */
  475. public function GetInsertID();
  476.  
  477. /**
  478. * Returns the number of rows affected by the most recent query
  479. * if the currently selected driver driver supports this feature.
  480. *
  481. * @return integer
  482. */
  483. public function Affected_Rows();
  484.  
  485. /**
  486. * Returns a cursor-like object from the database.
  487. *
  488. * @param string $sql SQL query to execute
  489. * @param array $bindings list of values to bind to SQL snippet
  490. *
  491. * @return mixed
  492. */
  493. public function GetCursor( $sql, $bindings = array() );
  494.  
  495. /**
  496. * Toggles debug mode. In debug mode the driver will print all
  497. * SQL to the screen together with some information about the
  498. * results. All SQL code that passes through the driver will be
  499. * passes on to the screen for inspection.
  500. * This method has no return value.
  501. *
  502. * @param boolean $tf TRUE = debug mode ON
  503. * @param Logger $customLogger
  504. *
  505. * @return void
  506. */
  507. public function setDebugMode( $tf, $customLogger );
  508.  
  509. /**
  510. * Starts a transaction.
  511. *
  512. * @return void
  513. */
  514. public function CommitTrans();
  515.  
  516. /**
  517. * Commits a transaction.
  518. *
  519. * @return void
  520. */
  521. public function StartTrans();
  522.  
  523. /**
  524. * Rolls back a transaction.
  525. *
  526. * @return void
  527. */
  528. public function FailTrans();
  529.  
  530. /**
  531. * Resets the internal Query Counter.
  532. *
  533. * @return self
  534. */
  535. public function resetCounter();
  536.  
  537. /**
  538. * Returns the number of SQL queries processed.
  539. *
  540. * @return integer
  541. */
  542. public function getQueryCount();
  543. }
  544. }
  545.  
  546. namespace RedBeanPHP\Driver {
  547.  
  548. use RedBeanPHP\Driver as Driver;
  549. use RedBeanPHP\Logger as Logger;
  550. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  551. use RedBeanPHP\RedException as RedException;
  552. use RedBeanPHP\RedException\SQL as SQL;
  553. use RedBeanPHP\Logger\RDefault as RDefault;
  554. use RedBeanPHP\PDOCompatible as PDOCompatible;
  555. use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
  556.  
  557. /**
  558. * PDO Driver
  559. * This Driver implements the RedBean Driver API.
  560. * for RedBeanPHP. This is the standard / default database driver
  561. * for RedBeanPHP.
  562. *
  563. * @file RedBeanPHP/PDO.php
  564. * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes
  565. * @license BSD/GPLv2
  566. *
  567. * @copyright
  568. * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
  569. * This source file is subject to the BSD/GPLv2 License that is bundled
  570. * with this source code in the file license.txt.
  571. */
  572. class RPDO implements Driver
  573. {
  574. /**
  575. * @var integer
  576. */
  577. protected $max;
  578.  
  579. /**
  580. * @var string
  581. */
  582. protected $dsn;
  583.  
  584. /**
  585. * @var boolean
  586. */
  587. protected $loggingEnabled = FALSE;
  588.  
  589. /**
  590. * @var Logger
  591. */
  592. protected $logger = NULL;
  593.  
  594. /**
  595. * @var PDO
  596. */
  597. protected $pdo;
  598.  
  599. /**
  600. * @var integer
  601. */
  602. protected $affectedRows;
  603.  
  604. /**
  605. * @var integer
  606. */
  607. protected $resultArray;
  608.  
  609. /**
  610. * @var array
  611. */
  612. protected $connectInfo = array();
  613.  
  614. /**
  615. * @var boolean
  616. */
  617. protected $isConnected = FALSE;
  618.  
  619. /**
  620. * @var bool
  621. */
  622. protected $flagUseStringOnlyBinding = FALSE;
  623.  
  624. /**
  625. * @var integer
  626. */
  627. protected $queryCounter = 0;
  628.  
  629. /**
  630. * @var string
  631. */
  632. protected $mysqlEncoding = '';
  633.  
  634. /**
  635. * Binds parameters. This method binds parameters to a PDOStatement for
  636. * Query Execution. This method binds parameters as NULL, INTEGER or STRING
  637. * and supports both named keys and question mark keys.
  638. *
  639. * @param PDOStatement $statement PDO Statement instance
  640. * @param array $bindings values that need to get bound to the statement
  641. *
  642. * @return void
  643. */
  644. protected function bindParams( $statement, $bindings )
  645. {
  646. foreach ( $bindings as $key => &$value ) {
  647. if ( is_integer( $key ) ) {
  648. if ( is_null( $value ) ) {
  649. $statement->bindValue( $key + 1, NULL, \PDO::PARAM_NULL );
  650. } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
  651. $statement->bindParam( $key + 1, $value, \PDO::PARAM_INT );
  652. } else {
  653. $statement->bindParam( $key + 1, $value, \PDO::PARAM_STR );
  654. }
  655. } else {
  656. if ( is_null( $value ) ) {
  657. $statement->bindValue( $key, NULL, \PDO::PARAM_NULL );
  658. } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
  659. $statement->bindParam( $key, $value, \PDO::PARAM_INT );
  660. } else {
  661. $statement->bindParam( $key, $value, \PDO::PARAM_STR );
  662. }
  663. }
  664. }
  665. }
  666.  
  667. /**
  668. * This method runs the actual SQL query and binds a list of parameters to the query.
  669. * slots. The result of the query will be stored in the protected property
  670. * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
  671. * is stored in protected property $affectedRows. If the debug flag is set
  672. * this function will send debugging output to screen buffer.
  673. *
  674. * @param string $sql the SQL string to be send to database server
  675. * @param array $bindings the values that need to get bound to the query slots
  676. * @param array $options
  677. *
  678. * @return mixed
  679. * @throws SQL
  680. */
  681. protected function runQuery( $sql, $bindings, $options = array() )
  682. {
  683. $this->connect();
  684. if ( $this->loggingEnabled && $this->logger ) {
  685. $this->logger->log( $sql, $bindings );
  686. }
  687. try {
  688. if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
  689. if ( defined( '\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' ) ) {
  690. $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) );
  691. } else {
  692. $statement = $this->pdo->prepare( $sql );
  693. }
  694. } else {
  695. $statement = $this->pdo->prepare( $sql );
  696. }
  697. $this->bindParams( $statement, $bindings );
  698. $statement->execute();
  699. $this->queryCounter ++;
  700. $this->affectedRows = $statement->rowCount();
  701. if ( $statement->columnCount() ) {
  702. $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
  703. if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
  704. $this->resultArray = array();
  705. return $statement;
  706. }
  707. $this->resultArray = $statement->fetchAll( $fetchStyle );
  708. if ( $this->loggingEnabled && $this->logger ) {
  709. $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
  710. }
  711. } else {
  712. $this->resultArray = array();
  713. }
  714. } catch ( \PDOException $e ) {
  715. //Unfortunately the code field is supposed to be int by default (php)
  716. //So we need a property to convey the SQL State code.
  717. $err = $e->getMessage();
  718. if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
  719. $exception = new SQL( $err, 0 );
  720. $exception->setSQLState( $e->getCode() );
  721. throw $exception;
  722. }
  723. }
  724.  
  725. /**
  726. * Try to fix MySQL character encoding problems.
  727. * MySQL < 5.5 does not support proper 4 byte unicode but they
  728. * seem to have added it with version 5.5 under a different label: utf8mb4.
  729. * We try to select the best possible charset based on your version data.
  730. *
  731. * @return void
  732. */
  733. protected function setEncoding()
  734. {
  735. $driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
  736. $version = floatval( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
  737. if ($driver === 'mysql') {
  738. $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8';
  739. $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect
  740. $this->pdo->exec(' SET NAMES '. $encoding); //also for current connection
  741. $this->mysqlEncoding = $encoding;
  742. }
  743. }
  744.  
  745. /**
  746. * Constructor. You may either specify dsn, user and password or
  747. * just give an existing PDO connection.
  748. *
  749. * Examples:
  750. * $driver = new RPDO($dsn, $user, $password);
  751. * $driver = new RPDO($existingConnection);
  752. *
  753. * @param string|object $dsn database connection string
  754. * @param string $user optional, usename to sign in
  755. * @param string $pass optional, password for connection login
  756. *
  757. * @return void
  758. */
  759. public function __construct( $dsn, $user = NULL, $pass = NULL )
  760. {
  761. if ( is_object( $dsn ) ) {
  762. $this->pdo = $dsn;
  763. $this->isConnected = TRUE;
  764. $this->setEncoding();
  765. $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
  766. $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
  767. // make sure that the dsn at least contains the type
  768. $this->dsn = $this->getDatabaseType();
  769. } else {
  770. $this->dsn = $dsn;
  771. $this->connectInfo = array( 'pass' => $pass, 'user' => $user );
  772. }
  773.  
  774. //PHP 5.3 PDO SQLite has a bug with large numbers:
  775. if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) || defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
  776. $this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
  777. } elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
  778. $this->max = 2147483647; //bindParam in pdo_cubrid also fails...
  779. } else {
  780. $this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
  781. }
  782. }
  783.  
  784. /**
  785. * Returns the best possible encoding for MySQL based on version data.
  786. *
  787. * @return string
  788. */
  789. public function getMysqlEncoding()
  790. {
  791. return $this->mysqlEncoding;
  792. }
  793.  
  794. /**
  795. * Whether to bind all parameters as strings.
  796. * If set to TRUE this will cause all integers to be bound as STRINGS.
  797. * This will NOT affect NULL values.
  798. *
  799. * @param boolean $yesNo pass TRUE to bind all parameters as strings.
  800. *
  801. * @return void
  802. */
  803. public function setUseStringOnlyBinding( $yesNo )
  804. {
  805. $this->flagUseStringOnlyBinding = (boolean) $yesNo;
  806. }
  807.  
  808. /**
  809. * Sets the maximum value to be bound as integer, normally
  810. * this value equals PHP's MAX INT constant, however sometimes
  811. * PDO driver bindings cannot bind large integers as integers.
  812. * This method allows you to manually set the max integer binding
  813. * value to manage portability/compatibility issues among different
  814. * PHP builds. This method will return the old value.
  815. *
  816. * @param integer $max maximum value for integer bindings
  817. *
  818. * @return integer
  819. */
  820. public function setMaxIntBind( $max )
  821. {
  822. if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
  823. $oldMax = $this->max;
  824. $this->max = $max;
  825. return $oldMax;
  826. }
  827.  
  828. /**
  829. * Establishes a connection to the database using PHP\PDO
  830. * functionality. If a connection has already been established this
  831. * method will simply return directly. This method also turns on
  832. * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
  833. * PDO-FETCH-ASSOC.
  834. *
  835. * @return void
  836. */
  837. public function connect()
  838. {
  839. if ( $this->isConnected ) return;
  840. try {
  841. $user = $this->connectInfo['user'];
  842. $pass = $this->connectInfo['pass'];
  843. $this->pdo = new \PDO(
  844. $this->dsn,
  845. $user,
  846. $pass
  847. );
  848. $this->setEncoding();
  849. $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, TRUE );
  850. //cant pass these as argument to constructor, CUBRID driver does not understand...
  851. $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
  852. $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
  853. $this->isConnected = TRUE;
  854. } catch ( \PDOException $exception ) {
  855. $matches = array();
  856. $dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
  857. throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
  858. }
  859. }
  860.  
  861. /**
  862. * Directly sets PDO instance into driver.
  863. * This method might improve performance, however since the driver does
  864. * not configure this instance terrible things may happen... only use
  865. * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
  866. * you know your database server VERY WELL.
  867. *
  868. * @param PDO $pdo PDO instance
  869. *
  870. * @return void
  871. */
  872. public function setPDO( \PDO $pdo ) {
  873. $this->pdo = $pdo;
  874. }
  875.  
  876. /**
  877. * @see Driver::GetAll
  878. */
  879. public function GetAll( $sql, $bindings = array() )
  880. {
  881. $this->runQuery( $sql, $bindings );
  882. return $this->resultArray;
  883. }
  884.  
  885. /**
  886. * @see Driver::GetAssocRow
  887. */
  888. public function GetAssocRow( $sql, $bindings = array() )
  889. {
  890. $this->runQuery( $sql, $bindings, array(
  891. 'fetchStyle' => \PDO::FETCH_ASSOC
  892. )
  893. );
  894. return $this->resultArray;
  895. }
  896.  
  897. /**
  898. * @see Driver::GetCol
  899. */
  900. public function GetCol( $sql, $bindings = array() )
  901. {
  902. $rows = $this->GetAll( $sql, $bindings );
  903. $cols = array();
  904. if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) {
  905. foreach ( $rows as $row ) {
  906. $cols[] = array_shift( $row );
  907. }
  908. }
  909.  
  910. return $cols;
  911. }
  912.  
  913. /**
  914. * @see Driver::GetOne
  915. */
  916. public function GetOne( $sql, $bindings = array() )
  917. {
  918. $arr = $this->GetAll( $sql, $bindings );
  919. $res = NULL;
  920. if ( !is_array( $arr ) ) return NULL;
  921. if ( count( $arr ) === 0 ) return NULL;
  922. $row1 = array_shift( $arr );
  923. if ( !is_array( $row1 ) ) return NULL;
  924. if ( count( $row1 ) === 0 ) return NULL;
  925. $col1 = array_shift( $row1 );
  926. return $col1;
  927. }
  928.  
  929. /**
  930. * Alias for getOne().
  931. * Backward compatibility.
  932. *
  933. * @param string $sql SQL
  934. * @param array $bindings bindings
  935. *
  936. * @return mixed
  937. */
  938. public function GetCell( $sql, $bindings = array() )
  939. {
  940. return $this->GetOne( $sql, $bindings );
  941. }
  942.  
  943. /**
  944. * @see Driver::GetRow
  945. */
  946. public function GetRow( $sql, $bindings = array() )
  947. {
  948. $arr = $this->GetAll( $sql, $bindings );
  949. return array_shift( $arr );
  950. }
  951.  
  952. /**
  953. * @see Driver::Excecute
  954. */
  955. public function Execute( $sql, $bindings = array() )
  956. {
  957. $this->runQuery( $sql, $bindings );
  958. return $this->affectedRows;
  959. }
  960.  
  961. /**
  962. * @see Driver::GetInsertID
  963. */
  964. public function GetInsertID()
  965. {
  966. $this->connect();
  967.  
  968. return (int) $this->pdo->lastInsertId();
  969. }
  970.  
  971. /**
  972. * @see Driver::GetCursor
  973. */
  974. public function GetCursor( $sql, $bindings = array() )
  975. {
  976. $statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
  977. $cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
  978. return $cursor;
  979. }
  980.  
  981. /**
  982. * @see Driver::Affected_Rows
  983. */
  984. public function Affected_Rows()
  985. {
  986. $this->connect();
  987. return (int) $this->affectedRows;
  988. }
  989.  
  990. /**
  991. * Toggles debug mode. In debug mode the driver will print all
  992. * SQL to the screen together with some information about the
  993. * results.
  994. *
  995. * @param boolean $trueFalse turn on/off
  996. * @param Logger $logger logger instance
  997. *
  998. * @return void
  999. */
  1000. public function setDebugMode( $tf, $logger = NULL )
  1001. {
  1002. $this->connect();
  1003. $this->loggingEnabled = (bool) $tf;
  1004. if ( $this->loggingEnabled and !$logger ) {
  1005. $logger = new RDefault();
  1006. }
  1007. $this->setLogger( $logger );
  1008. }
  1009.  
  1010. /**
  1011. * Injects Logger object.
  1012. * Sets the logger instance you wish to use.
  1013. *
  1014. * @param Logger $logger the logger instance to be used for logging
  1015. *
  1016. * @return void
  1017. */
  1018. public function setLogger( Logger $logger )
  1019. {
  1020. $this->logger = $logger;
  1021. }
  1022.  
  1023. /**
  1024. * Gets Logger object.
  1025. * Returns the currently active Logger instance.
  1026. *
  1027. * @return Logger
  1028. */
  1029. public function getLogger()
  1030. {
  1031. return $this->logger;
  1032. }
  1033.  
  1034. /**
  1035. * @see Driver::StartTrans
  1036. */
  1037. public function StartTrans()
  1038. {
  1039. $this->connect();
  1040. $this->pdo->beginTransaction();
  1041. }
  1042.  
  1043. /**
  1044. * @see Driver::CommitTrans
  1045. */
  1046. public function CommitTrans()
  1047. {
  1048. $this->connect();
  1049. $this->pdo->commit();
  1050. }
  1051.  
  1052. /**
  1053. * @see Driver::FailTrans
  1054. */
  1055. public function FailTrans()
  1056. {
  1057. $this->connect();
  1058. $this->pdo->rollback();
  1059. }
  1060.  
  1061. /**
  1062. * Returns the name of database driver for PDO.
  1063. * Uses the PDO attribute DRIVER NAME to obtain the name of the
  1064. * PDO driver.
  1065. *
  1066. * @return string
  1067. */
  1068. public function getDatabaseType()
  1069. {
  1070. $this->connect();
  1071.  
  1072. return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
  1073. }
  1074.  
  1075. /**
  1076. * Returns the version number of the database.
  1077. *
  1078. * @return mixed
  1079. */
  1080. public function getDatabaseVersion()
  1081. {
  1082. $this->connect();
  1083. return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
  1084. }
  1085.  
  1086. /**
  1087. * Returns the underlying PHP PDO instance.
  1088. *
  1089. * @return PDO
  1090. */
  1091. public function getPDO()
  1092. {
  1093. $this->connect();
  1094. return $this->pdo;
  1095. }
  1096.  
  1097. /**
  1098. * Closes database connection by destructing PDO.
  1099. *
  1100. * @return void
  1101. */
  1102. public function close()
  1103. {
  1104. $this->pdo = NULL;
  1105. $this->isConnected = FALSE;
  1106. }
  1107.  
  1108. /**
  1109. * Returns TRUE if the current PDO instance is connected.
  1110. *
  1111. * @return boolean
  1112. */
  1113. public function isConnected()
  1114. {
  1115. return $this->isConnected && $this->pdo;
  1116. }
  1117.  
  1118. /**
  1119. * Toggles logging, enables or disables logging.
  1120. *
  1121. * @param boolean $enable TRUE to enable logging
  1122. *
  1123. * @return self
  1124. */
  1125. public function setEnableLogging( $enable )
  1126. {
  1127. $this->loggingEnabled = (boolean) $enable;
  1128. }
  1129.  
  1130. /**
  1131. * Resets the internal Query Counter.
  1132. *
  1133. * @return self
  1134. */
  1135. public function resetCounter()
  1136. {
  1137. $this->queryCounter = 0;
  1138. return $this;
  1139. }
  1140.  
  1141. /**
  1142. * Returns the number of SQL queries processed.
  1143. *
  1144. * @return integer
  1145. */
  1146. public function getQueryCount()
  1147. {
  1148. return $this->queryCounter;
  1149. }
  1150.  
  1151. /**
  1152. * Returns the maximum value treated as integer parameter
  1153. * binding.
  1154. *
  1155. * This method is mainly for testing purposes but it can help
  1156. * you solve some issues relating to integer bindings.
  1157. *
  1158. * @return integer
  1159. */
  1160. public function getIntegerBindingMax()
  1161. {
  1162. return $this->max;
  1163. }
  1164. }
  1165. }
  1166.  
  1167. namespace RedBeanPHP {
  1168.  
  1169. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  1170. use RedBeanPHP\BeanHelper as BeanHelper;
  1171. use RedBeanPHP\RedException as RedException;
  1172.  
  1173. /* PHP 5.3 compatibility */
  1174. if (interface_exists('\JsonSerializable')) {
  1175. /* We extend JsonSerializable to avoid namespace conflicts,
  1176. can't define interface with special namespace in PHP */
  1177. interface Jsonable extends \JsonSerializable {};
  1178. } else {
  1179. interface Jsonable {};
  1180. }
  1181.  
  1182. /**
  1183. * OODBBean (Object Oriented DataBase Bean).
  1184. *
  1185. * to exchange information with the database. A bean represents
  1186. * a single table row and offers generic services for interaction
  1187. * with databases systems as well as some meta-data.
  1188. *
  1189. * @file RedBeanPHP/OODBBean.php
  1190. * @author Gabor de Mooij and the RedBeanPHP community
  1191. * @license BSD/GPLv2
  1192. * @desc OODBBean represents a bean. RedBeanPHP uses beans
  1193. *
  1194. * @copyright
  1195. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  1196. * This source file is subject to the BSD/GPLv2 License that is bundled
  1197. * with this source code in the file license.txt.
  1198. */
  1199. class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable,Jsonable
  1200. {
  1201. /**
  1202. * FUSE error modes.
  1203. */
  1204. const C_ERR_IGNORE = FALSE;
  1205. const C_ERR_LOG = 1;
  1206. const C_ERR_NOTICE = 2;
  1207. const C_ERR_WARN = 3;
  1208. const C_ERR_EXCEPTION = 4;
  1209. const C_ERR_FUNC = 5;
  1210. const C_ERR_FATAL = 6;
  1211.  
  1212. /**
  1213. * @var boolean
  1214. */
  1215. protected static $errorHandlingFUSE = FALSE;
  1216.  
  1217. /**
  1218. * @var callable|NULL
  1219. */
  1220. protected static $errorHandler = NULL;
  1221.  
  1222. /**
  1223. * @var array
  1224. */
  1225. protected static $aliases = array();
  1226.  
  1227. /**
  1228. * @var boolean
  1229. */
  1230. protected static $autoResolve = FALSE;
  1231.  
  1232. /**
  1233. * This is where the real properties of the bean live. They are stored and retrieved
  1234. * by the magic getter and setter (__get and __set).
  1235. *
  1236. * @var array $properties
  1237. */
  1238. protected $properties = array();
  1239.  
  1240. /**
  1241. * Here we keep the meta data of a bean.
  1242. *
  1243. * @var array
  1244. */
  1245. protected $__info = array();
  1246.  
  1247. /**
  1248. * The BeanHelper allows the bean to access the toolbox objects to implement
  1249. * rich functionality, otherwise you would have to do everything with R or
  1250. * external objects.
  1251. *
  1252. * @var BeanHelper
  1253. */
  1254. protected $beanHelper = NULL;
  1255.  
  1256. /**
  1257. * @var null
  1258. */
  1259. protected $fetchType = NULL;
  1260.  
  1261. /**
  1262. * @var string
  1263. */
  1264. protected $withSql = '';
  1265.  
  1266. /**
  1267. * @var array
  1268. */
  1269. protected $withParams = array();
  1270.  
  1271. /**
  1272. * @var string
  1273. */
  1274. protected $aliasName = NULL;
  1275.  
  1276. /**
  1277. * @var string
  1278. */
  1279. protected $via = NULL;
  1280.  
  1281. /**
  1282. * @var boolean
  1283. */
  1284. protected $noLoad = FALSE;
  1285.  
  1286. /**
  1287. * @var boolean
  1288. */
  1289. protected $all = FALSE;
  1290.  
  1291. /**
  1292. * Sets the error mode for FUSE.
  1293. * What to do if a FUSE model method does not exist?
  1294. * You can set the following options:
  1295. *
  1296. * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
  1297. * * OODBBean::C_ERR_LOG, logs the incident using error_log
  1298. * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
  1299. * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
  1300. * * OODBBean::C_ERR_EXCEPTION, throws an exception
  1301. * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
  1302. * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
  1303. *
  1304. * <code>
  1305. * Custom handler method signature: handler( array (
  1306. * 'message' => string
  1307. * 'bean' => OODBBean
  1308. * 'method' => string
  1309. * ) )
  1310. * </code>
  1311. *
  1312. * This method returns the old mode and handler as an array.
  1313. *
  1314. * @param integer $mode error handling mode
  1315. * @param callable|NULL $func custom handler
  1316. *
  1317. * @return array
  1318. */
  1319. public static function setErrorHandlingFUSE($mode, $func = NULL) {
  1320. if (
  1321. $mode !== self::C_ERR_IGNORE
  1322. && $mode !== self::C_ERR_LOG
  1323. && $mode !== self::C_ERR_NOTICE
  1324. && $mode !== self::C_ERR_WARN
  1325. && $mode !== self::C_ERR_EXCEPTION
  1326. && $mode !== self::C_ERR_FUNC
  1327. && $mode !== self::C_ERR_FATAL
  1328. ) throw new \Exception( 'Invalid error mode selected' );
  1329.  
  1330. if ( $mode === self::C_ERR_FUNC && !is_callable( $func ) ) {
  1331. throw new \Exception( 'Invalid error handler' );
  1332. }
  1333.  
  1334. $old = array( self::$errorHandlingFUSE, self::$errorHandler );
  1335. self::$errorHandlingFUSE = $mode;
  1336. if ( is_callable( $func ) ) {
  1337. self::$errorHandler = $func;
  1338. } else {
  1339. self::$errorHandler = NULL;
  1340. }
  1341. return $old;
  1342. }
  1343.  
  1344. /**
  1345. * Sets global aliases.
  1346. * Registers a batch of aliases in one go. This works the same as
  1347. * fetchAs and setAutoResolve but explicitly. For instance if you register
  1348. * the alias 'cover' for 'page' a property containing a reference to a
  1349. * page bean called 'cover' will correctly return the page bean and not
  1350. * a (non-existant) cover bean.
  1351. *
  1352. * <code>
  1353. * R::aliases( array( 'cover' => 'page' ) );
  1354. * $book = R::dispense( 'book' );
  1355. * $page = R::dispense( 'page' );
  1356. * $book->cover = $page;
  1357. * R::store( $book );
  1358. * $book = $book->fresh();
  1359. * $cover = $book->cover;
  1360. * echo $cover->getMeta( 'type' ); //page
  1361. * </code>
  1362. *
  1363. * The format of the aliases registration array is:
  1364. *
  1365. * {alias} => {actual type}
  1366. *
  1367. * In the example above we use:
  1368. *
  1369. * cover => page
  1370. *
  1371. * From that point on, every bean reference to a cover
  1372. * will return a 'page' bean. Note that with autoResolve this
  1373. * feature along with fetchAs() is no longer very important, although
  1374. * relying on explicit aliases can be a bit faster.
  1375. *
  1376. * @param array $list list of global aliases to use
  1377. *
  1378. * @return void
  1379. */
  1380. public static function aliases( $list )
  1381. {
  1382. self::$aliases = $list;
  1383. }
  1384.  
  1385. /**
  1386. * Enables or disables auto-resolving fetch types.
  1387. * Auto-resolving aliased parent beans is convenient but can
  1388. * be slower and can create infinite recursion if you
  1389. * used aliases to break cyclic relations in your domain.
  1390. *
  1391. * @param boolean $automatic TRUE to enable automatic resolving aliased parents
  1392. *
  1393. * @return void
  1394. */
  1395. public static function setAutoResolve( $automatic = TRUE )
  1396. {
  1397. self::$autoResolve = (boolean) $automatic;
  1398. }
  1399.  
  1400. /**
  1401. * Sets a meta property for all beans. This is a quicker way to set
  1402. * the meta properties for a collection of beans because this method
  1403. * can directly access the property arrays of the beans.
  1404. * This method returns the beans.
  1405. *
  1406. * @param array $beans beans to set the meta property of
  1407. * @param string $property property to set
  1408. * @param mixed $value value
  1409. *
  1410. * @return array
  1411. */
  1412. public static function setMetaAll( $beans, $property, $value )
  1413. {
  1414. foreach( $beans as $bean ) {
  1415. if ( $bean instanceof OODBBean ) $bean->__info[ $property ] = $value;
  1416. }
  1417.  
  1418. return $beans;
  1419. }
  1420.  
  1421. /**
  1422. * Parses the join in the with-snippet.
  1423. * For instance:
  1424. *
  1425. * <code>
  1426. * $author
  1427. * ->withCondition(' @joined.detail.title LIKE ? ')
  1428. * ->ownBookList;
  1429. * </code>
  1430. *
  1431. * will automatically join 'detail' on book to
  1432. * access the title field.
  1433. *
  1434. * @note this feature requires Narrow Field Mode and Join Feature
  1435. * to be both activated (default).
  1436. *
  1437. * @param string $type the source type for the join
  1438. *
  1439. * @return string
  1440. */
  1441. private function parseJoin( $type )
  1442. {
  1443. $joinSql = '';
  1444. $joins = array();
  1445. if ( strpos($this->withSql, '@joined.' ) !== FALSE ) {
  1446. $writer = $this->beanHelper->getToolBox()->getWriter();
  1447. $oldParts = $parts = explode( '@joined.', $this->withSql );
  1448. array_shift( $parts );
  1449. foreach($parts as $part) {
  1450. $explosion = explode( '.', $part );
  1451. $joinInfo = array_shift( $explosion );
  1452. //Dont join more than once..
  1453. if ( !isset( $joins[$joinInfo] ) ) {
  1454. $joins[ $joinInfo ] = true;
  1455. $joinSql .= $writer->writeJoin( $type, $joinInfo, 'LEFT' );
  1456. }
  1457. }
  1458. $this->withSql = implode( '', $oldParts );
  1459. $joinSql .= ' WHERE ';
  1460. }
  1461. return $joinSql;
  1462. }
  1463.  
  1464. /**
  1465. * Internal method.
  1466. * Obtains a shared list for a certain type.
  1467. *
  1468. * @param string $type the name of the list you want to retrieve.
  1469. *
  1470. * @return array
  1471. */
  1472. private function getSharedList( $type, $redbean, $toolbox )
  1473. {
  1474. $writer = $toolbox->getWriter();
  1475.  
  1476. if ( $this->via ) {
  1477. $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
  1478. if ( $oldName !== $this->via ) {
  1479. //set the new renaming rule
  1480. $writer->renameAssocTable( $oldName, $this->via );
  1481. }
  1482. $this->via = NULL;
  1483. }
  1484.  
  1485. $beans = array();
  1486. if ($this->getID()) {
  1487. $type = $this->beau( $type );
  1488. $assocManager = $redbean->getAssociationManager();
  1489. $beans = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
  1490. }
  1491.  
  1492. $this->withSql = '';
  1493. $this->withParams = array();
  1494.  
  1495. return $beans;
  1496. }
  1497.  
  1498. /**
  1499. * Internal method.
  1500. * Obtains the own list of a certain type.
  1501. *
  1502. * @param string $type name of the list you want to retrieve
  1503. * @param OODB $oodb The RB OODB object database instance
  1504. *
  1505. * @return array
  1506. */
  1507. private function getOwnList( $type, $redbean )
  1508. {
  1509. $type = $this->beau( $type );
  1510.  
  1511. if ( $this->aliasName ) {
  1512. $parentField = $this->aliasName;
  1513. $myFieldLink = $parentField . '_id';
  1514.  
  1515. $this->__info['sys.alias.' . $type] = $this->aliasName;
  1516.  
  1517. $this->aliasName = NULL;
  1518. } else {
  1519. $parentField = $this->__info['type'];
  1520. $myFieldLink = $parentField . '_id';
  1521. }
  1522.  
  1523. $beans = array();
  1524.  
  1525. if ( $this->getID() ) {
  1526.  
  1527. $firstKey = NULL;
  1528. if ( count( $this->withParams ) > 0 ) {
  1529. reset( $this->withParams );
  1530.  
  1531. $firstKey = key( $this->withParams );
  1532. }
  1533.  
  1534. $joinSql = $this->parseJoin( $type );
  1535.  
  1536. if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
  1537. $bindings = $this->withParams;
  1538. $bindings[':slot0'] = $this->getID();
  1539.  
  1540. $beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
  1541. } else {
  1542. $bindings = array_merge( array( $this->getID() ), $this->withParams );
  1543.  
  1544. $beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
  1545. }
  1546. }
  1547.  
  1548. $this->withSql = '';
  1549. $this->withParams = array();
  1550.  
  1551. foreach ( $beans as $beanFromList ) {
  1552. $beanFromList->__info['sys.parentcache.' . $parentField] = $this;
  1553. }
  1554.  
  1555. return $beans;
  1556. }
  1557.  
  1558. /**
  1559. * Initializes a bean. Used by OODB for dispensing beans.
  1560. * It is not recommended to use this method to initialize beans. Instead
  1561. * use the OODB object to dispense new beans. You can use this method
  1562. * if you build your own bean dispensing mechanism.
  1563. *
  1564. * @param string $type type of the new bean
  1565. * @param BeanHelper $beanhelper bean helper to obtain a toolbox and a model
  1566. *
  1567. * @return void
  1568. */
  1569. public function initializeForDispense( $type, BeanHelper $beanhelper )
  1570. {
  1571. $this->beanHelper = $beanhelper;
  1572. $this->__info['type'] = $type;
  1573. $this->__info['sys.id'] = 'id';
  1574. $this->__info['sys.orig'] = array( 'id' => 0 );
  1575. $this->__info['tainted'] = TRUE;
  1576. $this->__info['changed'] = TRUE;
  1577. $this->properties['id'] = 0;
  1578. }
  1579.  
  1580. /**
  1581. * Sets the Bean Helper. Normally the Bean Helper is set by OODB.
  1582. * Here you can change the Bean Helper. The Bean Helper is an object
  1583. * providing access to a toolbox for the bean necessary to retrieve
  1584. * nested beans (bean lists: ownBean, sharedBean) without the need to
  1585. * rely on static calls to the facade (or make this class dep. on OODB).
  1586. *
  1587. * @param BeanHelper $helper helper to use for this bean
  1588. *
  1589. * @return void
  1590. */
  1591. public function setBeanHelper( BeanHelper $helper )
  1592. {
  1593. $this->beanHelper = $helper;
  1594. }
  1595.  
  1596. /**
  1597. * Returns an ArrayIterator so you can treat the bean like
  1598. * an array with the properties container as its contents.
  1599. * This method is meant for PHP and allows you to access beans as if
  1600. * they were arrays, i.e. using array notation:
  1601. *
  1602. * $bean[$key] = $value;
  1603. *
  1604. * Note that not all PHP functions work with the array interface.
  1605. *
  1606. * @return ArrayIterator
  1607. */
  1608. public function getIterator()
  1609. {
  1610. return new \ArrayIterator( $this->properties );
  1611. }
  1612.  
  1613. /**
  1614. * Imports all values from an associative array $array. Chainable.
  1615. * This method imports the values in the first argument as bean
  1616. * propery and value pairs. Use the second parameter to provide a
  1617. * selection. If a selection array is passed, only the entries
  1618. * having keys mentioned in the selection array will be imported.
  1619. * Set the third parameter to TRUE to preserve spaces in selection keys.
  1620. *
  1621. * @param array $array what you want to import
  1622. * @param string|array $selection selection of values
  1623. * @param boolean $notrim if TRUE selection keys will NOT be trimmed
  1624. *
  1625. * @return OODBBean
  1626. */
  1627. public function import( $array, $selection = FALSE, $notrim = FALSE )
  1628. {
  1629. if ( is_string( $selection ) ) {
  1630. $selection = explode( ',', $selection );
  1631. }
  1632.  
  1633. if ( !$notrim && is_array( $selection ) ) {
  1634. foreach ( $selection as $key => $selected ) {
  1635. $selection[$key] = trim( $selected );
  1636. }
  1637. }
  1638.  
  1639. foreach ( $array as $key => $value ) {
  1640. if ( $key != '__info' ) {
  1641. if ( !$selection || ( $selection && in_array( $key, $selection ) ) ) {
  1642. if ( is_array($value ) ) {
  1643. if ( isset( $value['_type'] ) ) {
  1644. $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
  1645. unset( $value['_type'] );
  1646. $bean->import($value);
  1647. $this->$key = $bean;
  1648. } else {
  1649. $listBeans = array();
  1650. foreach( $value as $listKey => $listItem ) {
  1651. $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
  1652. unset( $listItem['_type'] );
  1653. $bean->import($listItem);
  1654. $list = &$this->$key;
  1655. $list[ $listKey ] = $bean;
  1656. }
  1657. }
  1658. } else {
  1659. $this->$key = $value;
  1660. }
  1661. }
  1662. }
  1663. }
  1664.  
  1665. return $this;
  1666. }
  1667.  
  1668. /**
  1669. * Fast way to import a row.
  1670. * Does not perform any checks.
  1671. *
  1672. * @param array $row a database row
  1673. *
  1674. * @return self
  1675. */
  1676. public function importRow( $row )
  1677. {
  1678. $this->properties = $row;
  1679. $this->__info['sys.orig'] = $row;
  1680. $this->__info['changed'] = FALSE;
  1681. return $this;
  1682. }
  1683.  
  1684. /**
  1685. * Imports data from another bean. Chainable.
  1686. * Copies the properties from the source bean to the internal
  1687. * property list.
  1688. *
  1689. * @param OODBBean $sourceBean the source bean to take properties from
  1690. *
  1691. * @return OODBBean
  1692. */
  1693. public function importFrom( OODBBean $sourceBean )
  1694. {
  1695. $this->__info['tainted'] = TRUE;
  1696. $this->__info['changed'] = TRUE;
  1697. $this->properties = $sourceBean->properties;
  1698.  
  1699. return $this;
  1700. }
  1701.  
  1702. /**
  1703. * Injects the properties of another bean but keeps the original ID.
  1704. * Just like import() but keeps the original ID.
  1705. * Chainable.
  1706. *
  1707. * @param OODBBean $otherBean the bean whose properties you would like to copy
  1708. *
  1709. * @return OODBBean
  1710. */
  1711. public function inject( OODBBean $otherBean )
  1712. {
  1713. $myID = $this->properties['id'];
  1714.  
  1715. $this->import( $otherBean->export() );
  1716.  
  1717. $this->id = $myID;
  1718.  
  1719. return $this;
  1720. }
  1721.  
  1722. /**
  1723. * Exports the bean as an array.
  1724. * This function exports the contents of a bean to an array and returns
  1725. * the resulting array.
  1726. *
  1727. * @param boolean $meta set to TRUE if you want to export meta data as well
  1728. * @param boolean $parents set to TRUE if you want to export parents as well
  1729. * @param boolean $onlyMe set to TRUE if you want to export only this bean
  1730. * @param array $filters optional whitelist for export
  1731. *
  1732. * @return array
  1733. */
  1734. public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
  1735. {
  1736. $arr = array();
  1737.  
  1738. if ( $parents ) {
  1739. foreach ( $this as $key => $value ) {
  1740. if ( substr( $key, -3 ) != '_id' ) continue;
  1741.  
  1742. $prop = substr( $key, 0, strlen( $key ) - 3 );
  1743. $this->$prop;
  1744. }
  1745. }
  1746.  
  1747. $hasFilters = is_array( $filters ) && count( $filters );
  1748.  
  1749. foreach ( $this as $key => $value ) {
  1750. if ( !$onlyMe && is_array( $value ) ) {
  1751. $vn = array();
  1752.  
  1753. foreach ( $value as $i => $b ) {
  1754. if ( !( $b instanceof OODBBean ) ) continue;
  1755. $vn[] = $b->export( $meta, FALSE, FALSE, $filters );
  1756. $value = $vn;
  1757. }
  1758. } elseif ( $value instanceof OODBBean ) {
  1759. if ( $hasFilters ) {
  1760. if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
  1761. }
  1762.  
  1763. $value = $value->export( $meta, $parents, FALSE, $filters );
  1764. }
  1765.  
  1766. $arr[$key] = $value;
  1767. }
  1768.  
  1769. if ( $meta ) {
  1770. $arr['__info'] = $this->__info;
  1771. }
  1772.  
  1773. return $arr;
  1774. }
  1775.  
  1776. /**
  1777. * Implements isset() function for use as an array.
  1778. *
  1779. * @param string $property name of the property you want to check
  1780. *
  1781. * @return boolean
  1782. */
  1783. public function __isset( $property )
  1784. {
  1785. $property = $this->beau( $property );
  1786.  
  1787. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  1788. $property = substr($property, 1);
  1789. }
  1790. return isset( $this->properties[$property] );
  1791. }
  1792.  
  1793. /**
  1794. * Returns the ID of the bean no matter what the ID field is.
  1795. *
  1796. * @return string|null
  1797. */
  1798. public function getID()
  1799. {
  1800. return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
  1801. }
  1802.  
  1803. /**
  1804. * Unsets a property of a bean.
  1805. * Magic method, gets called implicitly when performing the unset() operation
  1806. * on a bean property.
  1807. *
  1808. * @param string $property property to unset
  1809. *
  1810. * @return void
  1811. */
  1812. public function __unset( $property )
  1813. {
  1814. $property = $this->beau( $property );
  1815.  
  1816. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  1817. $property = substr($property, 1);
  1818. }
  1819.  
  1820. unset( $this->properties[$property] );
  1821.  
  1822. $shadowKey = 'sys.shadow.'.$property;
  1823. if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
  1824.  
  1825. //also clear modifiers
  1826. $this->withSql = '';
  1827. $this->withParams = array();
  1828. $this->aliasName = NULL;
  1829. $this->fetchType = NULL;
  1830. $this->noLoad = FALSE;
  1831. $this->all = FALSE;
  1832. $this->via = NULL;
  1833.  
  1834. return;
  1835. }
  1836.  
  1837. /**
  1838. * Adds WHERE clause conditions to ownList retrieval.
  1839. * For instance to get the pages that belong to a book you would
  1840. * issue the following command: $book->ownPage
  1841. * However, to order these pages by number use:
  1842. *
  1843. * <code>
  1844. * $book->with(' ORDER BY `number` ASC ')->ownPage
  1845. * </code>
  1846. *
  1847. * the additional SQL snippet will be merged into the final
  1848. * query.
  1849. *
  1850. * @param string $sql SQL to be added to retrieval query.
  1851. * @param array $bindings array with parameters to bind to SQL snippet
  1852. *
  1853. * @return OODBBean
  1854. */
  1855. public function with( $sql, $bindings = array() )
  1856. {
  1857. $this->withSql = $sql;
  1858. $this->withParams = $bindings;
  1859. return $this;
  1860. }
  1861.  
  1862. /**
  1863. * Just like with(). Except that this method prepends the SQL query snippet
  1864. * with AND which makes it slightly more comfortable to use a conditional
  1865. * SQL snippet. For instance to filter an own-list with pages (belonging to
  1866. * a book) on specific chapters you can use:
  1867. *
  1868. * $book->withCondition(' chapter = 3 ')->ownPage
  1869. *
  1870. * This will return in the own list only the pages having 'chapter == 3'.
  1871. *
  1872. * @param string $sql SQL to be added to retrieval query (prefixed by AND)
  1873. * @param array $bindings array with parameters to bind to SQL snippet
  1874. *
  1875. * @return OODBBean
  1876. */
  1877. public function withCondition( $sql, $bindings = array() )
  1878. {
  1879. $this->withSql = ' AND ' . $sql;
  1880. $this->withParams = $bindings;
  1881. return $this;
  1882. }
  1883.  
  1884. /**
  1885. * Tells the bean to (re)load the following list without any
  1886. * conditions. If you have an ownList or sharedList with a
  1887. * condition you can use this method to reload the entire list.
  1888. *
  1889. * Usage:
  1890. *
  1891. * <code>
  1892. * $bean->with( ' LIMIT 3 ' )->ownPage; //Just 3
  1893. * $bean->all()->ownPage; //Reload all pages
  1894. * </code>
  1895. *
  1896. * @return self
  1897. */
  1898. public function all()
  1899. {
  1900. $this->all = TRUE;
  1901. return $this;
  1902. }
  1903.  
  1904. /**
  1905. * Tells the bean to only access the list but not load
  1906. * its contents. Use this if you only want to add something to a list
  1907. * and you have no interest in retrieving its contents from the database.
  1908. *
  1909. * @return self
  1910. */
  1911. public function noLoad()
  1912. {
  1913. $this->noLoad = TRUE;
  1914. return $this;
  1915. }
  1916.  
  1917. /**
  1918. * Prepares an own-list to use an alias. This is best explained using
  1919. * an example. Imagine a project and a person. The project always involves
  1920. * two persons: a teacher and a student. The person beans have been aliased in this
  1921. * case, so to the project has a teacher_id pointing to a person, and a student_id
  1922. * also pointing to a person. Given a project, we obtain the teacher like this:
  1923. *
  1924. * <code>
  1925. * $project->fetchAs('person')->teacher;
  1926. * </code>
  1927. *
  1928. * Now, if we want all projects of a teacher we cant say:
  1929. *
  1930. * <code>
  1931. * $teacher->ownProject
  1932. * </code>
  1933. *
  1934. * because the $teacher is a bean of type 'person' and no project has been
  1935. * assigned to a person. Instead we use the alias() method like this:
  1936. *
  1937. * <code>
  1938. * $teacher->alias('teacher')->ownProject
  1939. * </code>
  1940. *
  1941. * now we get the projects associated with the person bean aliased as
  1942. * a teacher.
  1943. *
  1944. * @param string $aliasName the alias name to use
  1945. *
  1946. * @return OODBBean
  1947. */
  1948. public function alias( $aliasName )
  1949. {
  1950. $this->aliasName = $this->beau( $aliasName );
  1951.  
  1952. return $this;
  1953. }
  1954.  
  1955. /**
  1956. * Returns properties of bean as an array.
  1957. * This method returns the raw internal property list of the
  1958. * bean. Only use this method for optimization purposes. Otherwise
  1959. * use the export() method to export bean data to arrays.
  1960. *
  1961. * @return array
  1962. */
  1963. public function getProperties()
  1964. {
  1965. return $this->properties;
  1966. }
  1967.  
  1968. /**
  1969. * Returns properties of bean as an array.
  1970. * This method returns the raw internal property list of the
  1971. * bean. Only use this method for optimization purposes. Otherwise
  1972. * use the export() method to export bean data to arrays.
  1973. * This method returns an array with the properties array and
  1974. * the type (string).
  1975. *
  1976. * @return array
  1977. */
  1978. public function getPropertiesAndType()
  1979. {
  1980. return array( $this->properties, $this->__info['type'] );
  1981. }
  1982.  
  1983. /**
  1984. * Turns a camelcase property name into an underscored property name.
  1985. *
  1986. * Examples:
  1987. *
  1988. * * oneACLRoute -> one_acl_route
  1989. * * camelCase -> camel_case
  1990. *
  1991. * Also caches the result to improve performance.
  1992. *
  1993. * @param string $property property to un-beautify
  1994. *
  1995. * @return string
  1996. */
  1997. public function beau( $property )
  1998. {
  1999. static $beautifulColumns = array();
  2000.  
  2001. if ( ctype_lower( $property ) ) return $property;
  2002.  
  2003. if (
  2004. ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )
  2005. || ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) )
  2006. || ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) )
  2007. ) {
  2008.  
  2009. $property = preg_replace( '/List$/', '', $property );
  2010. return $property;
  2011. }
  2012.  
  2013. if ( !isset( $beautifulColumns[$property] ) ) {
  2014. $beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
  2015. }
  2016.  
  2017. return $beautifulColumns[$property];
  2018. }
  2019.  
  2020. /**
  2021. * Clears all modifiers.
  2022. *
  2023. * @return self
  2024. */
  2025. public function clearModifiers()
  2026. {
  2027. $this->withSql = '';
  2028. $this->withParams = array();
  2029. $this->aliasName = NULL;
  2030. $this->fetchType = NULL;
  2031. $this->noLoad = FALSE;
  2032. $this->all = FALSE;
  2033. $this->via = NULL;
  2034. return $this;
  2035. }
  2036.  
  2037. /**
  2038. * Determines whether a list is opened in exclusive mode or not.
  2039. * If a list has been opened in exclusive mode this method will return TRUE,
  2040. * othwerwise it will return FALSE.
  2041. *
  2042. * @param string $listName name of the list to check
  2043. *
  2044. * @return boolean
  2045. */
  2046. public function isListInExclusiveMode( $listName )
  2047. {
  2048. $listName = $this->beau( $listName );
  2049.  
  2050. if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
  2051. $listName = substr($listName, 1);
  2052. }
  2053.  
  2054. $listName = lcfirst( substr( $listName, 3 ) );
  2055.  
  2056. return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
  2057. }
  2058.  
  2059. /**
  2060. * Magic Getter. Gets the value for a specific property in the bean.
  2061. * If the property does not exist this getter will make sure no error
  2062. * occurs. This is because RedBean allows you to query (probe) for
  2063. * properties. If the property can not be found this method will
  2064. * return NULL instead.
  2065. *
  2066. * @param string $property name of the property you wish to obtain the value of
  2067. *
  2068. * @return mixed
  2069. */
  2070. public function &__get( $property )
  2071. {
  2072. $isEx = FALSE;
  2073. $isOwn = FALSE;
  2074. $isShared = FALSE;
  2075.  
  2076. if ( !ctype_lower( $property ) ) {
  2077. $property = $this->beau( $property );
  2078. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  2079. $property = substr($property, 1);
  2080. $listName = lcfirst( substr( $property, 3 ) );
  2081. $isEx = TRUE;
  2082. $isOwn = TRUE;
  2083. $this->__info['sys.exclusive-'.$listName] = TRUE;
  2084. } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
  2085. $isOwn = TRUE;
  2086. $listName = lcfirst( substr( $property, 3 ) );
  2087. } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
  2088. $isShared = TRUE;
  2089. }
  2090. }
  2091.  
  2092. $fieldLink = $property . '_id';
  2093. $exists = isset( $this->properties[$property] );
  2094.  
  2095. //If not exists and no field link and no list, bail out.
  2096. if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
  2097.  
  2098. $this->withSql = '';
  2099. $this->withParams = array();
  2100. $this->aliasName = NULL;
  2101. $this->fetchType = NULL;
  2102. $this->noLoad = FALSE;
  2103. $this->all = FALSE;
  2104. $this->via = NULL;
  2105.  
  2106. $NULL = NULL;
  2107. return $NULL;
  2108. }
  2109.  
  2110. $hasAlias = (!is_null($this->aliasName));
  2111. $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
  2112. ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
  2113. $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
  2114. $hasAll = (boolean) ($this->all);
  2115.  
  2116. //If exists and no list or exits and list not changed, bail out.
  2117. if ( $exists && ((!$isOwn && !$isShared ) || (!$hasSQL && !$differentAlias && !$hasAll)) ) {
  2118.  
  2119. $this->withSql = '';
  2120. $this->withParams = array();
  2121. $this->aliasName = NULL;
  2122. $this->fetchType = NULL;
  2123. $this->noLoad = FALSE;
  2124. $this->all = FALSE;
  2125. $this->via = NULL;
  2126. return $this->properties[$property];
  2127. }
  2128.  
  2129. list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
  2130.  
  2131. if ( isset( $this->$fieldLink ) ) {
  2132. $this->__info['tainted'] = TRUE;
  2133.  
  2134. if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
  2135. $bean = $this->__info["sys.parentcache.$property"];
  2136. } else {
  2137. if ( isset( self::$aliases[$property] ) ) {
  2138. $type = self::$aliases[$property];
  2139. } elseif ( $this->fetchType ) {
  2140. $type = $this->fetchType;
  2141. $this->fetchType = NULL;
  2142. } else {
  2143. $type = $property;
  2144. }
  2145. $bean = NULL;
  2146. if ( !is_null( $this->properties[$fieldLink] ) ) {
  2147. $bean = $redbean->load( $type, $this->properties[$fieldLink] );
  2148. //If the IDs dont match, we failed to load, so try autoresolv in that case...
  2149. if ( $bean->id !== $this->properties[$fieldLink] && self::$autoResolve ) {
  2150. $type = $this->beanHelper->getToolbox()->getWriter()->inferFetchType( $this->__info['type'], $property );
  2151. if ( !is_null( $type) ) {
  2152. $bean = $redbean->load( $type, $this->properties[$fieldLink] );
  2153. $this->__info["sys.autoresolved.{$property}"] = $type;
  2154. }
  2155. }
  2156. }
  2157. }
  2158.  
  2159. $this->properties[$property] = $bean;
  2160. $this->withSql = '';
  2161. $this->withParams = array();
  2162. $this->aliasName = NULL;
  2163. $this->fetchType = NULL;
  2164. $this->noLoad = FALSE;
  2165. $this->all = FALSE;
  2166. $this->via = NULL;
  2167.  
  2168. return $this->properties[$property];
  2169.  
  2170. }
  2171. //Implicit: elseif ( $isOwn || $isShared ) {
  2172. if ( $this->noLoad ) {
  2173. $beans = array();
  2174. } elseif ( $isOwn ) {
  2175. $beans = $this->getOwnList( $listName, $redbean );
  2176. } else {
  2177. $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
  2178. }
  2179.  
  2180. $this->properties[$property] = $beans;
  2181. $this->__info["sys.shadow.$property"] = $beans;
  2182. $this->__info['tainted'] = TRUE;
  2183.  
  2184. $this->withSql = '';
  2185. $this->withParams = array();
  2186. $this->aliasName = NULL;
  2187. $this->fetchType = NULL;
  2188. $this->noLoad = FALSE;
  2189. $this->all = FALSE;
  2190. $this->via = NULL;
  2191.  
  2192. return $this->properties[$property];
  2193. }
  2194.  
  2195. /**
  2196. * Magic Setter. Sets the value for a specific property.
  2197. * This setter acts as a hook for OODB to mark beans as tainted.
  2198. * The tainted meta property can be retrieved using getMeta("tainted").
  2199. * The tainted meta property indicates whether a bean has been modified and
  2200. * can be used in various caching mechanisms.
  2201. *
  2202. * @param string $property name of the property you wish to assign a value to
  2203. * @param mixed $value the value you want to assign
  2204. *
  2205. * @return void
  2206. */
  2207. public function __set( $property, $value )
  2208. {
  2209. $isEx = FALSE;
  2210. $isOwn = FALSE;
  2211. $isShared = FALSE;
  2212.  
  2213. if ( !ctype_lower( $property ) ) {
  2214. $property = $this->beau( $property );
  2215. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  2216. $property = substr($property, 1);
  2217. $listName = lcfirst( substr( $property, 3 ) );
  2218. $isEx = TRUE;
  2219. $isOwn = TRUE;
  2220. $this->__info['sys.exclusive-'.$listName] = TRUE;
  2221. } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
  2222. $isOwn = TRUE;
  2223. $listName = lcfirst( substr( $property, 3 ) );
  2224. } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
  2225. $isShared = TRUE;
  2226. }
  2227. }
  2228.  
  2229. $hasAlias = (!is_null($this->aliasName));
  2230. $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
  2231. ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
  2232. $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
  2233. $exists = isset( $this->properties[$property] );
  2234. $fieldLink = $property . '_id';
  2235.  
  2236. if ( ($isOwn || $isShared) && (!$exists || $hasSQL || $differentAlias) ) {
  2237.  
  2238. if ( !$this->noLoad ) {
  2239. list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
  2240. if ( $isOwn ) {
  2241. $beans = $this->getOwnList( $listName, $redbean );
  2242. } else {
  2243. $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
  2244. }
  2245. $this->__info["sys.shadow.$property"] = $beans;
  2246. }
  2247. }
  2248.  
  2249. $this->withSql = '';
  2250. $this->withParams = array();
  2251. $this->aliasName = NULL;
  2252. $this->fetchType = NULL;
  2253. $this->noLoad = FALSE;
  2254. $this->all = FALSE;
  2255. $this->via = NULL;
  2256.  
  2257. $this->__info['tainted'] = TRUE;
  2258. $this->__info['changed'] = TRUE;
  2259.  
  2260. if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
  2261. if ( is_null( $value ) || $value === FALSE ) {
  2262.  
  2263. unset( $this->properties[ $property ]);
  2264. $this->properties[ $fieldLink ] = NULL;
  2265.  
  2266. return;
  2267. } else {
  2268. throw new RedException( 'Cannot cast to bean.' );
  2269. }
  2270. }
  2271.  
  2272. if ( $value === FALSE ) {
  2273. $value = '0';
  2274. } elseif ( $value === TRUE ) {
  2275. $value = '1';
  2276. } elseif ( $value instanceof \DateTime ) {
  2277. $value = $value->format( 'Y-m-d H:i:s' );
  2278. }
  2279.  
  2280. $this->properties[$property] = $value;
  2281. }
  2282.  
  2283. /**
  2284. * Sets a property directly, for internal use only.
  2285. *
  2286. * @param string $property property
  2287. * @param mixed $value value
  2288. * @param boolean $updateShadow whether you want to update the shadow
  2289. * @param boolean $taint whether you want to mark the bean as tainted
  2290. *
  2291. * @return void
  2292. */
  2293. public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
  2294. {
  2295. $this->properties[$property] = $value;
  2296.  
  2297. if ( $updateShadow ) {
  2298. $this->__info['sys.shadow.' . $property] = $value;
  2299. }
  2300.  
  2301. if ( $taint ) {
  2302. $this->__info['tainted'] = TRUE;
  2303. $this->__info['changed'] = TRUE;
  2304. }
  2305. }
  2306.  
  2307. /**
  2308. * Returns the value of a meta property. A meta property
  2309. * contains additional information about the bean object that will not
  2310. * be stored in the database. Meta information is used to instruct
  2311. * RedBeanPHP as well as other systems how to deal with the bean.
  2312. * If the property cannot be found this getter will return NULL instead.
  2313. *
  2314. * Example:
  2315. *
  2316. * <code>
  2317. * $bean->setMeta( 'flush-cache', TRUE );
  2318. * </code>
  2319. *
  2320. * RedBeanPHP also stores meta data in beans, this meta data uses
  2321. * keys prefixed with 'sys.' (system).
  2322. *
  2323. * @param string $path path to property in meta data
  2324. * @param mixed $default default value
  2325. *
  2326. * @return mixed
  2327. */
  2328. public function getMeta( $path, $default = NULL )
  2329. {
  2330. return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
  2331. }
  2332.  
  2333. /**
  2334. * Gets and unsets a meta property.
  2335. * Moves a meta property out of the bean.
  2336. * This is a short-cut method that can be used instead
  2337. * of combining a get/unset.
  2338. *
  2339. * @param string $path path to property in meta data
  2340. * @param mixed $default default value
  2341. *
  2342. * @return mixed
  2343. */
  2344. public function moveMeta( $path, $value = NULL )
  2345. {
  2346. if ( isset( $this->__info[$path] ) ) {
  2347. $value = $this->__info[ $path ];
  2348. unset( $this->__info[ $path ] );
  2349. }
  2350. return $value;
  2351. }
  2352.  
  2353. /**
  2354. * Stores a value in the specified Meta information property.
  2355. * The first argument should be the key to store the value under,
  2356. * the second argument should be the value. It is common to use
  2357. * a path-like notation for meta data in RedBeanPHP like:
  2358. * 'my.meta.data', however the dots are purely for readability, the
  2359. * meta data methods do not store nested structures or hierarchies.
  2360. *
  2361. * @param string $path path / key to store value under
  2362. * @param mixed $value value to store in bean (not in database) as meta data
  2363. *
  2364. * @return OODBBean
  2365. */
  2366. public function setMeta( $path, $value )
  2367. {
  2368. $this->__info[$path] = $value;
  2369.  
  2370. return $this;
  2371. }
  2372.  
  2373. /**
  2374. * Copies the meta information of the specified bean
  2375. * This is a convenience method to enable you to
  2376. * exchange meta information easily.
  2377. *
  2378. * @param OODBBean $bean bean to copy meta data of
  2379. *
  2380. * @return OODBBean
  2381. */
  2382. public function copyMetaFrom( OODBBean $bean )
  2383. {
  2384. $this->__info = $bean->__info;
  2385.  
  2386. return $this;
  2387. }
  2388.  
  2389. /**
  2390. * Sends the call to the registered model.
  2391. *
  2392. * @param string $method name of the method
  2393. * @param array $args argument list
  2394. *
  2395. * @return mixed
  2396. */
  2397. public function __call( $method, $args )
  2398. {
  2399. if ( !isset( $this->__info['model'] ) ) {
  2400. $model = $this->beanHelper->getModelForBean( $this );
  2401.  
  2402. if ( !$model ) {
  2403. return NULL;
  2404. }
  2405.  
  2406. $this->__info['model'] = $model;
  2407. }
  2408. if ( !method_exists( $this->__info['model'], $method ) ) {
  2409.  
  2410. if ( self::$errorHandlingFUSE === FALSE ) {
  2411. return NULL;
  2412. }
  2413.  
  2414. if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
  2415. return NULL;
  2416. }
  2417.  
  2418. $message = "FUSE: method does not exist in model: $method";
  2419. if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
  2420. error_log( $message );
  2421. return NULL;
  2422. } elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
  2423. trigger_error( $message, E_USER_NOTICE );
  2424. return NULL;
  2425. } elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
  2426. trigger_error( $message, E_USER_WARNING );
  2427. return NULL;
  2428. } elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
  2429. throw new \Exception( $message );
  2430. } elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
  2431. $func = self::$errorHandler;
  2432. return $func(array(
  2433. 'message' => $message,
  2434. 'method' => $method,
  2435. 'args' => $args,
  2436. 'bean' => $this
  2437. ));
  2438. }
  2439. trigger_error( $message, E_USER_ERROR );
  2440. return NULL;
  2441. }
  2442.  
  2443. return call_user_func_array( array( $this->__info['model'], $method ), $args );
  2444. }
  2445.  
  2446. /**
  2447. * Implementation of __toString Method
  2448. * Routes call to Model. If the model implements a __toString() method this
  2449. * method will be called and the result will be returned. In case of an
  2450. * echo-statement this result will be printed. If the model does not
  2451. * implement a __toString method, this method will return a JSON
  2452. * representation of the current bean.
  2453. *
  2454. * @return string
  2455. */
  2456. public function __toString()
  2457. {
  2458. $string = $this->__call( '__toString', array() );
  2459.  
  2460. if ( $string === NULL ) {
  2461. $list = array();
  2462. foreach($this->properties as $property => $value) {
  2463. if (is_scalar($value)) {
  2464. $list[$property] = $value;
  2465. }
  2466. }
  2467. return json_encode( $list );
  2468. } else {
  2469. return $string;
  2470. }
  2471. }
  2472.  
  2473. /**
  2474. * Implementation of Array Access Interface, you can access bean objects
  2475. * like an array.
  2476. * Call gets routed to __set.
  2477. *
  2478. * @param mixed $offset offset string
  2479. * @param mixed $value value
  2480. *
  2481. * @return void
  2482. */
  2483. public function offsetSet( $offset, $value )
  2484. {
  2485. $this->__set( $offset, $value );
  2486. }
  2487.  
  2488. /**
  2489. * Implementation of Array Access Interface, you can access bean objects
  2490. * like an array.
  2491. *
  2492. * Array functions do not reveal x-own-lists and list-alias because
  2493. * you dont want duplicate entries in foreach-loops.
  2494. * Also offers a slight performance improvement for array access.
  2495. *
  2496. * @param mixed $offset property
  2497. *
  2498. * @return boolean
  2499. */
  2500. public function offsetExists( $offset )
  2501. {
  2502. return $this->__isset( $offset );
  2503. }
  2504.  
  2505. /**
  2506. * Implementation of Array Access Interface, you can access bean objects
  2507. * like an array.
  2508. * Unsets a value from the array/bean.
  2509. *
  2510. * Array functions do not reveal x-own-lists and list-alias because
  2511. * you dont want duplicate entries in foreach-loops.
  2512. * Also offers a slight performance improvement for array access.
  2513. *
  2514. * @param mixed $offset property
  2515. *
  2516. * @return void
  2517. */
  2518. public function offsetUnset( $offset )
  2519. {
  2520. $this->__unset( $offset );
  2521. }
  2522.  
  2523. /**
  2524. * Implementation of Array Access Interface, you can access bean objects
  2525. * like an array.
  2526. * Returns value of a property.
  2527. *
  2528. * Array functions do not reveal x-own-lists and list-alias because
  2529. * you dont want duplicate entries in foreach-loops.
  2530. * Also offers a slight performance improvement for array access.
  2531. *
  2532. * @param mixed $offset property
  2533. *
  2534. * @return mixed
  2535. */
  2536. public function &offsetGet( $offset )
  2537. {
  2538. return $this->__get( $offset );
  2539. }
  2540.  
  2541. /**
  2542. * Chainable method to cast a certain ID to a bean; for instance:
  2543. * $person = $club->fetchAs('person')->member;
  2544. * This will load a bean of type person using member_id as ID.
  2545. *
  2546. * @param string $type preferred fetch type
  2547. *
  2548. * @return OODBBean
  2549. */
  2550. public function fetchAs( $type )
  2551. {
  2552. $this->fetchType = $type;
  2553.  
  2554. return $this;
  2555. }
  2556.  
  2557. /**
  2558. * For polymorphic bean relations.
  2559. * Same as fetchAs but uses a column instead of a direct value.
  2560. *
  2561. * @param string $field field name to use for mapping
  2562. *
  2563. * @return OODBBean
  2564. */
  2565. public function poly( $field )
  2566. {
  2567. return $this->fetchAs( $this->$field );
  2568. }
  2569.  
  2570. /**
  2571. * Traverses a bean property with the specified function.
  2572. * Recursively iterates through the property invoking the
  2573. * function for each bean along the way passing the bean to it.
  2574. *
  2575. * Can be used together with with, withCondition, alias and fetchAs.
  2576. *
  2577. * @param string $property property
  2578. * @param callable $function function
  2579. * @param integer $maxDepth maximum depth for traversal
  2580. *
  2581. * @return OODBBean
  2582. * @throws RedException
  2583. */
  2584. public function traverse( $property, $function, $maxDepth = NULL )
  2585. {
  2586. $this->via = NULL;
  2587. if ( strpos( $property, 'shared' ) !== FALSE ) {
  2588. throw new RedException( 'Traverse only works with (x)own-lists.' );
  2589. }
  2590.  
  2591. if ( !is_null( $maxDepth ) ) {
  2592. if ( !$maxDepth-- ) return $this;
  2593. }
  2594.  
  2595. $oldFetchType = $this->fetchType;
  2596. $oldAliasName = $this->aliasName;
  2597. $oldWith = $this->withSql;
  2598. $oldBindings = $this->withParams;
  2599.  
  2600. $beans = $this->$property;
  2601.  
  2602. if ( $beans === NULL ) return $this;
  2603.  
  2604. if ( !is_array( $beans ) ) $beans = array( $beans );
  2605.  
  2606. foreach( $beans as $bean ) {
  2607. /** @var OODBBean $bean */
  2608. $function( $bean );
  2609.  
  2610. $bean->fetchType = $oldFetchType;
  2611. $bean->aliasName = $oldAliasName;
  2612. $bean->withSql = $oldWith;
  2613. $bean->withParams = $oldBindings;
  2614.  
  2615. $bean->traverse( $property, $function, $maxDepth );
  2616. }
  2617.  
  2618. return $this;
  2619. }
  2620.  
  2621. /**
  2622. * Implementation of Countable interface. Makes it possible to use
  2623. * count() function on a bean.
  2624. *
  2625. * @return integer
  2626. */
  2627. public function count()
  2628. {
  2629. return count( $this->properties );
  2630. }
  2631.  
  2632. /**
  2633. * Checks whether a bean is empty or not.
  2634. * A bean is empty if it has no other properties than the id field OR
  2635. * if all the other property are empty().
  2636. *
  2637. * @return boolean
  2638. */
  2639. public function isEmpty()
  2640. {
  2641. $empty = TRUE;
  2642. foreach ( $this->properties as $key => $value ) {
  2643. if ( $key == 'id' ) {
  2644. continue;
  2645. }
  2646. if ( !empty( $value ) ) {
  2647. $empty = FALSE;
  2648. }
  2649. }
  2650.  
  2651. return $empty;
  2652. }
  2653.  
  2654. /**
  2655. * Chainable setter.
  2656. *
  2657. * @param string $property the property of the bean
  2658. * @param mixed $value the value you want to set
  2659. *
  2660. * @return OODBBean
  2661. */
  2662. public function setAttr( $property, $value )
  2663. {
  2664. $this->$property = $value;
  2665.  
  2666. return $this;
  2667. }
  2668.  
  2669. /**
  2670. * Comfort method.
  2671. * Unsets all properties in array.
  2672. *
  2673. * @param array $properties properties you want to unset.
  2674. *
  2675. * @return OODBBean
  2676. */
  2677. public function unsetAll( $properties )
  2678. {
  2679. foreach ( $properties as $prop ) {
  2680. if ( isset( $this->properties[$prop] ) ) {
  2681. unset( $this->properties[$prop] );
  2682. }
  2683. }
  2684.  
  2685. return $this;
  2686. }
  2687.  
  2688. /**
  2689. * Returns original (old) value of a property.
  2690. * You can use this method to see what has changed in a
  2691. * bean.
  2692. *
  2693. * @param string $property name of the property you want the old value of
  2694. *
  2695. * @return mixed
  2696. */
  2697. public function old( $property )
  2698. {
  2699. $old = $this->getMeta( 'sys.orig', array() );
  2700.  
  2701. if ( array_key_exists( $property, $old ) ) {
  2702. return $old[$property];
  2703. }
  2704.  
  2705. return NULL;
  2706. }
  2707.  
  2708. /**
  2709. * Convenience method.
  2710. * Returns TRUE if the bean has been changed, or FALSE otherwise.
  2711. * Same as $bean->getMeta('tainted');
  2712. * Note that a bean becomes tainted as soon as you retrieve a list from
  2713. * the bean. This is because the bean lists are arrays and the bean cannot
  2714. * determine whether you have made modifications to a list so RedBeanPHP
  2715. * will mark the whole bean as tainted.
  2716. *
  2717. * @return boolean
  2718. */
  2719. public function isTainted()
  2720. {
  2721. return $this->getMeta( 'tainted' );
  2722. }
  2723.  
  2724. /**
  2725. * Returns TRUE if the value of a certain property of the bean has been changed and
  2726. * FALSE otherwise.
  2727. *
  2728. * Note that this method will return TRUE if applied to a loaded list.
  2729. * Also note that this method keeps track of the bean's history regardless whether
  2730. * it has been stored or not. Storing a bean does not undo it's history,
  2731. * to clean the history of a bean use: clearHistory().
  2732. *
  2733. * @param string $property name of the property you want the change-status of
  2734. *
  2735. * @return boolean
  2736. */
  2737. public function hasChanged( $property )
  2738. {
  2739. return ( array_key_exists( $property, $this->properties ) ) ?
  2740. $this->old( $property ) != $this->properties[$property] : FALSE;
  2741. }
  2742.  
  2743. /**
  2744. * Returns TRUE if the specified list exists, has been loaded and has been changed:
  2745. * beans have been added or deleted. This method will not tell you anything about
  2746. * the state of the beans in the list.
  2747. *
  2748. * @param string $property name of the list to check
  2749. *
  2750. * @return boolean
  2751. */
  2752. public function hasListChanged( $property )
  2753. {
  2754. if ( !array_key_exists( $property, $this->properties ) ) return FALSE;
  2755. $diffAdded = array_diff_assoc( $this->properties[$property], $this->__info['sys.shadow.'.$property] );
  2756. if ( count( $diffAdded ) ) return TRUE;
  2757. $diffMissing = array_diff_assoc( $this->__info['sys.shadow.'.$property], $this->properties[$property] );
  2758. if ( count( $diffMissing ) ) return TRUE;
  2759. return FALSE;
  2760. }
  2761.  
  2762. /**
  2763. * Clears (syncs) the history of the bean.
  2764. * Resets all shadow values of the bean to their current value.
  2765. *
  2766. * @return self
  2767. */
  2768. public function clearHistory()
  2769. {
  2770. $this->__info['sys.orig'] = array();
  2771. foreach( $this->properties as $key => $value ) {
  2772. if ( is_scalar($value) ) {
  2773. $this->__info['sys.orig'][$key] = $value;
  2774. } else {
  2775. $this->__info['sys.shadow.'.$key] = $value;
  2776. }
  2777. }
  2778. return $this;
  2779. }
  2780.  
  2781. /**
  2782. * Creates a N-M relation by linking an intermediate bean.
  2783. * This method can be used to quickly connect beans using indirect
  2784. * relations. For instance, given an album and a song you can connect the two
  2785. * using a track with a number like this:
  2786. *
  2787. * Usage:
  2788. *
  2789. * <code>
  2790. * $album->link('track', array('number'=>1))->song = $song;
  2791. * </code>
  2792. *
  2793. * or:
  2794. *
  2795. * <code>
  2796. * $album->link($trackBean)->song = $song;
  2797. * </code>
  2798. *
  2799. * What this method does is adding the link bean to the own-list, in this case
  2800. * ownTrack. If the first argument is a string and the second is an array or
  2801. * a JSON string then the linking bean gets dispensed on-the-fly as seen in
  2802. * example #1. After preparing the linking bean, the bean is returned thus
  2803. * allowing the chained setter: ->song = $song.
  2804. *
  2805. * @param string|OODBBean $type type of bean to dispense or the full bean
  2806. * @param string|array $qualification JSON string or array (optional)
  2807. *
  2808. * @return OODBBean
  2809. */
  2810. public function link( $typeOrBean, $qualification = array() )
  2811. {
  2812. if ( is_string( $typeOrBean ) ) {
  2813.  
  2814. $typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
  2815.  
  2816. $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
  2817.  
  2818. if ( is_string( $qualification ) ) {
  2819. $data = json_decode( $qualification, TRUE );
  2820. } else {
  2821. $data = $qualification;
  2822. }
  2823.  
  2824. foreach ( $data as $key => $value ) {
  2825. $bean->$key = $value;
  2826. }
  2827. } else {
  2828. $bean = $typeOrBean;
  2829. }
  2830.  
  2831. $list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
  2832.  
  2833. array_push( $this->$list, $bean );
  2834.  
  2835. return $bean;
  2836. }
  2837.  
  2838. /**
  2839. * Returns a bean of the given type with the same ID of as
  2840. * the current one. This only happens in a one-to-one relation.
  2841. * This is as far as support for 1-1 goes in RedBeanPHP. This
  2842. * method will only return a reference to the bean, changing it
  2843. * and storing the bean will not update the related one-bean.
  2844. *
  2845. * @return OODBBean
  2846. */
  2847. public function one( $type ) {
  2848. return $this->beanHelper->getToolBox()->getRedBean()->load( $type, $this->id );
  2849. }
  2850.  
  2851. /**
  2852. * Returns the same bean freshly loaded from the database.
  2853. *
  2854. * @return OODBBean
  2855. */
  2856. public function fresh()
  2857. {
  2858. return $this->beanHelper->getToolbox()->getRedBean()->load( $this->getMeta( 'type' ), $this->properties['id'] );
  2859. }
  2860.  
  2861. /**
  2862. * Registers a association renaming globally.
  2863. *
  2864. * @param string $via type you wish to use for shared lists
  2865. *
  2866. * @return OODBBean
  2867. */
  2868. public function via( $via )
  2869. {
  2870. $this->via = AQueryWriter::camelsSnake( $via );
  2871.  
  2872. return $this;
  2873. }
  2874.  
  2875. /**
  2876. * Counts all own beans of type $type.
  2877. * Also works with alias(), with() and withCondition().
  2878. *
  2879. * @param string $type the type of bean you want to count
  2880. *
  2881. * @return integer
  2882. */
  2883. public function countOwn( $type )
  2884. {
  2885. $type = $this->beau( $type );
  2886.  
  2887. if ( $this->aliasName ) {
  2888. $myFieldLink = $this->aliasName . '_id';
  2889.  
  2890. $this->aliasName = NULL;
  2891. } else {
  2892. $myFieldLink = $this->__info['type'] . '_id';
  2893. }
  2894.  
  2895. $count = 0;
  2896.  
  2897. if ( $this->getID() ) {
  2898.  
  2899. $firstKey = NULL;
  2900. if ( count( $this->withParams ) > 0 ) {
  2901. reset( $this->withParams );
  2902. $firstKey = key( $this->withParams );
  2903. }
  2904.  
  2905. $joinSql = $this->parseJoin( $type );
  2906.  
  2907. if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
  2908. $bindings = $this->withParams;
  2909. $bindings[':slot0'] = $this->getID();
  2910. $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
  2911. } else {
  2912. $bindings = array_merge( array( $this->getID() ), $this->withParams );
  2913. $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
  2914. }
  2915.  
  2916. }
  2917.  
  2918. $this->clearModifiers();
  2919. return (int) $count;
  2920. }
  2921.  
  2922. /**
  2923. * Counts all shared beans of type $type.
  2924. * Also works with via(), with() and withCondition().
  2925. *
  2926. * @param string $type type of bean you wish to count
  2927. *
  2928. * @return integer
  2929. */
  2930. public function countShared( $type )
  2931. {
  2932. $toolbox = $this->beanHelper->getToolbox();
  2933. $redbean = $toolbox->getRedBean();
  2934. $writer = $toolbox->getWriter();
  2935.  
  2936. if ( $this->via ) {
  2937. $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
  2938.  
  2939. if ( $oldName !== $this->via ) {
  2940. //set the new renaming rule
  2941. $writer->renameAssocTable( $oldName, $this->via );
  2942. $this->via = NULL;
  2943. }
  2944. }
  2945.  
  2946. $type = $this->beau( $type );
  2947. $count = 0;
  2948.  
  2949. if ( $this->getID() ) {
  2950. $count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
  2951. }
  2952.  
  2953. $this->clearModifiers();
  2954. return (integer) $count;
  2955. }
  2956.  
  2957. /**
  2958. * Iterates through the specified own-list and
  2959. * fetches all properties (with their type) and
  2960. * returns the references.
  2961. * Use this method to quickly load indirectly related
  2962. * beans in an own-list. Whenever you cannot use a
  2963. * shared-list this method offers the same convenience
  2964. * by aggregating the parent beans of all children in
  2965. * the specified own-list.
  2966. *
  2967. * Example:
  2968. *
  2969. * <code>
  2970. * $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
  2971. * </code>
  2972. *
  2973. * Loads (in batch) and returns references to all
  2974. * quest beans residing in the $questTarget->target properties
  2975. * of each element in the xownQuestTargetList.
  2976. *
  2977. * @param string $list the list you wish to process
  2978. * @param string $property the property to load
  2979. * @param string $type the type of bean residing in this property (optional)
  2980. *
  2981. * @return array
  2982. */
  2983. public function &aggr( $list, $property, $type = NULL )
  2984. {
  2985. $this->via = NULL;
  2986. $ids = $beanIndex = $references = array();
  2987.  
  2988. if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
  2989. if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
  2990. if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
  2991.  
  2992. if ( is_null( $type ) ) $type = $property;
  2993.  
  2994. foreach( $this->$list as $bean ) {
  2995. $field = $property . '_id';
  2996. if ( isset( $bean->$field) ) {
  2997. $ids[] = $bean->$field;
  2998. $beanIndex[$bean->$field] = $bean;
  2999. }
  3000. }
  3001.  
  3002. $beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
  3003.  
  3004. //now preload the beans as well
  3005. foreach( $beans as $bean ) {
  3006. $beanIndex[$bean->id]->setProperty( $property, $bean );
  3007. }
  3008.  
  3009. foreach( $beanIndex as $indexedBean ) {
  3010. $references[] = $indexedBean->$property;
  3011. }
  3012.  
  3013. return $references;
  3014. }
  3015.  
  3016. /**
  3017. * Tests whether the database identities of two beans are equal.
  3018. *
  3019. * @param OODBBean $bean other bean
  3020. *
  3021. * @return boolean
  3022. */
  3023. public function equals(OODBBean $bean)
  3024. {
  3025. return (bool) (
  3026. ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
  3027. && ( (string) $this->__info['type'] === (string) $bean->__info['type'] )
  3028. );
  3029. }
  3030.  
  3031. /**
  3032. * Magic method jsonSerialize, implementation for the \JsonSerializable interface,
  3033. * this method gets called by json_encode and facilitates a better JSON representation
  3034. * of the bean. Exports the bean on JSON serialization, for the JSON fans.
  3035. *
  3036. * @see http://php.net/manual/en/class.jsonserializable.php
  3037. *
  3038. * @return array
  3039. */
  3040. public function jsonSerialize()
  3041. {
  3042. return $this->export();
  3043. }
  3044. }
  3045. }
  3046.  
  3047. namespace RedBeanPHP {
  3048.  
  3049. use RedBeanPHP\Observer as Observer;
  3050.  
  3051. /**
  3052. * Observable
  3053. * Base class for Observables
  3054. *
  3055. * @file RedBeanPHP/Observable.php
  3056. * @author Gabor de Mooij and the RedBeanPHP community
  3057. * @license BSD/GPLv2
  3058. *
  3059. * @copyright
  3060. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3061. * This source file is subject to the BSD/GPLv2 License that is bundled
  3062. * with this source code in the file license.txt.
  3063. */
  3064. abstract class Observable { //bracket must be here - otherwise coverage software does not understand.
  3065.  
  3066. /**
  3067. * @var array
  3068. */
  3069. private $observers = array();
  3070.  
  3071. /**
  3072. * Implementation of the Observer Pattern.
  3073. * Adds an event listener to the observable object.
  3074. * First argument should be the name of the event you wish to listen for.
  3075. * Second argument should be the object that wants to be notified in case
  3076. * the event occurs.
  3077. *
  3078. * @param string $eventname event identifier
  3079. * @param Observer $observer observer instance
  3080. *
  3081. * @return void
  3082. */
  3083. public function addEventListener( $eventname, Observer $observer )
  3084. {
  3085. if ( !isset( $this->observers[$eventname] ) ) {
  3086. $this->observers[$eventname] = array();
  3087. }
  3088.  
  3089. foreach ( $this->observers[$eventname] as $o ) {
  3090. if ( $o == $observer ) {
  3091. return;
  3092. }
  3093. }
  3094.  
  3095. $this->observers[$eventname][] = $observer;
  3096. }
  3097.  
  3098. /**
  3099. * Notifies listeners.
  3100. * Sends the signal $eventname, the event identifier and a message object
  3101. * to all observers that have been registered to receive notification for
  3102. * this event. Part of the observer pattern implementation in RedBeanPHP.
  3103. *
  3104. * @param string $eventname event you want signal
  3105. * @param mixed $info message object to send along
  3106. *
  3107. * @return void
  3108. */
  3109. public function signal( $eventname, $info )
  3110. {
  3111. if ( !isset( $this->observers[$eventname] ) ) {
  3112. $this->observers[$eventname] = array();
  3113. }
  3114.  
  3115. foreach ( $this->observers[$eventname] as $observer ) {
  3116. $observer->onEvent( $eventname, $info );
  3117. }
  3118. }
  3119. }
  3120. }
  3121.  
  3122. namespace RedBeanPHP {
  3123.  
  3124. /**
  3125. * Observer.
  3126. *
  3127. * Interface for Observer object. Implementation of the
  3128. * observer pattern.
  3129. *
  3130. * @file RedBeanPHP/Observer.php
  3131. * @author Gabor de Mooij and the RedBeanPHP community
  3132. * @license BSD/GPLv2
  3133. * @desc Part of the observer pattern in RedBean
  3134. *
  3135. * @copyright
  3136. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3137. * This source file is subject to the BSD/GPLv2 License that is bundled
  3138. * with this source code in the file license.txt.
  3139. */
  3140. interface Observer
  3141. {
  3142. /**
  3143. * An observer object needs to be capable of receiving
  3144. * notifications. Therefore the observer needs to implement the
  3145. * onEvent method with two parameters: the event identifier specifying the
  3146. * current event and a message object (in RedBeanPHP this can also be a bean).
  3147. *
  3148. * @param string $eventname event identifier
  3149. * @param mixed $bean a message sent along with the notification
  3150. *
  3151. * @return void
  3152. */
  3153. public function onEvent( $eventname, $bean );
  3154. }
  3155. }
  3156.  
  3157. namespace RedBeanPHP {
  3158.  
  3159. /**
  3160. * Adapter Interface.
  3161. * Describes the API for a RedBeanPHP Database Adapter.
  3162. * This interface defines the API contract for
  3163. * a RedBeanPHP Database Adapter.
  3164. *
  3165. * @file RedBeanPHP/Adapter.php
  3166. * @author Gabor de Mooij and the RedBeanPHP Community
  3167. * @license BSD/GPLv2
  3168. *
  3169. * @copyright
  3170. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3171. * This source file is subject to the BSD/GPLv2 License that is bundled
  3172. * with this source code in the file license.txt.
  3173. */
  3174. interface Adapter
  3175. {
  3176. /**
  3177. * Returns the latest SQL statement.
  3178. *
  3179. * @return string
  3180. */
  3181. public function getSQL();
  3182.  
  3183. /**
  3184. * Executes an SQL Statement using an array of values to bind
  3185. * If $noevent is TRUE then this function will not signal its
  3186. * observers to notify about the SQL execution; this to prevent
  3187. * infinite recursion when using observers.
  3188. *
  3189. * @param string $sql string containing SQL code for database
  3190. * @param array $bindings array of values to bind to parameters in query string
  3191. * @param boolean $noevent no event firing
  3192. *
  3193. * @return void
  3194. */
  3195. public function exec( $sql, $bindings = array(), $noevent = FALSE );
  3196.  
  3197. /**
  3198. * Executes an SQL Query and returns a resultset.
  3199. * This method returns a multi dimensional resultset similar to getAll
  3200. * The values array can be used to bind values to the place holders in the
  3201. * SQL query.
  3202. *
  3203. * @param string $sql string containing SQL code for database
  3204. * @param array $bindings array of values to bind to parameters in query string
  3205. *
  3206. * @return array
  3207. */
  3208. public function get( $sql, $bindings = array() );
  3209.  
  3210. /**
  3211. * Executes an SQL Query and returns a resultset.
  3212. * This method returns a single row (one array) resultset.
  3213. * The values array can be used to bind values to the place holders in the
  3214. * SQL query.
  3215. *
  3216. * @param string $sql string containing SQL code for database
  3217. * @param array $bindings array of values to bind to parameters in query string
  3218. *
  3219. * @return array
  3220. */
  3221. public function getRow( $sql, $bindings = array() );
  3222.  
  3223. /**
  3224. * Executes an SQL Query and returns a resultset.
  3225. * This method returns a single column (one array) resultset.
  3226. * The values array can be used to bind values to the place holders in the
  3227. * SQL query.
  3228. *
  3229. * @param string $sql string containing SQL code for database
  3230. * @param array $bindings array of values to bind to parameters in query string
  3231. *
  3232. * @return array
  3233. */
  3234. public function getCol( $sql, $bindings = array() );
  3235.  
  3236. /**
  3237. * Executes an SQL Query and returns a resultset.
  3238. * This method returns a single cell, a scalar value as the resultset.
  3239. * The values array can be used to bind values to the place holders in the
  3240. * SQL query.
  3241. *
  3242. * @param string $sql string containing SQL code for database
  3243. * @param array $bindings array of values to bind to parameters in query string
  3244. *
  3245. * @return string
  3246. */
  3247. public function getCell( $sql, $bindings = array() );
  3248.  
  3249. /**
  3250. * Executes the SQL query specified in $sql and takes
  3251. * the first two columns of the resultset. This function transforms the
  3252. * resultset into an associative array. Values from the the first column will
  3253. * serve as keys while the values of the second column will be used as values.
  3254. * The values array can be used to bind values to the place holders in the
  3255. * SQL query.
  3256. *
  3257. * @param string $sql string containing SQL code for database
  3258. * @param array $bindings array of values to bind to parameters in query string
  3259. *
  3260. * @return array
  3261. */
  3262. public function getAssoc( $sql, $bindings = array() );
  3263.  
  3264. /**
  3265. * Executes the SQL query specified in $sql and indexes
  3266. * the row by the first column.
  3267. *
  3268. * @param string $sql Sstring containing SQL code for databaseQL
  3269. * @param array $bindings values to bind
  3270. *
  3271. * @return array
  3272. */
  3273. public function getAssocRow( $sql, $bindings = array() );
  3274.  
  3275. /**
  3276. * Returns the latest insert ID.
  3277. *
  3278. * @return integer
  3279. */
  3280. public function getInsertID();
  3281.  
  3282. /**
  3283. * Returns the number of rows that have been
  3284. * affected by the last update statement.
  3285. *
  3286. * @return integer
  3287. */
  3288. public function getAffectedRows();
  3289.  
  3290. /**
  3291. * Returns a database agnostic Cursor object.
  3292. *
  3293. * @param string $sql string containing SQL code for database
  3294. * @param array $bindings array of values to bind to parameters in query string
  3295. *
  3296. * @return Cursor
  3297. */
  3298. public function getCursor( $sql, $bindings = array() );
  3299.  
  3300. /**
  3301. * Returns the original database resource. This is useful if you want to
  3302. * perform operations on the driver directly instead of working with the
  3303. * adapter. RedBean will only access the adapter and never to talk
  3304. * directly to the driver though.
  3305. *
  3306. * @return mixed
  3307. */
  3308. public function getDatabase();
  3309.  
  3310. /**
  3311. * This method is part of the RedBean Transaction Management
  3312. * mechanisms.
  3313. * Starts a transaction.
  3314. *
  3315. * @return void
  3316. */
  3317. public function startTransaction();
  3318.  
  3319. /**
  3320. * This method is part of the RedBean Transaction Management
  3321. * mechanisms.
  3322. * Commits the transaction.
  3323. *
  3324. * @return void
  3325. */
  3326. public function commit();
  3327.  
  3328. /**
  3329. * This method is part of the RedBean Transaction Management
  3330. * mechanisms.
  3331. * Rolls back the transaction.
  3332. *
  3333. * @return void
  3334. */
  3335. public function rollback();
  3336.  
  3337. /**
  3338. * Closes database connection.
  3339. *
  3340. * @return void
  3341. */
  3342. public function close();
  3343. }
  3344. }
  3345.  
  3346. namespace RedBeanPHP\Adapter {
  3347.  
  3348. use RedBeanPHP\Observable as Observable;
  3349. use RedBeanPHP\Adapter as Adapter;
  3350. use RedBeanPHP\Driver as Driver;
  3351.  
  3352. /**
  3353. * DBAdapter (Database Adapter)
  3354. *
  3355. * An adapter class to connect various database systems to RedBean
  3356. * Database Adapter Class. The task of the database adapter class is to
  3357. * communicate with the database driver. You can use all sorts of database
  3358. * drivers with RedBeanPHP. The default database drivers that ships with
  3359. * the RedBeanPHP library is the RPDO driver ( which uses the PHP Data Objects
  3360. * Architecture aka PDO ).
  3361. *
  3362. * @file RedBeanPHP/Adapter/DBAdapter.php
  3363. * @author Gabor de Mooij and the RedBeanPHP Community.
  3364. * @license BSD/GPLv2
  3365. *
  3366. * @copyright
  3367. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community.
  3368. * This source file is subject to the BSD/GPLv2 License that is bundled
  3369. * with this source code in the file license.txt.
  3370. */
  3371. class DBAdapter extends Observable implements Adapter
  3372. {
  3373. /**
  3374. * @var Driver
  3375. */
  3376. private $db = NULL;
  3377.  
  3378. /**
  3379. * @var string
  3380. */
  3381. private $sql = '';
  3382.  
  3383. /**
  3384. * Constructor.
  3385. *
  3386. * Creates an instance of the RedBean Adapter Class.
  3387. * This class provides an interface for RedBean to work
  3388. * with ADO compatible DB instances.
  3389. *
  3390. * @param Driver $database ADO Compatible DB Instance
  3391. */
  3392. public function __construct( $database )
  3393. {
  3394. $this->db = $database;
  3395. }
  3396.  
  3397. /**
  3398. * @see Adapter::getSQL
  3399. */
  3400. public function getSQL()
  3401. {
  3402. return $this->sql;
  3403. }
  3404.  
  3405. /**
  3406. * @see Adapter::exec
  3407. */
  3408. public function exec( $sql, $bindings = array(), $noevent = FALSE )
  3409. {
  3410. if ( !$noevent ) {
  3411. $this->sql = $sql;
  3412. $this->signal( 'sql_exec', $this );
  3413. }
  3414.  
  3415. return $this->db->Execute( $sql, $bindings );
  3416. }
  3417.  
  3418. /**
  3419. * @see Adapter::get
  3420. */
  3421. public function get( $sql, $bindings = array() )
  3422. {
  3423. $this->sql = $sql;
  3424. $this->signal( 'sql_exec', $this );
  3425.  
  3426. return $this->db->GetAll( $sql, $bindings );
  3427. }
  3428.  
  3429. /**
  3430. * @see Adapter::getRow
  3431. */
  3432. public function getRow( $sql, $bindings = array() )
  3433. {
  3434. $this->sql = $sql;
  3435. $this->signal( 'sql_exec', $this );
  3436.  
  3437. return $this->db->GetRow( $sql, $bindings );
  3438. }
  3439.  
  3440. /**
  3441. * @see Adapter::getCol
  3442. */
  3443. public function getCol( $sql, $bindings = array() )
  3444. {
  3445. $this->sql = $sql;
  3446. $this->signal( 'sql_exec', $this );
  3447.  
  3448. return $this->db->GetCol( $sql, $bindings );
  3449. }
  3450.  
  3451. /**
  3452. * @see Adapter::getAssoc
  3453. */
  3454. public function getAssoc( $sql, $bindings = array() )
  3455. {
  3456. $this->sql = $sql;
  3457.  
  3458. $this->signal( 'sql_exec', $this );
  3459.  
  3460. $rows = $this->db->GetAll( $sql, $bindings );
  3461.  
  3462. $assoc = array();
  3463. if ( !$rows ) {
  3464. return $assoc;
  3465. }
  3466.  
  3467. foreach ( $rows as $row ) {
  3468. if ( empty( $row ) ) continue;
  3469.  
  3470. if ( count( $row ) > 2 ) {
  3471. $key = array_shift( $row );
  3472. $value = $row;
  3473. } elseif ( count( $row ) > 1 ) {
  3474. $key = array_shift( $row );
  3475. $value = array_shift( $row );
  3476. } else {
  3477. $key = array_shift( $row );
  3478. $value = $key;
  3479. }
  3480.  
  3481. $assoc[$key] = $value;
  3482. }
  3483.  
  3484. return $assoc;
  3485. }
  3486.  
  3487. /**
  3488. * @see Adapter::getAssocRow
  3489. */
  3490. public function getAssocRow($sql, $bindings = array())
  3491. {
  3492. $this->sql = $sql;
  3493. $this->signal( 'sql_exec', $this );
  3494.  
  3495. return $this->db->GetAssocRow( $sql, $bindings );
  3496. }
  3497.  
  3498. /**
  3499. * @see Adapter::getCell
  3500. */
  3501. public function getCell( $sql, $bindings = array(), $noSignal = NULL )
  3502. {
  3503. $this->sql = $sql;
  3504.  
  3505. if ( !$noSignal ) $this->signal( 'sql_exec', $this );
  3506.  
  3507. return $this->db->GetOne( $sql, $bindings );
  3508. }
  3509.  
  3510. /**
  3511. * @see Adapter::getCursor
  3512. */
  3513. public function getCursor( $sql, $bindings = array() )
  3514. {
  3515. return $this->db->GetCursor( $sql, $bindings );
  3516. }
  3517.  
  3518. /**
  3519. * @see Adapter::getInsertID
  3520. */
  3521. public function getInsertID()
  3522. {
  3523. return $this->db->getInsertID();
  3524. }
  3525.  
  3526. /**
  3527. * @see Adapter::getAffectedRows
  3528. */
  3529. public function getAffectedRows()
  3530. {
  3531. return $this->db->Affected_Rows();
  3532. }
  3533.  
  3534. /**
  3535. * @see Adapter::getDatabase
  3536. */
  3537. public function getDatabase()
  3538. {
  3539. return $this->db;
  3540. }
  3541.  
  3542. /**
  3543. * @see Adapter::startTransaction
  3544. */
  3545. public function startTransaction()
  3546. {
  3547. $this->db->StartTrans();
  3548. }
  3549.  
  3550. /**
  3551. * @see Adapter::commit
  3552. */
  3553. public function commit()
  3554. {
  3555. $this->db->CommitTrans();
  3556. }
  3557.  
  3558. /**
  3559. * @see Adapter::rollback
  3560. */
  3561. public function rollback()
  3562. {
  3563. $this->db->FailTrans();
  3564. }
  3565.  
  3566. /**
  3567. * @see Adapter::close.
  3568. */
  3569. public function close()
  3570. {
  3571. $this->db->close();
  3572. }
  3573. }
  3574. }
  3575.  
  3576. namespace RedBeanPHP {
  3577.  
  3578. /**
  3579. * Database Cursor Interface.
  3580. * Represents a simple database cursor.
  3581. * Cursors make it possible to create lightweight BeanCollections.
  3582. *
  3583. * @file RedBeanPHP/Cursor.php
  3584. * @author Gabor de Mooij and the RedBeanPHP Community
  3585. * @license BSD/GPLv2
  3586. *
  3587. * @copyright
  3588. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  3589. * This source file is subject to the BSD/GPLv2 License that is bundled
  3590. * with this source code in the file license.txt.
  3591. */
  3592. interface Cursor
  3593. {
  3594. /**
  3595. * Retrieves the next row from the result set.
  3596. *
  3597. * @return array
  3598. */
  3599. public function getNextItem();
  3600.  
  3601. /**
  3602. * Closes the database cursor.
  3603. * Some databases require a cursor to be closed before executing
  3604. * another statement/opening a new cursor.
  3605. *
  3606. * @return void
  3607. */
  3608. public function close();
  3609. }
  3610. }
  3611.  
  3612. namespace RedBeanPHP\Cursor {
  3613.  
  3614. use RedBeanPHP\Cursor as Cursor;
  3615.  
  3616. /**
  3617. * PDO Database Cursor
  3618. * Implementation of PDO Database Cursor.
  3619. * Used by the BeanCollection to fetch one bean at a time.
  3620. *
  3621. * @file RedBeanPHP/Cursor/PDOCursor.php
  3622. * @author Gabor de Mooij and the RedBeanPHP Community
  3623. * @license BSD/GPLv2
  3624. *
  3625. * @copyright
  3626. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3627. * This source file is subject to the BSD/GPLv2 License that is bundled
  3628. * with this source code in the file license.txt.
  3629. */
  3630. class PDOCursor implements Cursor
  3631. {
  3632. /**
  3633. * @var PDOStatement
  3634. */
  3635. protected $res;
  3636.  
  3637. /**
  3638. * @var string
  3639. */
  3640. protected $fetchStyle;
  3641.  
  3642. /**
  3643. * Constructor, creates a new instance of a PDO Database Cursor.
  3644. *
  3645. * @param PDOStatement $res the PDO statement
  3646. * @param string $fetchStyle fetch style constant to use
  3647. *
  3648. * @return void
  3649. */
  3650. public function __construct( \PDOStatement $res, $fetchStyle )
  3651. {
  3652. $this->res = $res;
  3653. $this->fetchStyle = $fetchStyle;
  3654. }
  3655.  
  3656. /**
  3657. * @see Cursor::getNextItem
  3658. */
  3659. public function getNextItem()
  3660. {
  3661. return $this->res->fetch();
  3662. }
  3663.  
  3664. /**
  3665. * @see Cursor::close
  3666. */
  3667. public function close()
  3668. {
  3669. $this->res->closeCursor();
  3670. }
  3671. }
  3672. }
  3673.  
  3674. namespace RedBeanPHP\Cursor {
  3675.  
  3676. use RedBeanPHP\Cursor as Cursor;
  3677.  
  3678. /**
  3679. * NULL Database Cursor
  3680. * Implementation of the NULL Cursor.
  3681. * Used for an empty BeanCollection.
  3682. *
  3683. * @file RedBeanPHP/Cursor/NULLCursor.php
  3684. * @author Gabor de Mooij and the RedBeanPHP Community
  3685. * @license BSD/GPLv2
  3686. *
  3687. * @copyright
  3688. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3689. * This source file is subject to the BSD/GPLv2 License that is bundled
  3690. * with this source code in the file license.txt.
  3691. */
  3692. class NullCursor implements Cursor
  3693. {
  3694. /**
  3695. * @see Cursor::getNextItem
  3696. */
  3697. public function getNextItem()
  3698. {
  3699. return NULL;
  3700. }
  3701.  
  3702. /**
  3703. * @see Cursor::close
  3704. */
  3705. public function close()
  3706. {
  3707. return NULL;
  3708. }
  3709. }
  3710. }
  3711.  
  3712. namespace RedBeanPHP {
  3713.  
  3714. use RedBeanPHP\Cursor as Cursor;
  3715. use RedBeanPHP\Repository as Repository;
  3716.  
  3717. /**
  3718. * BeanCollection.
  3719. *
  3720. * The BeanCollection represents a collection of beans and
  3721. * makes it possible to use database cursors. The BeanCollection
  3722. * has a method next() to obtain the first, next and last bean
  3723. * in the collection. The BeanCollection does not implement the array
  3724. * interface nor does it try to act like an array because it cannot go
  3725. * backward or rewind itself.
  3726. *
  3727. * Use the BeanCollection for large datasets where skip/limit is not an
  3728. * option. Keep in mind that ID-marking (querying a start ID) is a decent
  3729. * alternative though.
  3730. *
  3731. * @file RedBeanPHP/BeanCollection.php
  3732. * @author Gabor de Mooij and the RedBeanPHP community
  3733. * @license BSD/GPLv2
  3734. *
  3735. * @copyright
  3736. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  3737. * This source file is subject to the BSD/GPLv2 License that is bundled
  3738. * with this source code in the file license.txt.
  3739. */
  3740. class BeanCollection
  3741. {
  3742. /**
  3743. * @var Cursor
  3744. */
  3745. protected $cursor = NULL;
  3746.  
  3747. /**
  3748. * @var Repository
  3749. */
  3750. protected $repository = NULL;
  3751.  
  3752. /**
  3753. * @var string
  3754. */
  3755. protected $type = NULL;
  3756.  
  3757. /**
  3758. * Constructor, creates a new instance of the BeanCollection.
  3759. *
  3760. * @param string $type type of beans in this collection
  3761. * @param Repository $repository repository to use to generate bean objects
  3762. * @param Cursor $cursor cursor object to use
  3763. *
  3764. * @return void
  3765. */
  3766. public function __construct( $type, Repository $repository, Cursor $cursor )
  3767. {
  3768. $this->type = $type;
  3769. $this->cursor = $cursor;
  3770. $this->repository = $repository;
  3771. }
  3772.  
  3773. /**
  3774. * Returns the next bean in the collection.
  3775. * If called the first time, this will return the first bean in the collection.
  3776. * If there are no more beans left in the collection, this method
  3777. * will return NULL.
  3778. *
  3779. * @return OODBBean|NULL
  3780. */
  3781. public function next()
  3782. {
  3783. $row = $this->cursor->getNextItem();
  3784. if ( $row ) {
  3785. $beans = $this->repository->convertToBeans( $this->type, array( $row ) );
  3786. $bean = array_shift( $beans );
  3787. return $bean;
  3788. }
  3789. return NULL;
  3790. }
  3791.  
  3792. /**
  3793. * Closes the underlying cursor (needed for some databases).
  3794. *
  3795. * @return void
  3796. */
  3797. public function close()
  3798. {
  3799. $this->cursor->close();
  3800. }
  3801. }
  3802. }
  3803.  
  3804. namespace RedBeanPHP {
  3805.  
  3806. /**
  3807. * QueryWriter
  3808. * Interface for QueryWriters.
  3809. * Describes the API for a QueryWriter.
  3810. *
  3811. * Terminology:
  3812. *
  3813. * - beautified property (a camelCased property, has to be converted first)
  3814. * - beautified type (a camelCased type, has to be converted first)
  3815. * - type (a bean type, corresponds directly to a table)
  3816. * - property (a bean property, corresponds directly to a column)
  3817. * - table (a checked and quoted type, ready for use in a query)
  3818. * - column (a checked and quoted property, ready for use in query)
  3819. * - tableNoQ (same as type, but in context of a database operation)
  3820. * - columnNoQ (same as property, but in context of a database operation)
  3821. *
  3822. * @file RedBeanPHP/QueryWriter.php
  3823. * @author Gabor de Mooij and the RedBeanPHP community
  3824. * @license BSD/GPLv2
  3825. *
  3826. * @copyright
  3827. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3828. * This source file is subject to the BSD/GPLv2 License that is bundled
  3829. * with this source code in the file license.txt.
  3830. */
  3831. interface QueryWriter
  3832. {
  3833. /**
  3834. * SQL filter constants
  3835. */
  3836. const C_SQLFILTER_READ = 'r';
  3837. const C_SQLFILTER_WRITE = 'w';
  3838.  
  3839. /**
  3840. * Query Writer constants.
  3841. */
  3842. const C_SQLSTATE_NO_SUCH_TABLE = 1;
  3843. const C_SQLSTATE_NO_SUCH_COLUMN = 2;
  3844. const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;
  3845.  
  3846. /**
  3847. * Define data type regions
  3848. *
  3849. * 00 - 80: normal data types
  3850. * 80 - 99: special data types, only scan/code if requested
  3851. * 99 : specified by user, don't change
  3852. */
  3853. const C_DATATYPE_RANGE_SPECIAL = 80;
  3854. const C_DATATYPE_RANGE_SPECIFIED = 99;
  3855.  
  3856. /**
  3857. * Define GLUE types for use with glueSQLCondition methods.
  3858. * Determines how to prefix a snippet of SQL before appending it
  3859. * to other SQL (or integrating it, mixing it otherwise).
  3860. *
  3861. * WHERE - glue as WHERE condition
  3862. * AND - glue as AND condition
  3863. */
  3864. const C_GLUE_WHERE = 1;
  3865. const C_GLUE_AND = 2;
  3866.  
  3867. /**
  3868. * Writes an SQL Snippet for a JOIN, returns the
  3869. * SQL snippet string.
  3870. *
  3871. * @note A default implementation is available in AQueryWriter
  3872. * unless a database uses very different SQL this should suffice.
  3873. *
  3874. * @param string $type source type
  3875. * @param string $targetType target type (type to join)
  3876. * @param string $leftRight type of join (possible: 'LEFT', 'RIGHT' or 'INNER').
  3877. *
  3878. * @return string $joinSQLSnippet
  3879. */
  3880. public function writeJoin( $type, $targetType, $joinType );
  3881.  
  3882. /**
  3883. * Glues an SQL snippet to the beginning of a WHERE clause.
  3884. * This ensures users don't have to add WHERE to their query snippets.
  3885. *
  3886. * The snippet gets prefixed with WHERE or AND
  3887. * if it starts with a condition.
  3888. *
  3889. * If the snippet does NOT start with a condition (or this function thinks so)
  3890. * the snippet is returned as-is.
  3891. *
  3892. * The GLUE type determines the prefix:
  3893. *
  3894. * * NONE prefixes with WHERE
  3895. * * WHERE prefixes with WHERE and replaces AND if snippets starts with AND
  3896. * * AND prefixes with AND
  3897. *
  3898. * This method will never replace WHERE with AND since a snippet should never
  3899. * begin with WHERE in the first place. OR is not supported.
  3900. *
  3901. * Only a limited set of clauses will be recognized as non-conditions.
  3902. * For instance beginning a snippet with complex statements like JOIN or UNION
  3903. * will not work. This is too complex for use in a snippet.
  3904. *
  3905. * @note A default implementation is available in AQueryWriter
  3906. * unless a database uses very different SQL this should suffice.
  3907. *
  3908. * @param string $sql SQL Snippet
  3909. * @param integer $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND)
  3910. *
  3911. * @return string
  3912. */
  3913. public function glueSQLCondition( $sql, $glue = NULL );
  3914.  
  3915. /**
  3916. * Determines if there is a LIMIT 1 clause in the SQL.
  3917. * If not, it will add a LIMIT 1. (used for findOne).
  3918. *
  3919. * @note A default implementation is available in AQueryWriter
  3920. * unless a database uses very different SQL this should suffice.
  3921. *
  3922. * @param string $sql query to scan and adjust
  3923. *
  3924. * @return string
  3925. */
  3926. public function glueLimitOne( $sql );
  3927.  
  3928. /**
  3929. * Returns the tables that are in the database.
  3930. *
  3931. * @return array
  3932. */
  3933. public function getTables();
  3934.  
  3935. /**
  3936. * This method will create a table for the bean.
  3937. * This methods accepts a type and infers the corresponding table name.
  3938. *
  3939. * @param string $type type of bean you want to create a table for
  3940. *
  3941. * @return void
  3942. */
  3943. public function createTable( $type );
  3944.  
  3945. /**
  3946. * Returns an array containing all the columns of the specified type.
  3947. * The format of the return array looks like this:
  3948. * $field => $type where $field is the name of the column and $type
  3949. * is a database specific description of the datatype.
  3950. *
  3951. * This methods accepts a type and infers the corresponding table name.
  3952. *
  3953. * @param string $type type of bean you want to obtain a column list of
  3954. *
  3955. * @return array
  3956. */
  3957. public function getColumns( $type );
  3958.  
  3959. /**
  3960. * Returns the Column Type Code (integer) that corresponds
  3961. * to the given value type. This method is used to determine the minimum
  3962. * column type required to represent the given value.
  3963. *
  3964. * @param string $value value
  3965. *
  3966. * @return integer
  3967. */
  3968. public function scanType( $value, $alsoScanSpecialForTypes = FALSE );
  3969.  
  3970. /**
  3971. * This method will add a column to a table.
  3972. * This methods accepts a type and infers the corresponding table name.
  3973. *
  3974. * @param string $type name of the table
  3975. * @param string $column name of the column
  3976. * @param integer $field data type for field
  3977. *
  3978. * @return void
  3979. */
  3980. public function addColumn( $type, $column, $field );
  3981.  
  3982. /**
  3983. * Returns the Type Code for a Column Description.
  3984. * Given an SQL column description this method will return the corresponding
  3985. * code for the writer. If the include specials flag is set it will also
  3986. * return codes for special columns. Otherwise special columns will be identified
  3987. * as specified columns.
  3988. *
  3989. * @param string $typedescription description
  3990. * @param boolean $includeSpecials whether you want to get codes for special columns as well
  3991. *
  3992. * @return integer
  3993. */
  3994. public function code( $typedescription, $includeSpecials = FALSE );
  3995.  
  3996. /**
  3997. * This method will widen the column to the specified data type.
  3998. * This methods accepts a type and infers the corresponding table name.
  3999. *
  4000. * @param string $type type / table that needs to be adjusted
  4001. * @param string $column column that needs to be altered
  4002. * @param integer $datatype target data type
  4003. *
  4004. * @return void
  4005. */
  4006. public function widenColumn( $type, $column, $datatype );
  4007.  
  4008. /**
  4009. * Selects records from the database.
  4010. * This methods selects the records from the database that match the specified
  4011. * type, conditions (optional) and additional SQL snippet (optional).
  4012. *
  4013. * @param string $type name of the table you want to query
  4014. * @param array $conditions criteria ( $column => array( $values ) )
  4015. * @param string $addSQL additional SQL snippet
  4016. * @param array $bindings bindings for SQL snippet
  4017. *
  4018. * @return array
  4019. */
  4020. public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
  4021.  
  4022. /**
  4023. * Selects records from the database and returns a cursor.
  4024. * This methods selects the records from the database that match the specified
  4025. * type, conditions (optional) and additional SQL snippet (optional).
  4026. *
  4027. * @param string $type name of the table you want to query
  4028. * @param array $conditions criteria ( $column => array( $values ) )
  4029. * @param string $addSQL additional SQL snippet
  4030. * @param array $bindings bindings for SQL snippet
  4031. *
  4032. * @return Cursor
  4033. */
  4034. public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() );
  4035.  
  4036. /**
  4037. * Returns records through an intermediate type. This method is used to obtain records using a link table and
  4038. * allows the SQL snippets to reference columns in the link table for additional filtering or ordering.
  4039. *
  4040. * @param string $sourceType source type, the reference type you want to use to fetch related items on the other side
  4041. * @param string $destType destination type, the target type you want to get beans of
  4042. * @param mixed $linkID ID to use for the link table
  4043. * @param string $addSql Additional SQL snippet
  4044. * @param array $bindings Bindings for SQL snippet
  4045. *
  4046. * @return array
  4047. */
  4048. public function queryRecordRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() );
  4049.  
  4050. /**
  4051. * Returns the row that links $sourceType $sourcID to $destType $destID in an N-M relation.
  4052. *
  4053. * @param string $sourceType source type, the first part of the link you're looking for
  4054. * @param string $destType destination type, the second part of the link you're looking for
  4055. * @param string $sourceID ID for the source
  4056. * @param string $destID ID for the destination
  4057. *
  4058. * @return array|null
  4059. */
  4060. public function queryRecordLink( $sourceType, $destType, $sourceID, $destID );
  4061.  
  4062. /**
  4063. * Counts the number of records in the database that match the
  4064. * conditions and additional SQL.
  4065. *
  4066. * @param string $type name of the table you want to query
  4067. * @param array $conditions criteria ( $column => array( $values ) )
  4068. * @param string $addSQL additional SQL snippet
  4069. * @param array $bindings bindings for SQL snippet
  4070. *
  4071. * @return integer
  4072. */
  4073. public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
  4074.  
  4075. /**
  4076. * Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$bindings.
  4077. *
  4078. * @param string $sourceType source type
  4079. * @param string $targetType the thing you want to count
  4080. * @param mixed $linkID the of the source type
  4081. * @param string $addSQL additional SQL snippet
  4082. * @param array $bindings bindings for SQL snippet
  4083. *
  4084. * @return integer
  4085. */
  4086. public function queryRecordCountRelated( $sourceType, $targetType, $linkID, $addSQL = '', $bindings = array() );
  4087.  
  4088. /**
  4089. * Returns all rows of specified type that have been tagged with one of the
  4090. * strings in the specified tag list array.
  4091. *
  4092. * Note that the additional SQL snippet can only be used for pagination,
  4093. * the SQL snippet will be appended to the end of the query.
  4094. *
  4095. * @param string $type the bean type you want to query
  4096. * @param array $tagList an array of strings, each string containing a tag title
  4097. * @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
  4098. * @param string $addSql addition SQL snippet, for pagination
  4099. * @param array $bindings parameter bindings for additional SQL snippet
  4100. *
  4101. * @return array
  4102. */
  4103. public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
  4104.  
  4105. /**
  4106. * This method should update (or insert a record), it takes
  4107. * a table name, a list of update values ( $field => $value ) and an
  4108. * primary key ID (optional). If no primary key ID is provided, an
  4109. * INSERT will take place.
  4110. * Returns the new ID.
  4111. * This methods accepts a type and infers the corresponding table name.
  4112. *
  4113. * @param string $type name of the table to update
  4114. * @param array $updatevalues list of update values
  4115. * @param integer $id optional primary key ID value
  4116. *
  4117. * @return integer
  4118. */
  4119. public function updateRecord( $type, $updatevalues, $id = NULL );
  4120.  
  4121. /**
  4122. * Deletes records from the database.
  4123. * @note $addSql is always prefixed with ' WHERE ' or ' AND .'
  4124. *
  4125. * @param string $type name of the table you want to query
  4126. * @param array $conditions criteria ( $column => array( $values ) )
  4127. * @param string $sql additional SQL
  4128. * @param array $bindings bindings
  4129. *
  4130. * @return void
  4131. */
  4132. public function deleteRecord( $type, $conditions = array(), $addSql = '', $bindings = array() );
  4133.  
  4134. /**
  4135. * Deletes all links between $sourceType and $destType in an N-M relation.
  4136. *
  4137. * @param string $sourceType source type
  4138. * @param string $destType destination type
  4139. * @param string $sourceID source ID
  4140. *
  4141. * @return void
  4142. */
  4143. public function deleteRelations( $sourceType, $destType, $sourceID );
  4144.  
  4145. /**
  4146. * @see QueryWriter::addUniqueConstaint
  4147. */
  4148. public function addUniqueIndex( $type, $columns );
  4149.  
  4150. /**
  4151. * This method will add a UNIQUE constraint index to a table on columns $columns.
  4152. * This methods accepts a type and infers the corresponding table name.
  4153. *
  4154. * @param string $type target bean type
  4155. * @param array $columnsPartOfIndex columns to include in index
  4156. *
  4157. * @return void
  4158. */
  4159. public function addUniqueConstraint( $type, $columns );
  4160.  
  4161. /**
  4162. * This method will check whether the SQL state is in the list of specified states
  4163. * and returns TRUE if it does appear in this list or FALSE if it
  4164. * does not. The purpose of this method is to translate the database specific state to
  4165. * a one of the constants defined in this class and then check whether it is in the list
  4166. * of standard states provided.
  4167. *
  4168. * @param string $state SQL state to consider
  4169. * @param array $list list of standardized SQL state constants to check against
  4170. *
  4171. * @return boolean
  4172. */
  4173. public function sqlStateIn( $state, $list );
  4174.  
  4175. /**
  4176. * This method will remove all beans of a certain type.
  4177. * This methods accepts a type and infers the corresponding table name.
  4178. *
  4179. * @param string $type bean type
  4180. *
  4181. * @return void
  4182. */
  4183. public function wipe( $type );
  4184.  
  4185. /**
  4186. * This method will add a foreign key from type and field to
  4187. * target type and target field.
  4188. * The foreign key is created without an action. On delete/update
  4189. * no action will be triggered. The FK is only used to allow database
  4190. * tools to generate pretty diagrams and to make it easy to add actions
  4191. * later on.
  4192. * This methods accepts a type and infers the corresponding table name.
  4193. *
  4194. *
  4195. * @param string $type type that will have a foreign key field
  4196. * @param string $targetType points to this type
  4197. * @param string $property field that contains the foreign key value
  4198. * @param string $targetProperty field where the fk points to
  4199. * @param string $isDep whether target is dependent and should cascade on update/delete
  4200. *
  4201. * @return void
  4202. */
  4203. public function addFK( $type, $targetType, $property, $targetProperty, $isDep = false );
  4204.  
  4205. /**
  4206. * This method will add an index to a type and field with name
  4207. * $name.
  4208. * This methods accepts a type and infers the corresponding table name.
  4209. *
  4210. * @param string $type type to add index to
  4211. * @param string $name name of the new index
  4212. * @param string $property field to index
  4213. *
  4214. * @return void
  4215. */
  4216. public function addIndex( $type, $name, $property );
  4217.  
  4218. /**
  4219. * Checks and filters a database structure element like a table of column
  4220. * for safe use in a query. A database structure has to conform to the
  4221. * RedBeanPHP DB security policy which basically means only alphanumeric
  4222. * symbols are allowed. This security policy is more strict than conventional
  4223. * SQL policies and does therefore not require database specific escaping rules.
  4224. *
  4225. * @param string $databaseStructure name of the column/table to check
  4226. * @param boolean $noQuotes TRUE to NOT put backticks or quotes around the string
  4227. *
  4228. * @return string
  4229. */
  4230. public function esc( $databaseStructure, $dontQuote = FALSE );
  4231.  
  4232. /**
  4233. * Removes all tables and views from the database.
  4234. *
  4235. * @return void
  4236. */
  4237. public function wipeAll();
  4238.  
  4239. /**
  4240. * Renames an association. For instance if you would like to refer to
  4241. * album_song as: track you can specify this by calling this method like:
  4242. *
  4243. * <code>
  4244. * renameAssociation('album_song','track')
  4245. * </code>
  4246. *
  4247. * This allows:
  4248. *
  4249. * <code>
  4250. * $album->sharedSong
  4251. * </code>
  4252. *
  4253. * to add/retrieve beans from track instead of album_song.
  4254. * Also works for exportAll().
  4255. *
  4256. * This method also accepts a single associative array as
  4257. * its first argument.
  4258. *
  4259. * @param string|array $fromType original type name, or array
  4260. * @param string $toType new type name (only if 1st argument is string)
  4261. *
  4262. * @return void
  4263. */
  4264. public function renameAssocTable( $fromType, $toType = NULL );
  4265.  
  4266. /**
  4267. * Returns the format for link tables.
  4268. * Given an array containing two type names this method returns the
  4269. * name of the link table to be used to store and retrieve
  4270. * association records. For instance, given two types: person and
  4271. * project, the corresponding link table might be: 'person_project'.
  4272. *
  4273. * @param array $types two types array($type1, $type2)
  4274. *
  4275. * @return string
  4276. */
  4277. public function getAssocTable( $types );
  4278.  
  4279. /**
  4280. * Given a bean type and a property, this method
  4281. * tries to infer the fetch type using the foreign key
  4282. * definitions in the database.
  4283. * For instance: project, student -> person.
  4284. * If no fetchType can be inferred, this method will return NULL.
  4285. *
  4286. * @note QueryWriters do not have to implement this method,
  4287. * it's optional. A default version is available in AQueryWriter.
  4288. *
  4289. * @param $type the source type to fetch a target type for
  4290. * @param $property the property to fetch the type of
  4291. *
  4292. * @return string|NULL
  4293. */
  4294. public function inferFetchType( $type, $property );
  4295. }
  4296. }
  4297.  
  4298. namespace RedBeanPHP\QueryWriter {
  4299.  
  4300. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  4301. use RedBeanPHP\RedException as RedException;
  4302. use RedBeanPHP\QueryWriter as QueryWriter;
  4303. use RedBeanPHP\OODBBean as OODBBean;
  4304. use RedBeanPHP\RedException\SQL as SQLException;
  4305.  
  4306. /**
  4307. * RedBeanPHP Abstract Query Writer.
  4308. * Represents an abstract Database to RedBean
  4309. * To write a driver for a different database for RedBean
  4310. * Contains a number of functions all implementors can
  4311. * inherit or override.
  4312. *
  4313. * @file RedBeanPHP/QueryWriter/AQueryWriter.php
  4314. * @author Gabor de Mooij and the RedBeanPHP Community
  4315. * @license BSD/GPLv2
  4316. *
  4317. * @copyright
  4318. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  4319. * This source file is subject to the BSD/GPLv2 License that is bundled
  4320. * with this source code in the file license.txt.
  4321. */
  4322. abstract class AQueryWriter
  4323. {
  4324. /**
  4325. * @var array
  4326. */
  4327. private static $sqlFilters = array();
  4328.  
  4329. /**
  4330. * @var boolean
  4331. */
  4332. private static $flagSQLFilterSafeMode = false;
  4333.  
  4334. /**
  4335. * @var boolean
  4336. */
  4337. private static $flagNarrowFieldMode = true;
  4338.  
  4339. /**
  4340. * @var array
  4341. */
  4342. public static $renames = array();
  4343.  
  4344. /**
  4345. * @var DBAdapter
  4346. */
  4347. protected $adapter;
  4348.  
  4349. /**
  4350. * @var string
  4351. */
  4352. protected $defaultValue = 'NULL';
  4353.  
  4354. /**
  4355. * @var string
  4356. */
  4357. protected $quoteCharacter = '';
  4358.  
  4359. /**
  4360. * @var boolean
  4361. */
  4362. protected $flagUseCache = TRUE;
  4363.  
  4364. /**
  4365. * @var array
  4366. */
  4367. protected $cache = array();
  4368.  
  4369. /**
  4370. * @var integer
  4371. */
  4372. protected $maxCacheSizePerType = 20;
  4373.  
  4374. /**
  4375. * @var array
  4376. */
  4377. public $typeno_sqltype = array();
  4378.  
  4379. /**
  4380. * Checks whether a number can be treated like an int.
  4381. *
  4382. * @param string $value string representation of a certain value
  4383. *
  4384. * @return boolean
  4385. */
  4386. public static function canBeTreatedAsInt( $value )
  4387. {
  4388. return (bool) ( strval( $value ) === strval( intval( $value ) ) );
  4389. }
  4390.  
  4391. /**
  4392. * @see QueryWriter::getAssocTableFormat
  4393. */
  4394. public static function getAssocTableFormat( $types )
  4395. {
  4396. sort( $types );
  4397.  
  4398. $assoc = implode( '_', $types );
  4399.  
  4400. return ( isset( self::$renames[$assoc] ) ) ? self::$renames[$assoc] : $assoc;
  4401. }
  4402.  
  4403. /**
  4404. * @see QueryWriter::renameAssociation
  4405. */
  4406. public static function renameAssociation( $from, $to = NULL )
  4407. {
  4408. if ( is_array( $from ) ) {
  4409. foreach ( $from as $key => $value ) self::$renames[$key] = $value;
  4410.  
  4411. return;
  4412. }
  4413.  
  4414. self::$renames[$from] = $to;
  4415. }
  4416.  
  4417. /**
  4418. * Globally available service method for RedBeanPHP.
  4419. * Converts a camel cased string to a snake cased string.
  4420. *
  4421. * @param string $camel camelCased string to converty to snake case
  4422. *
  4423. * @return string
  4424. */
  4425. public static function camelsSnake( $camel )
  4426. {
  4427. return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) );
  4428. }
  4429.  
  4430. /**
  4431. * Clears renames.
  4432. *
  4433. * @return void
  4434. */
  4435. public static function clearRenames()
  4436. {
  4437. self::$renames = array();
  4438. }
  4439.  
  4440. /**
  4441. * Toggles 'Narrow Field Mode'.
  4442. * In Narrow Field mode the queryRecord method will
  4443. * narrow its selection field to
  4444. *
  4445. * SELECT {table}.*
  4446. *
  4447. * instead of
  4448. *
  4449. * SELECT *
  4450. *
  4451. * This is a better way of querying because it allows
  4452. * more flexibility (for instance joins). However if you need
  4453. * the wide selector for backward compatibility; use this method
  4454. * to turn OFF Narrow Field Mode by passing FALSE.
  4455. *
  4456. * @param boolean $narrowField TRUE = Narrow Field FALSE = Wide Field
  4457. *
  4458. * @return void
  4459. */
  4460. public static function setNarrowFieldMode( $narrowField )
  4461. {
  4462. self::$flagNarrowFieldMode = (boolean) $narrowField;
  4463. }
  4464.  
  4465. /**
  4466. * Sets SQL filters.
  4467. * This is a lowlevel method to set the SQL filter array.
  4468. * The format of this array is:
  4469. *
  4470. * <code>
  4471. * array(
  4472. * '<MODE, i.e. 'r' for read, 'w' for write>' => array(
  4473. * '<TABLE NAME>' => array(
  4474. * '<COLUMN NAME>' => '<SQL>'
  4475. * )
  4476. * )
  4477. * )
  4478. * </code>
  4479. *
  4480. * Example:
  4481. *
  4482. * <code>
  4483. * array(
  4484. * QueryWriter::C_SQLFILTER_READ => array(
  4485. * 'book' => array(
  4486. * 'title' => ' LOWER(book.title) '
  4487. * )
  4488. * )
  4489. * </code>
  4490. *
  4491. * Note that you can use constants instead of magical chars
  4492. * as keys for the uppermost array.
  4493. * This is a lowlevel method. For a more friendly method
  4494. * please take a look at the facade: R::bindFunc().
  4495. *
  4496. * @param array list of filters to set
  4497. *
  4498. * @return void
  4499. */
  4500. public static function setSQLFilters( $sqlFilters, $safeMode = false )
  4501. {
  4502. self::$flagSQLFilterSafeMode = (boolean) $safeMode;
  4503. self::$sqlFilters = $sqlFilters;
  4504. }
  4505.  
  4506. /**
  4507. * Returns current SQL Filters.
  4508. * This method returns the raw SQL filter array.
  4509. * This is a lowlevel method. For a more friendly method
  4510. * please take a look at the facade: R::bindFunc().
  4511. *
  4512. * @return array
  4513. */
  4514. public static function getSQLFilters()
  4515. {
  4516. return self::$sqlFilters;
  4517. }
  4518.  
  4519. /**
  4520. * Returns a cache key for the cache values passed.
  4521. * This method returns a fingerprint string to be used as a key to store
  4522. * data in the writer cache.
  4523. *
  4524. * @param array $keyValues key-value to generate key for
  4525. *
  4526. * @return string
  4527. */
  4528. private function getCacheKey( $keyValues )
  4529. {
  4530. return json_encode( $keyValues );
  4531. }
  4532.  
  4533. /**
  4534. * Returns the values associated with the provided cache tag and key.
  4535. *
  4536. * @param string $cacheTag cache tag to use for lookup
  4537. * @param string $key key to use for lookup
  4538. *
  4539. * @return mixed
  4540. */
  4541. private function getCached( $cacheTag, $key )
  4542. {
  4543. $sql = $this->adapter->getSQL();
  4544.  
  4545. if ($this->updateCache()) {
  4546. if ( isset( $this->cache[$cacheTag][$key] ) ) {
  4547. return $this->cache[$cacheTag][$key];
  4548. }
  4549. }
  4550.  
  4551. return NULL;
  4552. }
  4553.  
  4554. /**
  4555. * Checks if the previous query had a keep-cache tag.
  4556. * If so, the cache will persist, otherwise the cache will be flushed.
  4557. *
  4558. * Returns TRUE if the cache will remain and FALSE if a flush has
  4559. * been performed.
  4560. *
  4561. * @return boolean
  4562. */
  4563. private function updateCache()
  4564. {
  4565. $sql = $this->adapter->getSQL();
  4566. if ( strpos( $sql, '-- keep-cache' ) !== strlen( $sql ) - 13 ) {
  4567. // If SQL has been taken place outside of this method then something else then
  4568. // a select query might have happened! (or instruct to keep cache)
  4569. $this->cache = array();
  4570. return FALSE;
  4571. }
  4572. return TRUE;
  4573. }
  4574.  
  4575. /**
  4576. * Stores data from the writer in the cache under a specific key and cache tag.
  4577. * A cache tag is used to make sure the cache remains consistent. In most cases the cache tag
  4578. * will be the bean type, this makes sure queries associated with a certain reference type will
  4579. * never contain conflicting data.
  4580. * Why not use the cache tag as a key? Well
  4581. * we need to make sure the cache contents fits the key (and key is based on the cache values).
  4582. * Otherwise it would be possible to store two different result sets under the same key (the cache tag).
  4583. *
  4584. * In previous versions you could only store one key-entry, I have changed this to
  4585. * improve caching efficiency (issue #400).
  4586. *
  4587. * @param string $cacheTag cache tag (secondary key)
  4588. * @param string $key key to store values under
  4589. * @param array $values content to be stored
  4590. *
  4591. * @return void
  4592. */
  4593. private function putResultInCache( $cacheTag, $key, $values )
  4594. {
  4595. if ( isset( $this->cache[$cacheTag] ) ) {
  4596. if ( count( $this->cache[$cacheTag] ) > $this->maxCacheSizePerType ) array_shift( $this->cache[$cacheTag] );
  4597. } else {
  4598. $this->cache[$cacheTag] = array();
  4599. }
  4600.  
  4601. $this->cache[$cacheTag][$key] = $values;
  4602. }
  4603.  
  4604. /**
  4605. * Creates an SQL snippet from a list of conditions of format:
  4606. *
  4607. * <code>
  4608. * array(
  4609. * key => array(
  4610. * value1, value2, value3 ....
  4611. * )
  4612. * )
  4613. * </code>
  4614. *
  4615. * @param array $conditions list of conditions
  4616. * @param array $bindings parameter bindings for SQL snippet
  4617. * @param string $addSql additional SQL snippet to append to result
  4618. *
  4619. * @return string
  4620. */
  4621. private function makeSQLFromConditions( $conditions, &$bindings, $addSql = '' )
  4622. {
  4623. reset( $bindings );
  4624. $firstKey = key( $bindings );
  4625. $paramTypeIsNum = ( is_numeric( $firstKey ) );
  4626. $counter = 0;
  4627.  
  4628. $sqlConditions = array();
  4629. foreach ( $conditions as $column => $values ) {
  4630. if ( !count( $values ) ) continue;
  4631.  
  4632. $sql = $this->esc( $column );
  4633. $sql .= ' IN ( ';
  4634.  
  4635. if ( !is_array( $values ) ) $values = array( $values );
  4636.  
  4637. // If it's safe to skip bindings, do so...
  4638. if ( ctype_digit( implode( '', $values ) ) ) {
  4639. $sql .= implode( ',', $values ) . ' ) ';
  4640.  
  4641. // only numeric, cant do much harm
  4642. $sqlConditions[] = $sql;
  4643. } else {
  4644.  
  4645. if ( $paramTypeIsNum ) {
  4646. $sql .= implode( ',', array_fill( 0, count( $values ), '?' ) ) . ' ) ';
  4647.  
  4648. array_unshift($sqlConditions, $sql);
  4649.  
  4650. foreach ( $values as $k => $v ) {
  4651. $values[$k] = strval( $v );
  4652.  
  4653. array_unshift( $bindings, $v );
  4654. }
  4655. } else {
  4656.  
  4657. $slots = array();
  4658.  
  4659. foreach( $values as $k => $v ) {
  4660. $slot = ':slot'.$counter++;
  4661. $slots[] = $slot;
  4662. $bindings[$slot] = strval( $v );
  4663. }
  4664.  
  4665. $sql .= implode( ',', $slots ).' ) ';
  4666. $sqlConditions[] = $sql;
  4667. }
  4668. }
  4669. }
  4670.  
  4671. $sql = '';
  4672. if ( is_array( $sqlConditions ) && count( $sqlConditions ) > 0 ) {
  4673. $sql = implode( ' AND ', $sqlConditions );
  4674. $sql = " WHERE ( $sql ) ";
  4675.  
  4676. if ( $addSql ) $sql .= $addSql;
  4677. } elseif ( $addSql ) {
  4678. $sql = $addSql;
  4679. }
  4680.  
  4681. return $sql;
  4682. }
  4683.  
  4684. /**
  4685. * Returns the table names and column names for a relational query.
  4686. *
  4687. * @param string $sourceType type of the source bean
  4688. * @param string $destType type of the bean you want to obtain using the relation
  4689. * @param boolean $noQuote TRUE if you want to omit quotes
  4690. *
  4691. * @return array
  4692. */
  4693. private function getRelationalTablesAndColumns( $sourceType, $destType, $noQuote = FALSE )
  4694. {
  4695. $linkTable = $this->esc( $this->getAssocTable( array( $sourceType, $destType ) ), $noQuote );
  4696. $sourceCol = $this->esc( $sourceType . '_id', $noQuote );
  4697.  
  4698. if ( $sourceType === $destType ) {
  4699. $destCol = $this->esc( $destType . '2_id', $noQuote );
  4700. } else {
  4701. $destCol = $this->esc( $destType . '_id', $noQuote );
  4702. }
  4703.  
  4704. $sourceTable = $this->esc( $sourceType, $noQuote );
  4705. $destTable = $this->esc( $destType, $noQuote );
  4706.  
  4707. return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol );
  4708. }
  4709.  
  4710. /**
  4711. * Given a type and a property name this method
  4712. * returns the foreign key map section associated with this pair.
  4713. *
  4714. * @param string $type name of the type
  4715. * @param string $property name of the property
  4716. *
  4717. * @return array|NULL
  4718. */
  4719. protected function getForeignKeyForTypeProperty( $type, $property )
  4720. {
  4721. $property = $this->esc( $property, TRUE );
  4722.  
  4723. try {
  4724. $map = $this->getKeyMapForType( $type );
  4725. } catch ( SQLException $e ) {
  4726. return NULL;
  4727. }
  4728.  
  4729. foreach( $map as $key ) {
  4730. if ( $key['from'] === $property ) return $key;
  4731. }
  4732. return NULL;
  4733. }
  4734.  
  4735. /**
  4736. * Returns the foreign key map (FKM) for a type.
  4737. * A foreign key map describes the foreign keys in a table.
  4738. * A FKM always has the same structure:
  4739. *
  4740. * <code>
  4741. * array(
  4742. * 'name' => <name of the foreign key>
  4743. * 'from' => <name of the column on the source table>
  4744. * 'table' => <name of the target table>
  4745. * 'to' => <name of the target column> (most of the time 'id')
  4746. * 'on_update' => <update rule: 'SET NULL','CASCADE' or 'RESTRICT'>
  4747. * 'on_delete' => <delete rule: 'SET NULL','CASCADE' or 'RESTRICT'>
  4748. * )
  4749. * </code>
  4750. *
  4751. * @note the keys in the result array are FKDLs, i.e. descriptive unique
  4752. * keys per source table. Also see: AQueryWriter::makeFKLabel for details.
  4753. *
  4754. * @param string $type the bean type you wish to obtain a key map of
  4755. *
  4756. * @return array
  4757. */
  4758. protected function getKeyMapForType( $type )
  4759. {
  4760. return array();
  4761. }
  4762.  
  4763. /**
  4764. * This method makes a key for a foreign key description array.
  4765. * This key is a readable string unique for every source table.
  4766. * This uniform key is called the FKDL Foreign Key Description Label.
  4767. * Note that the source table is not part of the FKDL because
  4768. * this key is supposed to be 'per source table'. If you wish to
  4769. * include a source table, prefix the key with 'on_table_<SOURCE>_'.
  4770. *
  4771. * @param string $from the column of the key in the source table
  4772. * @param string $type the type (table) where the key points to
  4773. * @param string $to the target column of the foreign key (mostly just 'id')
  4774. *
  4775. * @return string
  4776. */
  4777. protected function makeFKLabel($from, $type, $to)
  4778. {
  4779. return "from_{$from}_to_table_{$type}_col_{$to}";
  4780. }
  4781.  
  4782. /**
  4783. * Returns an SQL Filter snippet for reading.
  4784. *
  4785. * @param string $type type of bean
  4786. *
  4787. * @return string
  4788. */
  4789. protected function getSQLFilterSnippet( $type )
  4790. {
  4791. $existingCols = array();
  4792. if (self::$flagSQLFilterSafeMode) {
  4793. $existingCols = $this->getColumns( $type );
  4794. }
  4795.  
  4796. $sqlFilters = array();
  4797. if ( isset( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] ) ) {
  4798. foreach( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] as $property => $sqlFilter ) {
  4799. if ( !self::$flagSQLFilterSafeMode || isset( $existingCols[$property] ) ) {
  4800. $sqlFilters[] = $sqlFilter.' AS '.$property.' ';
  4801. }
  4802. }
  4803. }
  4804. $sqlFilterStr = ( count($sqlFilters) ) ? ( ','.implode( ',', $sqlFilters ) ) : '';
  4805. return $sqlFilterStr;
  4806. }
  4807.  
  4808. /**
  4809. * Generates a list of parameters (slots) for an SQL snippet.
  4810. * This method calculates the correct number of slots to insert in the
  4811. * SQL snippet and determines the correct type of slot. If the bindings
  4812. * array contains named parameters this method will return named ones and
  4813. * update the keys in the value list accordingly (that's why we use the &).
  4814. *
  4815. * If you pass an offset the bindings will be re-added to the value list.
  4816. * Some databases cant handle duplicate parameter names in queries.
  4817. *
  4818. * @param array &$valueList list of values to generate slots for (gets modified if needed)
  4819. * @param array $otherBindings list of additional bindings
  4820. * @param integer $offset start counter at...
  4821. *
  4822. * @return string
  4823. */
  4824. protected function getParametersForInClause( &$valueList, $otherBindings, $offset = 0 )
  4825. {
  4826. if ( is_array( $otherBindings ) && count( $otherBindings ) > 0 ) {
  4827. reset( $otherBindings );
  4828.  
  4829. $key = key( $otherBindings );
  4830.  
  4831. if ( !is_numeric($key) ) {
  4832. $filler = array();
  4833. $newList = (!$offset) ? array() : $valueList;
  4834. $counter = $offset;
  4835.  
  4836. foreach( $valueList as $value ) {
  4837. $slot = ':slot' . ( $counter++ );
  4838. $filler[] = $slot;
  4839. $newList[$slot] = $value;
  4840. }
  4841.  
  4842. // Change the keys!
  4843. $valueList = $newList;
  4844.  
  4845. return implode( ',', $filler );
  4846. }
  4847. }
  4848.  
  4849. return implode( ',', array_fill( 0, count( $valueList ), '?' ) );
  4850. }
  4851.  
  4852. /**
  4853. * Adds a data type to the list of data types.
  4854. * Use this method to add a new column type definition to the writer.
  4855. * Used for UUID support.
  4856. *
  4857. * @param integer $dataTypeID magic number constant assigned to this data type
  4858. * @param string $SQLDefinition SQL column definition (i.e. INT(11))
  4859. *
  4860. * @return self
  4861. */
  4862. protected function addDataType( $dataTypeID, $SQLDefinition )
  4863. {
  4864. $this->typeno_sqltype[ $dataTypeID ] = $SQLDefinition;
  4865. $this->sqltype_typeno[ $SQLDefinition ] = $dataTypeID;
  4866. return $this;
  4867. }
  4868.  
  4869. /**
  4870. * Returns the sql that should follow an insert statement.
  4871. *
  4872. * @param string $table name
  4873. *
  4874. * @return string
  4875. */
  4876. protected function getInsertSuffix( $table )
  4877. {
  4878. return '';
  4879. }
  4880.  
  4881. /**
  4882. * Checks whether a value starts with zeros. In this case
  4883. * the value should probably be stored using a text datatype instead of a
  4884. * numerical type in order to preserve the zeros.
  4885. *
  4886. * @param string $value value to be checked.
  4887. *
  4888. * @return boolean
  4889. */
  4890. protected function startsWithZeros( $value )
  4891. {
  4892. $value = strval( $value );
  4893.  
  4894. if ( strlen( $value ) > 1 && strpos( $value, '0' ) === 0 && strpos( $value, '0.' ) !== 0 ) {
  4895. return TRUE;
  4896. } else {
  4897. return FALSE;
  4898. }
  4899. }
  4900.  
  4901. /**
  4902. * Inserts a record into the database using a series of insert columns
  4903. * and corresponding insertvalues. Returns the insert id.
  4904. *
  4905. * @param string $table table to perform query on
  4906. * @param array $insertcolumns columns to be inserted
  4907. * @param array $insertvalues values to be inserted
  4908. *
  4909. * @return integer
  4910. */
  4911. protected function insertRecord( $type, $insertcolumns, $insertvalues )
  4912. {
  4913. $default = $this->defaultValue;
  4914. $suffix = $this->getInsertSuffix( $type );
  4915. $table = $this->esc( $type );
  4916.  
  4917. if ( count( $insertvalues ) > 0 && is_array( $insertvalues[0] ) && count( $insertvalues[0] ) > 0 ) {
  4918.  
  4919. $insertSlots = array();
  4920. foreach ( $insertcolumns as $k => $v ) {
  4921. $insertcolumns[$k] = $this->esc( $v );
  4922.  
  4923. if (isset(self::$sqlFilters['w'][$type][$v])) {
  4924. $insertSlots[] = self::$sqlFilters['w'][$type][$v];
  4925. } else {
  4926. $insertSlots[] = '?';
  4927. }
  4928. }
  4929.  
  4930. $insertSQL = "INSERT INTO $table ( id, " . implode( ',', $insertcolumns ) . " ) VALUES
  4931. ( $default, " . implode( ',', $insertSlots ) . " ) $suffix";
  4932.  
  4933. $ids = array();
  4934. foreach ( $insertvalues as $i => $insertvalue ) {
  4935. $ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i );
  4936. }
  4937.  
  4938. $result = count( $ids ) === 1 ? array_pop( $ids ) : $ids;
  4939. } else {
  4940. $result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix" );
  4941. }
  4942.  
  4943. if ( $suffix ) return $result;
  4944.  
  4945. $last_id = $this->adapter->getInsertID();
  4946.  
  4947. return $last_id;
  4948. }
  4949.  
  4950. /**
  4951. * Checks table name or column name.
  4952. *
  4953. * @param string $table table string
  4954. *
  4955. * @return string
  4956. */
  4957. protected function check( $struct )
  4958. {
  4959. if ( !is_string( $struct ) || !preg_match( '/^[a-zA-Z0-9_]+$/', $struct ) ) {
  4960. throw new RedException( 'Identifier does not conform to RedBeanPHP security policies.' );
  4961. }
  4962.  
  4963. return $struct;
  4964. }
  4965.  
  4966. /**
  4967. * Checks whether the specified type (i.e. table) already exists in the database.
  4968. * Not part of the Object Database interface!
  4969. *
  4970. * @param string $table table name
  4971. *
  4972. * @return boolean
  4973. */
  4974. public function tableExists( $table )
  4975. {
  4976. $tables = $this->getTables();
  4977.  
  4978. return in_array( $table, $tables );
  4979. }
  4980.  
  4981. /**
  4982. * @see QueryWriter::glueSQLCondition
  4983. */
  4984. public function glueSQLCondition( $sql, $glue = NULL )
  4985. {
  4986. static $snippetCache = array();
  4987.  
  4988. if ( trim( $sql ) === '' ) {
  4989. return $sql;
  4990. }
  4991.  
  4992. $key = $glue . '|' . $sql;
  4993.  
  4994. if ( isset( $snippetCache[$key] ) ) {
  4995. return $snippetCache[$key];
  4996. }
  4997.  
  4998. $lsql = ltrim( $sql );
  4999.  
  5000. if ( preg_match( '/^(INNER|LEFT|RIGHT|JOIN|AND|OR|WHERE|ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', $lsql ) ) {
  5001. if ( $glue === QueryWriter::C_GLUE_WHERE && stripos( $lsql, 'AND' ) === 0 ) {
  5002. $snippetCache[$key] = ' WHERE ' . substr( $lsql, 3 );
  5003. } else {
  5004. $snippetCache[$key] = $sql;
  5005. }
  5006. } else {
  5007. $snippetCache[$key] = ( ( $glue === QueryWriter::C_GLUE_AND ) ? ' AND ' : ' WHERE ') . $sql;
  5008. }
  5009.  
  5010. return $snippetCache[$key];
  5011. }
  5012.  
  5013. /**
  5014. * @see QueryWriter::glueLimitOne
  5015. */
  5016. public function glueLimitOne( $sql = '')
  5017. {
  5018. return ( strpos( strtoupper( $sql ), 'LIMIT' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql;
  5019. }
  5020.  
  5021. /**
  5022. * @see QueryWriter::esc
  5023. */
  5024. public function esc( $dbStructure, $dontQuote = FALSE )
  5025. {
  5026. $this->check( $dbStructure );
  5027.  
  5028. return ( $dontQuote ) ? $dbStructure : $this->quoteCharacter . $dbStructure . $this->quoteCharacter;
  5029. }
  5030.  
  5031. /**
  5032. * @see QueryWriter::addColumn
  5033. */
  5034. public function addColumn( $type, $column, $field )
  5035. {
  5036. $table = $type;
  5037. $type = $field;
  5038. $table = $this->esc( $table );
  5039. $column = $this->esc( $column );
  5040.  
  5041. $type = ( isset( $this->typeno_sqltype[$type] ) ) ? $this->typeno_sqltype[$type] : '';
  5042.  
  5043. $this->adapter->exec( "ALTER TABLE $table ADD $column $type " );
  5044. }
  5045.  
  5046. /**
  5047. * @see QueryWriter::updateRecord
  5048. */
  5049. public function updateRecord( $type, $updatevalues, $id = NULL )
  5050. {
  5051. $table = $type;
  5052.  
  5053. if ( !$id ) {
  5054. $insertcolumns = $insertvalues = array();
  5055.  
  5056. foreach ( $updatevalues as $pair ) {
  5057. $insertcolumns[] = $pair['property'];
  5058. $insertvalues[] = $pair['value'];
  5059. }
  5060.  
  5061. //Otherwise psql returns string while MySQL/SQLite return numeric causing problems with additions (array_diff)
  5062. return (string) $this->insertRecord( $table, $insertcolumns, array( $insertvalues ) );
  5063. }
  5064.  
  5065. if ( $id && !count( $updatevalues ) ) {
  5066. return $id;
  5067. }
  5068.  
  5069. $table = $this->esc( $table );
  5070. $sql = "UPDATE $table SET ";
  5071.  
  5072. $p = $v = array();
  5073.  
  5074. foreach ( $updatevalues as $uv ) {
  5075.  
  5076. if ( isset( self::$sqlFilters['w'][$type][$uv['property']] ) ) {
  5077. $p[] = " {$this->esc( $uv["property"] )} = ". self::$sqlFilters['w'][$type][$uv['property']];
  5078. } else {
  5079. $p[] = " {$this->esc( $uv["property"] )} = ? ";
  5080. }
  5081.  
  5082. $v[] = $uv['value'];
  5083. }
  5084.  
  5085. $sql .= implode( ',', $p ) . ' WHERE id = ? ';
  5086.  
  5087. $v[] = $id;
  5088.  
  5089. $this->adapter->exec( $sql, $v );
  5090.  
  5091. return $id;
  5092. }
  5093.  
  5094. /**
  5095. * @see QueryWriter::writeJoin
  5096. */
  5097. public function writeJoin( $type, $targetType, $leftRight = 'LEFT' )
  5098. {
  5099. if ( $leftRight !== 'LEFT' && $leftRight !== 'RIGHT' && $leftRight !== 'INNER' )
  5100. throw new RedException( 'Invalid JOIN.' );
  5101.  
  5102. $table = $this->esc( $type );
  5103. $targetTable = $this->esc( $targetType );
  5104. $field = $this->esc( $targetType, TRUE );
  5105. return " {$leftRight} JOIN {$targetTable} ON {$targetTable}.id = {$table}.{$field}_id ";
  5106. }
  5107.  
  5108. /**
  5109. * @see QueryWriter::queryRecord
  5110. */
  5111. public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
  5112. {
  5113. $addSql = $this->glueSQLCondition( $addSql, ( count($conditions) > 0) ? QueryWriter::C_GLUE_AND : NULL );
  5114.  
  5115. $key = NULL;
  5116. if ( $this->flagUseCache ) {
  5117. $key = $this->getCacheKey( array( $conditions, $addSql, $bindings, 'select' ) );
  5118.  
  5119. if ( $cached = $this->getCached( $type, $key ) ) {
  5120. return $cached;
  5121. }
  5122. }
  5123.  
  5124. $table = $this->esc( $type );
  5125.  
  5126. $sqlFilterStr = '';
  5127. if ( count( self::$sqlFilters ) ) {
  5128. $sqlFilterStr = $this->getSQLFilterSnippet( $type );
  5129. }
  5130.  
  5131. $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
  5132.  
  5133. $fieldSelection = ( self::$flagNarrowFieldMode ) ? "{$table}.*" : '*';
  5134. $sql = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} -- keep-cache";
  5135.  
  5136. $rows = $this->adapter->get( $sql, $bindings );
  5137.  
  5138. if ( $this->flagUseCache && $key ) {
  5139. $this->putResultInCache( $type, $key, $rows );
  5140. }
  5141.  
  5142. return $rows;
  5143. }
  5144.  
  5145. /**
  5146. * @see QueryWriter::queryRecordWithCursor
  5147. */
  5148. public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() )
  5149. {
  5150. $sql = $this->glueSQLCondition( $addSql, NULL );
  5151. $table = $this->esc( $type );
  5152. $sql = "SELECT {$table}.* FROM {$table} {$sql}";
  5153. return $this->adapter->getCursor( $sql, $bindings );
  5154. }
  5155.  
  5156. /**
  5157. * @see QueryWriter::queryRecordRelated
  5158. */
  5159. public function queryRecordRelated( $sourceType, $destType, $linkIDs, $addSql = '', $bindings = array() )
  5160. {
  5161. $addSql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
  5162.  
  5163. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  5164.  
  5165. $key = $this->getCacheKey( array( $sourceType, $destType, implode( ',', $linkIDs ), $addSql, $bindings ) );
  5166.  
  5167. if ( $this->flagUseCache && $cached = $this->getCached( $destType, $key ) ) {
  5168. return $cached;
  5169. }
  5170.  
  5171. $inClause = $this->getParametersForInClause( $linkIDs, $bindings );
  5172.  
  5173. $sqlFilterStr = '';
  5174. if ( count( self::$sqlFilters ) ) {
  5175. $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
  5176. }
  5177.  
  5178. if ( $sourceType === $destType ) {
  5179. $inClause2 = $this->getParametersForInClause( $linkIDs, $bindings, count( $bindings ) ); //for some databases
  5180. $sql = "
  5181. SELECT
  5182. {$destTable}.* {$sqlFilterStr} ,
  5183. COALESCE(
  5184. NULLIF({$linkTable}.{$sourceCol}, {$destTable}.id),
  5185. NULLIF({$linkTable}.{$destCol}, {$destTable}.id)) AS linked_by
  5186. FROM {$linkTable}
  5187. INNER JOIN {$destTable} ON
  5188. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) ) OR
  5189. ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} IN ($inClause2) )
  5190. {$addSql}
  5191. -- keep-cache";
  5192.  
  5193. $linkIDs = array_merge( $linkIDs, $linkIDs );
  5194. } else {
  5195. $sql = "
  5196. SELECT
  5197. {$destTable}.* {$sqlFilterStr},
  5198. {$linkTable}.{$sourceCol} AS linked_by
  5199. FROM {$linkTable}
  5200. INNER JOIN {$destTable} ON
  5201. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) )
  5202. {$addSql}
  5203. -- keep-cache";
  5204. }
  5205.  
  5206. $bindings = array_merge( $linkIDs, $bindings );
  5207.  
  5208. $rows = $this->adapter->get( $sql, $bindings );
  5209.  
  5210. $this->putResultInCache( $destType, $key, $rows );
  5211.  
  5212. return $rows;
  5213. }
  5214.  
  5215. /**
  5216. * @see QueryWriter::queryRecordLink
  5217. */
  5218. public function queryRecordLink( $sourceType, $destType, $sourceID, $destID )
  5219. {
  5220. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  5221.  
  5222. $key = $this->getCacheKey( array( $sourceType, $destType, $sourceID, $destID ) );
  5223.  
  5224. if ( $this->flagUseCache && $cached = $this->getCached( $linkTable, $key ) ) {
  5225. return $cached;
  5226. }
  5227.  
  5228. $sqlFilterStr = '';
  5229. if ( count( self::$sqlFilters ) ) {
  5230. $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
  5231. }
  5232.  
  5233. if ( $sourceTable === $destTable ) {
  5234. $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
  5235. WHERE ( {$sourceCol} = ? AND {$destCol} = ? ) OR
  5236. ( {$destCol} = ? AND {$sourceCol} = ? ) -- keep-cache";
  5237. $row = $this->adapter->getRow( $sql, array( $sourceID, $destID, $sourceID, $destID ) );
  5238. } else {
  5239. $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
  5240. WHERE {$sourceCol} = ? AND {$destCol} = ? -- keep-cache";
  5241. $row = $this->adapter->getRow( $sql, array( $sourceID, $destID ) );
  5242. }
  5243.  
  5244. $this->putResultInCache( $linkTable, $key, $row );
  5245.  
  5246. return $row;
  5247. }
  5248.  
  5249. /**
  5250. * @see QueryWriter::queryTagged
  5251. */
  5252. public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
  5253. {
  5254. $assocType = $this->getAssocTable( array( $type, 'tag' ) );
  5255. $assocTable = $this->esc( $assocType );
  5256. $assocField = $type . '_id';
  5257. $table = $this->esc( $type );
  5258. $slots = implode( ',', array_fill( 0, count( $tagList ), '?' ) );
  5259. $score = ( $all ) ? count( $tagList ) : 1;
  5260.  
  5261. $sql = "
  5262. SELECT {$table}.*, count({$table}.id) FROM {$table}
  5263. INNER JOIN {$assocTable} ON {$assocField} = {$table}.id
  5264. INNER JOIN tag ON {$assocTable}.tag_id = tag.id
  5265. WHERE tag.title IN ({$slots})
  5266. GROUP BY {$table}.id
  5267. HAVING count({$table}.id) >= ?
  5268. {$addSql}
  5269. ";
  5270.  
  5271. $bindings = array_merge( $tagList, array( $score ), $bindings );
  5272. $rows = $this->adapter->get( $sql, $bindings );
  5273. return $rows;
  5274. }
  5275.  
  5276. /**
  5277. * @see QueryWriter::queryRecordCount
  5278. */
  5279. public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
  5280. {
  5281. $addSql = $this->glueSQLCondition( $addSql );
  5282.  
  5283. $table = $this->esc( $type );
  5284.  
  5285. $this->updateCache(); //check if cache chain has been broken
  5286.  
  5287. $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
  5288. $sql = "SELECT COUNT(*) FROM {$table} {$sql} -- keep-cache";
  5289.  
  5290. return (int) $this->adapter->getCell( $sql, $bindings );
  5291. }
  5292.  
  5293. /**
  5294. * @see QueryWriter::queryRecordCountRelated
  5295. */
  5296. public function queryRecordCountRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() )
  5297. {
  5298. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  5299.  
  5300. $this->updateCache(); //check if cache chain has been broken
  5301.  
  5302. if ( $sourceType === $destType ) {
  5303. $sql = "
  5304. SELECT COUNT(*) FROM {$linkTable}
  5305. INNER JOIN {$destTable} ON
  5306. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? ) OR
  5307. ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} = ? )
  5308. {$addSql}
  5309. -- keep-cache";
  5310.  
  5311. $bindings = array_merge( array( $linkID, $linkID ), $bindings );
  5312. } else {
  5313. $sql = "
  5314. SELECT COUNT(*) FROM {$linkTable}
  5315. INNER JOIN {$destTable} ON
  5316. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? )
  5317. {$addSql}
  5318. -- keep-cache";
  5319.  
  5320. $bindings = array_merge( array( $linkID ), $bindings );
  5321. }
  5322.  
  5323. return (int) $this->adapter->getCell( $sql, $bindings );
  5324. }
  5325.  
  5326. /**
  5327. * @see QueryWriter::deleteRecord
  5328. */
  5329. public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
  5330. {
  5331. $addSql = $this->glueSQLCondition( $addSql );
  5332.  
  5333. $table = $this->esc( $type );
  5334.  
  5335. $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
  5336. $sql = "DELETE FROM {$table} {$sql}";
  5337.  
  5338. $this->adapter->exec( $sql, $bindings );
  5339. }
  5340.  
  5341. /**
  5342. * @see QueryWriter::deleteRelations
  5343. */
  5344. public function deleteRelations( $sourceType, $destType, $sourceID )
  5345. {
  5346. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  5347.  
  5348. if ( $sourceTable === $destTable ) {
  5349. $sql = "DELETE FROM {$linkTable}
  5350. WHERE ( {$sourceCol} = ? ) OR
  5351. ( {$destCol} = ? )
  5352. ";
  5353.  
  5354. $this->adapter->exec( $sql, array( $sourceID, $sourceID ) );
  5355. } else {
  5356. $sql = "DELETE FROM {$linkTable}
  5357. WHERE {$sourceCol} = ? ";
  5358.  
  5359. $this->adapter->exec( $sql, array( $sourceID ) );
  5360. }
  5361. }
  5362.  
  5363. /**
  5364. * @see QueryWriter::widenColumn
  5365. */
  5366. public function widenColumn( $type, $property, $dataType )
  5367. {
  5368. if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
  5369.  
  5370. $table = $this->esc( $type );
  5371. $column = $this->esc( $property );
  5372.  
  5373. $newType = $this->typeno_sqltype[$dataType];
  5374.  
  5375. $this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newType " );
  5376.  
  5377. return TRUE;
  5378. }
  5379.  
  5380. /**
  5381. * @see QueryWriter::wipe
  5382. */
  5383. public function wipe( $type )
  5384. {
  5385. $table = $this->esc( $type );
  5386.  
  5387. $this->adapter->exec( "TRUNCATE $table " );
  5388. }
  5389.  
  5390. /**
  5391. * @see QueryWriter::renameAssocTable
  5392. */
  5393. public function renameAssocTable( $from, $to = NULL )
  5394. {
  5395. self::renameAssociation( $from, $to );
  5396. }
  5397.  
  5398. /**
  5399. * @see QueryWriter::getAssocTable
  5400. */
  5401. public function getAssocTable( $types )
  5402. {
  5403. return self::getAssocTableFormat( $types );
  5404. }
  5405.  
  5406. /**
  5407. * Turns caching on or off. Default: off.
  5408. * If caching is turned on retrieval queries fired after eachother will
  5409. * use a result row cache.
  5410. *
  5411. * @param boolean
  5412. *
  5413. * @return void
  5414. */
  5415. public function setUseCache( $yesNo )
  5416. {
  5417. $this->flushCache();
  5418.  
  5419. $this->flagUseCache = (bool) $yesNo;
  5420. }
  5421.  
  5422. /**
  5423. * Flushes the Query Writer Cache.
  5424. * Clears the internal query cache array and returns its overall
  5425. * size.
  5426. *
  5427. * @return integer
  5428. */
  5429. public function flushCache( $newMaxCacheSizePerType = NULL )
  5430. {
  5431. if ( !is_null( $newMaxCacheSizePerType ) && $newMaxCacheSizePerType > 0 ) {
  5432. $this->maxCacheSizePerType = $newMaxCacheSizePerType;
  5433. }
  5434. $count = count( $this->cache, COUNT_RECURSIVE );
  5435. $this->cache = array();
  5436. return $count;
  5437. }
  5438.  
  5439. /**
  5440. * @deprecated Use esc() instead.
  5441. *
  5442. * @param string $column column to be escaped
  5443. * @param boolean $noQuotes omit quotes
  5444. *
  5445. * @return string
  5446. */
  5447. public function safeColumn( $column, $noQuotes = FALSE )
  5448. {
  5449. return $this->esc( $column, $noQuotes );
  5450. }
  5451.  
  5452. /**
  5453. * @deprecated Use esc() instead.
  5454. *
  5455. * @param string $table table to be escaped
  5456. * @param boolean $noQuotes omit quotes
  5457. *
  5458. * @return string
  5459. */
  5460. public function safeTable( $table, $noQuotes = FALSE )
  5461. {
  5462. return $this->esc( $table, $noQuotes );
  5463. }
  5464.  
  5465. /**
  5466. * @see QueryWriter::inferFetchType
  5467. */
  5468. public function inferFetchType( $type, $property )
  5469. {
  5470. $type = $this->esc( $type, TRUE );
  5471. $field = $this->esc( $property, TRUE ) . '_id';
  5472. $keys = $this->getKeyMapForType( $type );
  5473.  
  5474. foreach( $keys as $key ) {
  5475. if (
  5476. $key['from'] === $field
  5477. ) return $key['table'];
  5478. }
  5479. return NULL;
  5480. }
  5481.  
  5482. /**
  5483. * @see QueryWriter::addUniqueConstraint
  5484. */
  5485. public function addUniqueIndex( $type, $properties )
  5486. {
  5487. return $this->addUniqueConstraint( $type, $properties );
  5488. }
  5489. }
  5490. }
  5491.  
  5492. namespace RedBeanPHP\QueryWriter {
  5493.  
  5494. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  5495. use RedBeanPHP\QueryWriter as QueryWriter;
  5496. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  5497. use RedBeanPHP\Adapter as Adapter;
  5498. use RedBeanPHP\RedException\SQL as SQLException;
  5499.  
  5500. /**
  5501. * RedBeanPHP MySQLWriter.
  5502. * This is a QueryWriter class for RedBeanPHP.
  5503. * This QueryWriter provides support for the MySQL/MariaDB database platform.
  5504. *
  5505. * @file RedBeanPHP/QueryWriter/MySQL.php
  5506. * @author Gabor de Mooij and the RedBeanPHP Community
  5507. * @license BSD/GPLv2
  5508. *
  5509. * @copyright
  5510. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  5511. * This source file is subject to the BSD/GPLv2 License that is bundled
  5512. * with this source code in the file license.txt.
  5513. */
  5514. class MySQL extends AQueryWriter implements QueryWriter
  5515. {
  5516. /**
  5517. * Data types
  5518. */
  5519. const C_DATATYPE_BOOL = 0;
  5520. const C_DATATYPE_UINT32 = 2;
  5521. const C_DATATYPE_DOUBLE = 3;
  5522. const C_DATATYPE_TEXT7 = 4; //InnoDB cant index varchar(255) utf8mb4 - so keep 191 as long as possible
  5523. const C_DATATYPE_TEXT8 = 5;
  5524. const C_DATATYPE_TEXT16 = 6;
  5525. const C_DATATYPE_TEXT32 = 7;
  5526. const C_DATATYPE_SPECIAL_DATE = 80;
  5527. const C_DATATYPE_SPECIAL_DATETIME = 81;
  5528. const C_DATATYPE_SPECIAL_POINT = 90;
  5529. const C_DATATYPE_SPECIAL_LINESTRING = 91;
  5530. const C_DATATYPE_SPECIAL_POLYGON = 92;
  5531. const C_DATATYPE_SPECIAL_MONEY = 93;
  5532.  
  5533. const C_DATATYPE_SPECIFIED = 99;
  5534.  
  5535. /**
  5536. * @var DBAdapter
  5537. */
  5538. protected $adapter;
  5539.  
  5540. /**
  5541. * @var string
  5542. */
  5543. protected $quoteCharacter = '`';
  5544.  
  5545. /**
  5546. * @see AQueryWriter::getKeyMapForType
  5547. */
  5548. protected function getKeyMapForType( $type )
  5549. {
  5550. $databaseName = $this->adapter->getCell('SELECT DATABASE()');
  5551. $table = $this->esc( $type, TRUE );
  5552. $keys = $this->adapter->get('
  5553. SELECT
  5554. information_schema.key_column_usage.constraint_name AS `name`,
  5555. information_schema.key_column_usage.referenced_table_name AS `table`,
  5556. information_schema.key_column_usage.column_name AS `from`,
  5557. information_schema.key_column_usage.referenced_column_name AS `to`,
  5558. information_schema.referential_constraints.update_rule AS `on_update`,
  5559. information_schema.referential_constraints.delete_rule AS `on_delete`
  5560. FROM information_schema.key_column_usage
  5561. INNER JOIN information_schema.referential_constraints
  5562. ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
  5563. WHERE
  5564. information_schema.key_column_usage.table_schema = :database
  5565. AND information_schema.referential_constraints.constraint_schema = :database
  5566. AND information_schema.key_column_usage.constraint_schema = :database
  5567. AND information_schema.key_column_usage.table_name = :table
  5568. AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
  5569. AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
  5570. ', array( ':database' => $databaseName, ':table' => $table ) );
  5571. $keyInfoList = array();
  5572. foreach ( $keys as $k ) {
  5573. $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
  5574. $keyInfoList[$label] = array(
  5575. 'name' => $k['name'],
  5576. 'from' => $k['from'],
  5577. 'table' => $k['table'],
  5578. 'to' => $k['to'],
  5579. 'on_update' => $k['on_update'],
  5580. 'on_delete' => $k['on_delete']
  5581. );
  5582. }
  5583. return $keyInfoList;
  5584. }
  5585.  
  5586. /**
  5587. * Constructor
  5588. *
  5589. * @param Adapter $adapter Database Adapter
  5590. */
  5591. public function __construct( Adapter $adapter )
  5592. {
  5593. $this->typeno_sqltype = array(
  5594. MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ',
  5595. MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ',
  5596. MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ',
  5597. MySQL::C_DATATYPE_TEXT7 => ' VARCHAR(191) ',
  5598. MYSQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ',
  5599. MySQL::C_DATATYPE_TEXT16 => ' TEXT ',
  5600. MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ',
  5601. MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ',
  5602. MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
  5603. MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ',
  5604. MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ',
  5605. MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ',
  5606. MySQL::C_DATATYPE_SPECIAL_MONEY => ' DECIMAL(10,2) '
  5607. );
  5608.  
  5609. $this->sqltype_typeno = array();
  5610.  
  5611. foreach ( $this->typeno_sqltype as $k => $v ) {
  5612. $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
  5613. }
  5614.  
  5615. $this->adapter = $adapter;
  5616.  
  5617. $this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
  5618. }
  5619.  
  5620. /**
  5621. * This method returns the datatype to be used for primary key IDS and
  5622. * foreign keys. Returns one if the data type constants.
  5623. *
  5624. * @return integer
  5625. */
  5626. public function getTypeForID()
  5627. {
  5628. return self::C_DATATYPE_UINT32;
  5629. }
  5630.  
  5631. /**
  5632. * @see QueryWriter::getTables
  5633. */
  5634. public function getTables()
  5635. {
  5636. return $this->adapter->getCol( 'show tables' );
  5637. }
  5638.  
  5639. /**
  5640. * @see QueryWriter::createTable
  5641. */
  5642. public function createTable( $table )
  5643. {
  5644. $table = $this->esc( $table );
  5645.  
  5646. $encoding = $this->adapter->getDatabase()->getMysqlEncoding();
  5647. $sql = "CREATE TABLE $table (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET={$encoding} COLLATE={$encoding}_unicode_ci ";
  5648.  
  5649. $this->adapter->exec( $sql );
  5650. }
  5651.  
  5652. /**
  5653. * @see QueryWriter::getColumns
  5654. */
  5655. public function getColumns( $table )
  5656. {
  5657. $columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
  5658.  
  5659. $columns = array();
  5660. foreach ( $columnsRaw as $r ) {
  5661. $columns[$r['Field']] = $r['Type'];
  5662. }
  5663.  
  5664. return $columns;
  5665. }
  5666.  
  5667. /**
  5668. * @see QueryWriter::scanType
  5669. */
  5670. public function scanType( $value, $flagSpecial = FALSE )
  5671. {
  5672. $this->svalue = $value;
  5673.  
  5674. if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
  5675. if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7;
  5676.  
  5677. if ( $flagSpecial ) {
  5678. if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
  5679. return MySQL::C_DATATYPE_SPECIAL_MONEY;
  5680. }
  5681. if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
  5682. return MySQL::C_DATATYPE_SPECIAL_DATE;
  5683. }
  5684. if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
  5685. return MySQL::C_DATATYPE_SPECIAL_DATETIME;
  5686. }
  5687. if ( preg_match( '/^POINT\(/', $value ) ) {
  5688. return MySQL::C_DATATYPE_SPECIAL_POINT;
  5689. }
  5690. if ( preg_match( '/^LINESTRING\(/', $value ) ) {
  5691. return MySQL::C_DATATYPE_SPECIAL_LINESTRING;
  5692. }
  5693. if ( preg_match( '/^POLYGON\(/', $value ) ) {
  5694. return MySQL::C_DATATYPE_SPECIAL_POLYGON;
  5695. }
  5696. }
  5697.  
  5698. //setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?).
  5699. if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' ) {
  5700. return MySQL::C_DATATYPE_BOOL;
  5701. }
  5702.  
  5703. if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
  5704.  
  5705. if ( !$this->startsWithZeros( $value ) ) {
  5706.  
  5707. if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
  5708. return MySQL::C_DATATYPE_UINT32;
  5709. }
  5710.  
  5711. if ( is_numeric( $value ) ) {
  5712. return MySQL::C_DATATYPE_DOUBLE;
  5713. }
  5714. }
  5715.  
  5716. if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) {
  5717. return MySQL::C_DATATYPE_TEXT7;
  5718. }
  5719.  
  5720. if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
  5721. return MySQL::C_DATATYPE_TEXT8;
  5722. }
  5723.  
  5724. if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
  5725. return MySQL::C_DATATYPE_TEXT16;
  5726. }
  5727.  
  5728. return MySQL::C_DATATYPE_TEXT32;
  5729. }
  5730.  
  5731. /**
  5732. * @see QueryWriter::code
  5733. */
  5734. public function code( $typedescription, $includeSpecials = FALSE )
  5735. {
  5736. if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
  5737. $r = $this->sqltype_typeno[$typedescription];
  5738. } else {
  5739. $r = self::C_DATATYPE_SPECIFIED;
  5740. }
  5741.  
  5742. if ( $includeSpecials ) {
  5743. return $r;
  5744. }
  5745.  
  5746. if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
  5747. return self::C_DATATYPE_SPECIFIED;
  5748. }
  5749.  
  5750. return $r;
  5751. }
  5752.  
  5753. /**
  5754. * @see QueryWriter::addUniqueIndex
  5755. */
  5756. public function addUniqueConstraint( $type, $properties )
  5757. {
  5758. $tableNoQ = $this->esc( $type, TRUE );
  5759. $columns = array();
  5760. foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
  5761. $table = $this->esc( $type );
  5762. sort( $columns ); // Else we get multiple indexes due to order-effects
  5763. $name = 'UQ_' . sha1( implode( ',', $columns ) );
  5764. try {
  5765. $sql = "ALTER TABLE $table
  5766. ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
  5767. $this->adapter->exec( $sql );
  5768. } catch ( SQLException $e ) {
  5769. //do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
  5770. return FALSE;
  5771. }
  5772. return TRUE;
  5773. }
  5774.  
  5775. /**
  5776. * @see QueryWriter::addIndex
  5777. */
  5778. public function addIndex( $type, $name, $property )
  5779. {
  5780. try {
  5781. $table = $this->esc( $type );
  5782. $name = preg_replace( '/\W/', '', $name );
  5783. $column = $this->esc( $property );
  5784. $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
  5785. return TRUE;
  5786. } catch ( SQLException $e ) {
  5787. return FALSE;
  5788. }
  5789. }
  5790.  
  5791. /**
  5792. * @see QueryWriter::addFK
  5793. * @return bool
  5794. */
  5795. public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
  5796. {
  5797. $table = $this->esc( $type );
  5798. $targetTable = $this->esc( $targetType );
  5799. $targetTableNoQ = $this->esc( $targetType, TRUE );
  5800. $field = $this->esc( $property );
  5801. $fieldNoQ = $this->esc( $property, TRUE );
  5802. $targetField = $this->esc( $targetProperty );
  5803. $targetFieldNoQ = $this->esc( $targetProperty, TRUE );
  5804. $tableNoQ = $this->esc( $type, TRUE );
  5805. $fieldNoQ = $this->esc( $property, TRUE );
  5806. if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
  5807.  
  5808. //Widen the column if it's incapable of representing a foreign key (at least INT).
  5809. $columns = $this->getColumns( $tableNoQ );
  5810. $idType = $this->getTypeForID();
  5811. if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
  5812. $this->widenColumn( $type, $property, $idType );
  5813. }
  5814.  
  5815. $fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
  5816. $cName = 'c_'.$fkName;
  5817. try {
  5818. $this->adapter->exec( "
  5819. ALTER TABLE {$table}
  5820. ADD CONSTRAINT $cName
  5821. FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}`
  5822. (`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
  5823. } catch ( SQLException $e ) {
  5824. // Failure of fk-constraints is not a problem
  5825. }
  5826. return true;
  5827. }
  5828.  
  5829. /**
  5830. * @see QueryWriter::sqlStateIn
  5831. */
  5832. public function sqlStateIn( $state, $list )
  5833. {
  5834. $stateMap = array(
  5835. '42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  5836. '42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  5837. '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
  5838. );
  5839.  
  5840. return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
  5841. }
  5842.  
  5843. /**
  5844. * @see QueryWriter::wipeAll
  5845. */
  5846. public function wipeAll()
  5847. {
  5848. $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
  5849.  
  5850. foreach ( $this->getTables() as $t ) {
  5851. try {
  5852. $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
  5853. } catch ( SQLException $e ) {
  5854. }
  5855.  
  5856. try {
  5857. $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" );
  5858. } catch ( SQLException $e ) {
  5859. }
  5860. }
  5861.  
  5862. $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
  5863. }
  5864. }
  5865. }
  5866.  
  5867. namespace RedBeanPHP\QueryWriter {
  5868.  
  5869. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  5870. use RedBeanPHP\QueryWriter as QueryWriter;
  5871. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  5872. use RedBeanPHP\Adapter as Adapter;
  5873. use RedBeanPHP\RedException\SQL as SQLException;
  5874.  
  5875. /**
  5876. * RedBeanPHP SQLiteWriter with support for SQLite types
  5877. * This is a QueryWriter class for RedBeanPHP.
  5878. * This QueryWriter provides support for the SQLite database platform.
  5879. *
  5880. * @file RedBeanPHP/QueryWriter/SQLiteT.php
  5881. * @author Gabor de Mooij and the RedBeanPHP Community
  5882. * @license BSD/GPLv2
  5883. *
  5884. * @copyright
  5885. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  5886. * This source file is subject to the BSD/GPLv2 License that is bundled
  5887. * with this source code in the file license.txt.
  5888. */
  5889. class SQLiteT extends AQueryWriter implements QueryWriter
  5890. {
  5891. /**
  5892. * Data types
  5893. */
  5894. const C_DATATYPE_INTEGER = 0;
  5895. const C_DATATYPE_NUMERIC = 1;
  5896. const C_DATATYPE_TEXT = 2;
  5897. const C_DATATYPE_SPECIFIED = 99;
  5898.  
  5899. /**
  5900. * @var DBAdapter
  5901. */
  5902. protected $adapter;
  5903.  
  5904. /**
  5905. * @var string
  5906. */
  5907. protected $quoteCharacter = '`';
  5908.  
  5909. /**
  5910. * Gets all information about a table (from a type).
  5911. *
  5912. * Format:
  5913. * array(
  5914. * name => name of the table
  5915. * columns => array( name => datatype )
  5916. * indexes => array() raw index information rows from PRAGMA query
  5917. * keys => array() raw key information rows from PRAGMA query
  5918. * )
  5919. *
  5920. * @param string $type type you want to get info of
  5921. *
  5922. * @return array
  5923. */
  5924. protected function getTable( $type )
  5925. {
  5926. $tableName = $this->esc( $type, TRUE );
  5927. $columns = $this->getColumns( $type );
  5928. $indexes = $this->getIndexes( $type );
  5929. $keys = $this->getKeyMapForType( $type );
  5930.  
  5931. $table = array(
  5932. 'columns' => $columns,
  5933. 'indexes' => $indexes,
  5934. 'keys' => $keys,
  5935. 'name' => $tableName
  5936. );
  5937.  
  5938. $this->tableArchive[$tableName] = $table;
  5939.  
  5940. return $table;
  5941. }
  5942.  
  5943. /**
  5944. * Puts a table. Updates the table structure.
  5945. * In SQLite we can't change columns, drop columns, change or add foreign keys so we
  5946. * have a table-rebuild function. You simply load your table with getTable(), modify it and
  5947. * then store it with putTable()...
  5948. *
  5949. * @param array $tableMap information array
  5950. *
  5951. * @return void
  5952. */
  5953. protected function putTable( $tableMap )
  5954. {
  5955. $table = $tableMap['name'];
  5956. $q = array();
  5957. $q[] = "DROP TABLE IF EXISTS tmp_backup;";
  5958.  
  5959. $oldColumnNames = array_keys( $this->getColumns( $table ) );
  5960.  
  5961. foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
  5962.  
  5963. $q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
  5964. $q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
  5965. $q[] = "PRAGMA foreign_keys = 0 ";
  5966. $q[] = "DROP TABLE `$table`;";
  5967.  
  5968. $newTableDefStr = '';
  5969. foreach ( $tableMap['columns'] as $column => $type ) {
  5970. if ( $column != 'id' ) {
  5971. $newTableDefStr .= ",`$column` $type";
  5972. }
  5973. }
  5974.  
  5975. $fkDef = '';
  5976. foreach ( $tableMap['keys'] as $key ) {
  5977. $fkDef .= ", FOREIGN KEY(`{$key['from']}`)
  5978. REFERENCES `{$key['table']}`(`{$key['to']}`)
  5979. ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
  5980. }
  5981.  
  5982. $q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
  5983.  
  5984. foreach ( $tableMap['indexes'] as $name => $index ) {
  5985. if ( strpos( $name, 'UQ_' ) === 0 ) {
  5986. $cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
  5987. foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
  5988. $q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
  5989. } else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
  5990. }
  5991.  
  5992. $q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
  5993. $q[] = "DROP TABLE tmp_backup;";
  5994. $q[] = "PRAGMA foreign_keys = 1 ";
  5995.  
  5996. foreach ( $q as $sq ) $this->adapter->exec( $sq );
  5997. }
  5998.  
  5999. /**
  6000. * Returns the an array describing the indexes for type $type.
  6001. *
  6002. * @param string $type type to describe indexes of
  6003. *
  6004. * @return array
  6005. */
  6006. protected function getIndexes( $type )
  6007. {
  6008. $table = $this->esc( $type, TRUE );
  6009. $indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
  6010.  
  6011. $indexInfoList = array();
  6012. foreach ( $indexes as $i ) {
  6013. $indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
  6014.  
  6015. $indexInfoList[$i['name']]['unique'] = $i['unique'];
  6016. }
  6017.  
  6018. return $indexInfoList;
  6019. }
  6020.  
  6021. /**
  6022. * Adds a foreign key to a type.
  6023. * Note: cant put this in try-catch because that can hide the fact
  6024. * that database has been damaged.
  6025. *
  6026. * @param string $type type you want to modify table of
  6027. * @param string $targetType target type
  6028. * @param string $field field of the type that needs to get the fk
  6029. * @param string $targetField field where the fk needs to point to
  6030. * @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE
  6031. *
  6032. * @return boolean
  6033. */
  6034. protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
  6035. {
  6036. $table = $this->esc( $type, TRUE );
  6037. $targetTable = $this->esc( $targetType, TRUE );
  6038. $column = $this->esc( $property, TRUE );
  6039. $targetColumn = $this->esc( $targetProperty, TRUE );
  6040.  
  6041. $tables = $this->getTables();
  6042. if ( !in_array( $targetTable, $tables ) ) return FALSE;
  6043.  
  6044. if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
  6045. $t = $this->getTable( $table );
  6046. $consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
  6047. $label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
  6048. $t['keys'][$label] = array(
  6049. 'table' => $targetTable,
  6050. 'from' => $column,
  6051. 'to' => $targetColumn,
  6052. 'on_update' => $consSQL,
  6053. 'on_delete' => $consSQL
  6054. );
  6055. $this->putTable( $t );
  6056. return TRUE;
  6057. }
  6058.  
  6059. /**
  6060. * @see AQueryWriter::getKeyMapForType
  6061. */
  6062. protected function getKeyMapForType( $type )
  6063. {
  6064. $table = $this->esc( $type, TRUE );
  6065. $keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
  6066. $keyInfoList = array();
  6067. foreach ( $keys as $k ) {
  6068. $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
  6069. $keyInfoList[$label] = array(
  6070. 'name' => $label,
  6071. 'from' => $k['from'],
  6072. 'table' => $k['table'],
  6073. 'to' => $k['to'],
  6074. 'on_update' => $k['on_update'],
  6075. 'on_delete' => $k['on_delete']
  6076. );
  6077. }
  6078. return $keyInfoList;
  6079. }
  6080.  
  6081. /**
  6082. * Constructor
  6083. *
  6084. * @param Adapter $adapter Database Adapter
  6085. */
  6086. public function __construct( Adapter $adapter )
  6087. {
  6088. $this->typeno_sqltype = array(
  6089. SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
  6090. SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
  6091. SQLiteT::C_DATATYPE_TEXT => 'TEXT',
  6092. );
  6093.  
  6094. $this->sqltype_typeno = array();
  6095.  
  6096. foreach ( $this->typeno_sqltype as $k => $v ) {
  6097. $this->sqltype_typeno[$v] = $k;
  6098. }
  6099.  
  6100. $this->adapter = $adapter;
  6101. }
  6102.  
  6103. /**
  6104. * This method returns the datatype to be used for primary key IDS and
  6105. * foreign keys. Returns one if the data type constants.
  6106. *
  6107. * @return integer $const data type to be used for IDS.
  6108. */
  6109. public function getTypeForID()
  6110. {
  6111. return self::C_DATATYPE_INTEGER;
  6112. }
  6113.  
  6114. /**
  6115. * @see QueryWriter::scanType
  6116. */
  6117. public function scanType( $value, $flagSpecial = FALSE )
  6118. {
  6119. $this->svalue = $value;
  6120.  
  6121. if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
  6122. if ( $value === INF ) return self::C_DATATYPE_TEXT;
  6123.  
  6124. if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
  6125.  
  6126. if ( $value === TRUE || $value === FALSE ) return self::C_DATATYPE_INTEGER;
  6127.  
  6128. if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
  6129.  
  6130. if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
  6131. || preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
  6132. || preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
  6133. ) {
  6134. return self::C_DATATYPE_NUMERIC;
  6135. }
  6136.  
  6137. return self::C_DATATYPE_TEXT;
  6138. }
  6139.  
  6140. /**
  6141. * @see QueryWriter::addColumn
  6142. */
  6143. public function addColumn( $table, $column, $type )
  6144. {
  6145. $column = $this->check( $column );
  6146. $table = $this->check( $table );
  6147. $type = $this->typeno_sqltype[$type];
  6148.  
  6149. $this->adapter->exec( "ALTER TABLE `$table` ADD `$column` $type " );
  6150. }
  6151.  
  6152. /**
  6153. * @see QueryWriter::code
  6154. */
  6155. public function code( $typedescription, $includeSpecials = FALSE )
  6156. {
  6157. $r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
  6158.  
  6159. return $r;
  6160. }
  6161.  
  6162. /**
  6163. * @see QueryWriter::widenColumn
  6164. */
  6165. public function widenColumn( $type, $column, $datatype )
  6166. {
  6167. $t = $this->getTable( $type );
  6168.  
  6169. $t['columns'][$column] = $this->typeno_sqltype[$datatype];
  6170.  
  6171. $this->putTable( $t );
  6172. }
  6173.  
  6174. /**
  6175. * @see QueryWriter::getTables();
  6176. */
  6177. public function getTables()
  6178. {
  6179. return $this->adapter->getCol( "SELECT name FROM sqlite_master
  6180. WHERE type='table' AND name!='sqlite_sequence';" );
  6181. }
  6182.  
  6183. /**
  6184. * @see QueryWriter::createTable
  6185. */
  6186. public function createTable( $table )
  6187. {
  6188. $table = $this->esc( $table );
  6189.  
  6190. $sql = "CREATE TABLE $table ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ";
  6191.  
  6192. $this->adapter->exec( $sql );
  6193. }
  6194.  
  6195. /**
  6196. * @see QueryWriter::getColumns
  6197. */
  6198. public function getColumns( $table )
  6199. {
  6200. $table = $this->esc( $table, TRUE );
  6201.  
  6202. $columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
  6203.  
  6204. $columns = array();
  6205. foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
  6206.  
  6207. return $columns;
  6208. }
  6209.  
  6210. /**
  6211. * @see QueryWriter::addUniqueIndex
  6212. */
  6213. public function addUniqueConstraint( $type, $properties )
  6214. {
  6215. $tableNoQ = $this->esc( $type, TRUE );
  6216. $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
  6217. $t = $this->getTable( $type );
  6218. $t['indexes'][$name] = array( 'name' => $name );
  6219. try {
  6220. $this->putTable( $t );
  6221. } catch( SQLException $e ) {
  6222. return FALSE;
  6223. }
  6224. return TRUE;
  6225. }
  6226.  
  6227. /**
  6228. * @see QueryWriter::sqlStateIn
  6229. */
  6230. public function sqlStateIn( $state, $list )
  6231. {
  6232. $stateMap = array(
  6233. 'HY000' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  6234. '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
  6235. );
  6236.  
  6237. return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
  6238. }
  6239.  
  6240. /**
  6241. * @see QueryWriter::addIndex
  6242. */
  6243. public function addIndex( $type, $name, $column )
  6244. {
  6245. $columns = $this->getColumns( $type );
  6246. if ( !isset( $columns[$column] ) ) return FALSE;
  6247.  
  6248. $table = $this->esc( $type );
  6249. $name = preg_replace( '/\W/', '', $name );
  6250. $column = $this->esc( $column, TRUE );
  6251.  
  6252. try {
  6253. $t = $this->getTable( $type );
  6254. $t['indexes'][$name] = array( 'name' => $column );
  6255. $this->putTable( $t );
  6256. return TRUE;
  6257. } catch( SQLException $exception ) {
  6258. return FALSE;
  6259. }
  6260. }
  6261.  
  6262. /**
  6263. * @see QueryWriter::wipe
  6264. */
  6265. public function wipe( $type )
  6266. {
  6267. $table = $this->esc( $type );
  6268.  
  6269. $this->adapter->exec( "DELETE FROM $table " );
  6270. }
  6271.  
  6272. /**
  6273. * @see QueryWriter::addFK
  6274. */
  6275. public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
  6276. {
  6277. return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
  6278. }
  6279.  
  6280. /**
  6281. * @see QueryWriter::wipeAll
  6282. */
  6283. public function wipeAll()
  6284. {
  6285. $this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
  6286.  
  6287. foreach ( $this->getTables() as $t ) {
  6288. try {
  6289. $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
  6290. } catch ( SQLException $e ) {
  6291. }
  6292.  
  6293. try {
  6294. $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
  6295. } catch ( SQLException $e ) {
  6296. }
  6297. }
  6298.  
  6299. $this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
  6300. }
  6301. }
  6302. }
  6303.  
  6304. namespace RedBeanPHP\QueryWriter {
  6305.  
  6306. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  6307. use RedBeanPHP\QueryWriter as QueryWriter;
  6308. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  6309. use RedBeanPHP\Adapter as Adapter;
  6310. use RedBeanPHP\RedException\SQL as SQLException;
  6311.  
  6312. /**
  6313. * RedBeanPHP PostgreSQL Query Writer.
  6314. * This is a QueryWriter class for RedBeanPHP.
  6315. * This QueryWriter provides support for the PostgreSQL database platform.
  6316. *
  6317. * @file RedBeanPHP/QueryWriter/PostgreSQL.php
  6318. * @author Gabor de Mooij and the RedBeanPHP Community
  6319. * @license BSD/GPLv2
  6320. *
  6321. * @copyright
  6322. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  6323. * This source file is subject to the BSD/GPLv2 License that is bundled
  6324. * with this source code in the file license.txt.
  6325. */
  6326. class PostgreSQL extends AQueryWriter implements QueryWriter
  6327. {
  6328. /**
  6329. * Data types
  6330. */
  6331. const C_DATATYPE_INTEGER = 0;
  6332. const C_DATATYPE_DOUBLE = 1;
  6333. const C_DATATYPE_TEXT = 3;
  6334. const C_DATATYPE_SPECIAL_DATE = 80;
  6335. const C_DATATYPE_SPECIAL_DATETIME = 81;
  6336. const C_DATATYPE_SPECIAL_POINT = 90;
  6337. const C_DATATYPE_SPECIAL_LSEG = 91;
  6338. const C_DATATYPE_SPECIAL_CIRCLE = 92;
  6339. const C_DATATYPE_SPECIAL_MONEY = 93;
  6340. const C_DATATYPE_SPECIAL_POLYGON = 94;
  6341. const C_DATATYPE_SPECIAL_MONEY2 = 95; //Numbers only money, i.e. fixed point numeric
  6342. const C_DATATYPE_SPECIAL_JSON = 96; //JSON support (only manual)
  6343. const C_DATATYPE_SPECIFIED = 99;
  6344.  
  6345. /**
  6346. * @var DBAdapter
  6347. */
  6348. protected $adapter;
  6349.  
  6350. /**
  6351. * @var string
  6352. */
  6353. protected $quoteCharacter = '"';
  6354.  
  6355. /**
  6356. * @var string
  6357. */
  6358. protected $defaultValue = 'DEFAULT';
  6359.  
  6360. /**
  6361. * Returns the insert suffix SQL Snippet
  6362. *
  6363. * @param string $table table
  6364. *
  6365. * @return string $sql SQL Snippet
  6366. */
  6367. protected function getInsertSuffix( $table )
  6368. {
  6369. return 'RETURNING id ';
  6370. }
  6371.  
  6372. /**
  6373. * @see AQueryWriter::getKeyMapForType
  6374. */
  6375. protected function getKeyMapForType( $type )
  6376. {
  6377. $table = $this->esc( $type, TRUE );
  6378. $keys = $this->adapter->get( '
  6379. SELECT
  6380. information_schema.key_column_usage.constraint_name AS "name",
  6381. information_schema.key_column_usage.column_name AS "from",
  6382. information_schema.constraint_table_usage.table_name AS "table",
  6383. information_schema.constraint_column_usage.column_name AS "to",
  6384. information_schema.referential_constraints.update_rule AS "on_update",
  6385. information_schema.referential_constraints.delete_rule AS "on_delete"
  6386. FROM information_schema.key_column_usage
  6387. INNER JOIN information_schema.constraint_table_usage
  6388. ON (
  6389. information_schema.key_column_usage.constraint_name = information_schema.constraint_table_usage.constraint_name
  6390. AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_table_usage.constraint_schema
  6391. AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_table_usage.constraint_catalog
  6392. )
  6393. INNER JOIN information_schema.constraint_column_usage
  6394. ON (
  6395. information_schema.key_column_usage.constraint_name = information_schema.constraint_column_usage.constraint_name
  6396. AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_column_usage.constraint_schema
  6397. AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_column_usage.constraint_catalog
  6398. )
  6399. INNER JOIN information_schema.referential_constraints
  6400. ON (
  6401. information_schema.key_column_usage.constraint_name = information_schema.referential_constraints.constraint_name
  6402. AND information_schema.key_column_usage.constraint_schema = information_schema.referential_constraints.constraint_schema
  6403. AND information_schema.key_column_usage.constraint_catalog = information_schema.referential_constraints.constraint_catalog
  6404. )
  6405. WHERE
  6406. information_schema.key_column_usage.table_catalog = current_database()
  6407. AND information_schema.key_column_usage.table_schema = ANY( current_schemas( FALSE ) )
  6408. AND information_schema.key_column_usage.table_name = ?
  6409. ', array( $type ) );
  6410. $keyInfoList = array();
  6411. foreach ( $keys as $k ) {
  6412. $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
  6413. $keyInfoList[$label] = array(
  6414. 'name' => $k['name'],
  6415. 'from' => $k['from'],
  6416. 'table' => $k['table'],
  6417. 'to' => $k['to'],
  6418. 'on_update' => $k['on_update'],
  6419. 'on_delete' => $k['on_delete']
  6420. );
  6421. }
  6422. return $keyInfoList;
  6423. }
  6424.  
  6425. /**
  6426. * Constructor
  6427. *
  6428. * @param Adapter $adapter Database Adapter
  6429. */
  6430. public function __construct( Adapter $adapter )
  6431. {
  6432. $this->typeno_sqltype = array(
  6433. self::C_DATATYPE_INTEGER => ' integer ',
  6434. self::C_DATATYPE_DOUBLE => ' double precision ',
  6435. self::C_DATATYPE_TEXT => ' text ',
  6436. self::C_DATATYPE_SPECIAL_DATE => ' date ',
  6437. self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
  6438. self::C_DATATYPE_SPECIAL_POINT => ' point ',
  6439. self::C_DATATYPE_SPECIAL_LSEG => ' lseg ',
  6440. self::C_DATATYPE_SPECIAL_CIRCLE => ' circle ',
  6441. self::C_DATATYPE_SPECIAL_MONEY => ' money ',
  6442. self::C_DATATYPE_SPECIAL_MONEY2 => ' numeric(10,2) ',
  6443. self::C_DATATYPE_SPECIAL_POLYGON => ' polygon ',
  6444. self::C_DATATYPE_SPECIAL_JSON => ' json ',
  6445. );
  6446.  
  6447. $this->sqltype_typeno = array();
  6448.  
  6449. foreach ( $this->typeno_sqltype as $k => $v ) {
  6450. $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
  6451. }
  6452.  
  6453. $this->adapter = $adapter;
  6454. }
  6455.  
  6456. /**
  6457. * This method returns the datatype to be used for primary key IDS and
  6458. * foreign keys. Returns one if the data type constants.
  6459. *
  6460. * @return integer
  6461. */
  6462. public function getTypeForID()
  6463. {
  6464. return self::C_DATATYPE_INTEGER;
  6465. }
  6466.  
  6467. /**
  6468. * @see QueryWriter::getTables
  6469. */
  6470. public function getTables()
  6471. {
  6472. return $this->adapter->getCol( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ANY( current_schemas( FALSE ) )' );
  6473. }
  6474.  
  6475. /**
  6476. * @see QueryWriter::createTable
  6477. */
  6478. public function createTable( $table )
  6479. {
  6480. $table = $this->esc( $table );
  6481.  
  6482. $this->adapter->exec( " CREATE TABLE $table (id SERIAL PRIMARY KEY); " );
  6483. }
  6484.  
  6485. /**
  6486. * @see QueryWriter::getColumns
  6487. */
  6488. public function getColumns( $table )
  6489. {
  6490. $table = $this->esc( $table, TRUE );
  6491.  
  6492. $columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table' AND table_schema = ANY( current_schemas( FALSE ) )" );
  6493.  
  6494. $columns = array();
  6495. foreach ( $columnsRaw as $r ) {
  6496. $columns[$r['column_name']] = $r['data_type'];
  6497. }
  6498.  
  6499. return $columns;
  6500. }
  6501.  
  6502. /**
  6503. * @see QueryWriter::scanType
  6504. */
  6505. public function scanType( $value, $flagSpecial = FALSE )
  6506. {
  6507. $this->svalue = $value;
  6508.  
  6509. if ( $value === INF ) return self::C_DATATYPE_TEXT;
  6510.  
  6511. if ( $flagSpecial && $value ) {
  6512. if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
  6513. return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
  6514. }
  6515.  
  6516. if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
  6517. return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
  6518. }
  6519.  
  6520. if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
  6521. return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
  6522. }
  6523.  
  6524. if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
  6525. return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
  6526. }
  6527.  
  6528. if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
  6529. return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
  6530. }
  6531.  
  6532. if ( preg_match( '/^\((\([\d\.]+,[\d\.]+\),?)+\)$/', $value ) ) {
  6533. return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON;
  6534. }
  6535.  
  6536. if ( preg_match( '/^\-?(\$|€|¥|£)[\d,\.]+$/', $value ) ) {
  6537. return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
  6538. }
  6539.  
  6540. if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
  6541. return PostgreSQL::C_DATATYPE_SPECIAL_MONEY2;
  6542. }
  6543. }
  6544.  
  6545. if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
  6546.  
  6547. if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
  6548.  
  6549. if ( $value === FALSE || $value === TRUE || $value === NULL || ( is_numeric( $value )
  6550. && AQueryWriter::canBeTreatedAsInt( $value )
  6551. && $value < 2147483648
  6552. && $value > -2147483648 )
  6553. ) {
  6554. return self::C_DATATYPE_INTEGER;
  6555. } elseif ( is_numeric( $value ) ) {
  6556. return self::C_DATATYPE_DOUBLE;
  6557. } else {
  6558. return self::C_DATATYPE_TEXT;
  6559. }
  6560. }
  6561.  
  6562. /**
  6563. * @see QueryWriter::code
  6564. */
  6565. public function code( $typedescription, $includeSpecials = FALSE )
  6566. {
  6567. $r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
  6568.  
  6569. if ( $includeSpecials ) return $r;
  6570.  
  6571. if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
  6572. return self::C_DATATYPE_SPECIFIED;
  6573. }
  6574.  
  6575. return $r;
  6576. }
  6577.  
  6578. /**
  6579. * @see QueryWriter::widenColumn
  6580. */
  6581. public function widenColumn( $type, $column, $datatype )
  6582. {
  6583. $table = $type;
  6584. $type = $datatype;
  6585.  
  6586. $table = $this->esc( $table );
  6587. $column = $this->esc( $column );
  6588.  
  6589. $newtype = $this->typeno_sqltype[$type];
  6590.  
  6591. $this->adapter->exec( "ALTER TABLE $table \n\t ALTER COLUMN $column TYPE $newtype " );
  6592. }
  6593.  
  6594. /**
  6595. * @see QueryWriter::addUniqueIndex
  6596. */
  6597. public function addUniqueConstraint( $type, $properties )
  6598. {
  6599. $tableNoQ = $this->esc( $type, TRUE );
  6600. $columns = array();
  6601. foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
  6602. $table = $this->esc( $type );
  6603. sort( $columns ); //else we get multiple indexes due to order-effects
  6604. $name = "UQ_" . sha1( $table . implode( ',', $columns ) );
  6605. $sql = "ALTER TABLE {$table}
  6606. ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
  6607. try {
  6608. $this->adapter->exec( $sql );
  6609. } catch( SQLException $e ) {
  6610. return FALSE;
  6611. }
  6612. return TRUE;
  6613. }
  6614.  
  6615. /**
  6616. * @see QueryWriter::sqlStateIn
  6617. */
  6618. public function sqlStateIn( $state, $list )
  6619. {
  6620. $stateMap = array(
  6621. '42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  6622. '42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  6623. '23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
  6624. );
  6625.  
  6626. return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
  6627. }
  6628.  
  6629. /**
  6630. * @see QueryWriter::addIndex
  6631. */
  6632. public function addIndex( $type, $name, $property )
  6633. {
  6634. $table = $this->esc( $type );
  6635. $name = preg_replace( '/\W/', '', $name );
  6636. $column = $this->esc( $property );
  6637.  
  6638. try {
  6639. $this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " );
  6640. return TRUE;
  6641. } catch ( SQLException $e ) {
  6642. return FALSE;
  6643. }
  6644. }
  6645.  
  6646. /**
  6647. * @see QueryWriter::addFK
  6648. */
  6649. public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
  6650. {
  6651. $table = $this->esc( $type );
  6652. $targetTable = $this->esc( $targetType );
  6653. $field = $this->esc( $property );
  6654. $targetField = $this->esc( $targetProperty );
  6655. $tableNoQ = $this->esc( $type, TRUE );
  6656. $fieldNoQ = $this->esc( $property, TRUE );
  6657. if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
  6658. try{
  6659. $delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
  6660. $this->adapter->exec( "ALTER TABLE {$table}
  6661. ADD FOREIGN KEY ( {$field} ) REFERENCES {$targetTable}
  6662. ({$targetField}) ON DELETE {$delRule} ON UPDATE {$delRule} DEFERRABLE ;" );
  6663. return TRUE;
  6664. } catch ( SQLException $e ) {
  6665. return FALSE;
  6666. }
  6667. }
  6668.  
  6669. /**
  6670. * @see QueryWriter::wipeAll
  6671. */
  6672. public function wipeAll()
  6673. {
  6674. $this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
  6675.  
  6676. foreach ( $this->getTables() as $t ) {
  6677. $t = $this->esc( $t );
  6678. //Some plugins (PostGIS have unremovable tables/views), avoid exceptions.
  6679. try { $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " ); }catch( \Exception $e ) {}
  6680. }
  6681.  
  6682. $this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
  6683. }
  6684. }
  6685. }
  6686.  
  6687. namespace RedBeanPHP {
  6688.  
  6689. /**
  6690. * RedBean\Exception Base.
  6691. * Represents the base class for RedBeanPHP\Exceptions.
  6692. *
  6693. * @file RedBeanPHP/Exception.php
  6694. * @author Gabor de Mooij and the RedBeanPHP Community
  6695. * @license BSD/GPLv2
  6696. *
  6697. * @copyright
  6698. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6699. * This source file is subject to the BSD/GPLv2 License that is bundled
  6700. * with this source code in the file license.txt.
  6701. */
  6702. class RedException extends \Exception
  6703. {
  6704. }
  6705. }
  6706.  
  6707. namespace RedBeanPHP\RedException {
  6708.  
  6709. use RedBeanPHP\RedException as RedException;
  6710.  
  6711. /**
  6712. * SQL Exception.
  6713. * Represents a generic database exception independent of the underlying driver.
  6714. *
  6715. * @file RedBeanPHP/RedException/SQL.php
  6716. * @author Gabor de Mooij and the RedBeanPHP Community
  6717. * @license BSD/GPLv2
  6718. *
  6719. * @copyright
  6720. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  6721. * This source file is subject to the BSD/GPLv2 License that is bundled
  6722. * with this source code in the file license.txt.
  6723. */
  6724. class SQL extends RedException
  6725. {
  6726. /**
  6727. * @var string
  6728. */
  6729. private $sqlState;
  6730.  
  6731. /**
  6732. * Returns an ANSI-92 compliant SQL state.
  6733. *
  6734. * @return string
  6735. */
  6736. public function getSQLState()
  6737. {
  6738. return $this->sqlState;
  6739. }
  6740.  
  6741. /**
  6742. * Returns the raw SQL STATE, possibly compliant with
  6743. * ANSI SQL error codes - but this depends on database driver.
  6744. *
  6745. * @param string $sqlState SQL state error code
  6746. *
  6747. * @return void
  6748. */
  6749. public function setSQLState( $sqlState )
  6750. {
  6751. $this->sqlState = $sqlState;
  6752. }
  6753.  
  6754. /**
  6755. * To String prints both code and SQL state.
  6756. *
  6757. * @return string
  6758. */
  6759. public function __toString()
  6760. {
  6761. return '[' . $this->getSQLState() . '] - ' . $this->getMessage()."\n".
  6762. 'trace: ' . $this->getTraceAsString();
  6763. }
  6764. }
  6765. }
  6766.  
  6767. namespace RedBeanPHP {
  6768.  
  6769. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  6770. use RedBeanPHP\QueryWriter as QueryWriter;
  6771. use RedBeanPHP\BeanHelper as BeanHelper;
  6772. use RedBeanPHP\RedException\SQL as SQLException;
  6773. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  6774. use RedBeanPHP\Cursor as Cursor;
  6775. use RedBeanPHP\Cursor\NullCursor as NullCursor;
  6776.  
  6777. /**
  6778. * Abstract Repository.
  6779. *
  6780. * OODB manages two repositories, a fluid one that
  6781. * adjust the database schema on-the-fly to accomodate for
  6782. * new bean types (tables) and new properties (columns) and
  6783. * a frozen one for use in a production environment. OODB
  6784. * allows you to swap the repository instances using the freeze()
  6785. * method.
  6786. *
  6787. * @file RedBeanPHP/Repository.php
  6788. * @author Gabor de Mooij and the RedBeanPHP community
  6789. * @license BSD/GPLv2
  6790. *
  6791. * @copyright
  6792. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6793. * This source file is subject to the BSD/GPLv2 License that is bundled
  6794. * with this source code in the file license.txt.
  6795. */
  6796. abstract class Repository
  6797. {
  6798. /**
  6799. * @var array
  6800. */
  6801. protected $stash = NULL;
  6802.  
  6803. /*
  6804. * @var integer
  6805. */
  6806. protected $nesting = 0;
  6807.  
  6808. /**
  6809. * @var DBAdapter
  6810. */
  6811. protected $writer;
  6812.  
  6813. /**
  6814. * Stores a bean and its lists in one run.
  6815. *
  6816. * @param OODBBean $bean bean to process
  6817. *
  6818. * @return void
  6819. */
  6820. protected function storeBeanWithLists( OODBBean $bean )
  6821. {
  6822. $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups
  6823. foreach ( $bean as $property => $value ) {
  6824. $value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value;
  6825. if ( $value instanceof OODBBean ) {
  6826. $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value );
  6827. $bean->setMeta("sys.typeof.{$property}", $value->getMeta('type'));
  6828. } elseif ( is_array( $value ) ) {
  6829. $originals = $bean->moveMeta( 'sys.shadow.' . $property, array() );
  6830. if ( strpos( $property, 'own' ) === 0 ) {
  6831. list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue );
  6832. $listName = lcfirst( substr( $property, 3 ) );
  6833. if ($bean->moveMeta( 'sys.exclusive-'. $listName ) ) {
  6834. OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE );
  6835. OODBBean::setMetaAll( $ownAdditions, 'sys.buildcommand.fkdependson', $bean->getMeta( 'type' ) );
  6836. }
  6837. unset( $bean->$property );
  6838. } elseif ( strpos( $property, 'shared' ) === 0 ) {
  6839. list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue );
  6840. unset( $bean->$property );
  6841. }
  6842. }
  6843. }
  6844. $this->storeBean( $bean );
  6845. $this->processTrashcan( $bean, $ownTrashcan );
  6846. $this->processAdditions( $bean, $ownAdditions );
  6847. $this->processResidue( $ownresidue );
  6848. $this->processSharedTrashcan( $bean, $sharedTrashcan );
  6849. $this->processSharedAdditions( $bean, $sharedAdditions );
  6850. $this->processSharedResidue( $bean, $sharedresidue );
  6851. }
  6852.  
  6853. /**
  6854. * Process groups. Internal function. Processes different kind of groups for
  6855. * storage function. Given a list of original beans and a list of current beans,
  6856. * this function calculates which beans remain in the list (residue), which
  6857. * have been deleted (are in the trashcan) and which beans have been added
  6858. * (additions).
  6859. *
  6860. * @param array $originals originals
  6861. * @param array $current the current beans
  6862. * @param array $additions beans that have been added
  6863. * @param array $trashcan beans that have been deleted
  6864. * @param array $residue beans that have been left untouched
  6865. *
  6866. * @return array
  6867. */
  6868. protected function processGroups( $originals, $current, $additions, $trashcan, $residue )
  6869. {
  6870. return array(
  6871. array_merge( $additions, array_diff( $current, $originals ) ),
  6872. array_merge( $trashcan, array_diff( $originals, $current ) ),
  6873. array_merge( $residue, array_intersect( $current, $originals ) )
  6874. );
  6875. }
  6876.  
  6877. /**
  6878. * Processes an embedded bean.
  6879. *
  6880. * @param OODBBean|SimpleModel $embeddedBean the bean or model
  6881. *
  6882. * @return integer
  6883. */
  6884. protected function prepareEmbeddedBean( $embeddedBean )
  6885. {
  6886. if ( !$embeddedBean->id || $embeddedBean->getMeta( 'tainted' ) ) {
  6887. $this->store( $embeddedBean );
  6888. }
  6889.  
  6890. return $embeddedBean->id;
  6891. }
  6892.  
  6893. /**
  6894. * Processes a list of beans from a bean. A bean may contain lists. This
  6895. * method handles shared addition lists; i.e. the $bean->sharedObject properties.
  6896. *
  6897. * @param OODBBean $bean the bean
  6898. * @param array $sharedAdditions list with shared additions
  6899. *
  6900. * @return void
  6901. */
  6902. protected function processSharedAdditions( $bean, $sharedAdditions )
  6903. {
  6904. foreach ( $sharedAdditions as $addition ) {
  6905. if ( $addition instanceof OODBBean ) {
  6906. $this->oodb->getAssociationManager()->associate( $addition, $bean );
  6907. } else {
  6908. throw new RedException( 'Array may only contain OODBBeans' );
  6909. }
  6910. }
  6911. }
  6912.  
  6913. /**
  6914. * Processes a list of beans from a bean. A bean may contain lists. This
  6915. * method handles own lists; i.e. the $bean->ownObject properties.
  6916. * A residue is a bean in an own-list that stays where it is. This method
  6917. * checks if there have been any modification to this bean, in that case
  6918. * the bean is stored once again, otherwise the bean will be left untouched.
  6919. *
  6920. * @param OODBBean $bean bean tor process
  6921. * @param array $ownresidue list to process
  6922. *
  6923. * @return void
  6924. */
  6925. protected function processResidue( $ownresidue )
  6926. {
  6927. foreach ( $ownresidue as $residue ) {
  6928. if ( $residue->getMeta( 'tainted' ) ) {
  6929. $this->store( $residue );
  6930. }
  6931. }
  6932. }
  6933.  
  6934. /**
  6935. * Processes a list of beans from a bean. A bean may contain lists. This
  6936. * method handles own lists; i.e. the $bean->ownObject properties.
  6937. * A trash can bean is a bean in an own-list that has been removed
  6938. * (when checked with the shadow). This method
  6939. * checks if the bean is also in the dependency list. If it is the bean will be removed.
  6940. * If not, the connection between the bean and the owner bean will be broken by
  6941. * setting the ID to NULL.
  6942. *
  6943. * @param OODBBean $bean bean to process
  6944. * @param array $ownTrashcan list to process
  6945. *
  6946. * @return void
  6947. */
  6948. protected function processTrashcan( $bean, $ownTrashcan )
  6949. {
  6950. foreach ( $ownTrashcan as $trash ) {
  6951.  
  6952. $myFieldLink = $bean->getMeta( 'type' ) . '_id';
  6953. $alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) );
  6954. if ( $alias ) $myFieldLink = $alias . '_id';
  6955.  
  6956. if ( $trash->getMeta( 'sys.garbage' ) === true ) {
  6957. $this->trash( $trash );
  6958. } else {
  6959. $trash->$myFieldLink = NULL;
  6960. $this->store( $trash );
  6961. }
  6962. }
  6963. }
  6964.  
  6965. /**
  6966. * Unassociates the list items in the trashcan.
  6967. *
  6968. * @param OODBBean $bean bean to process
  6969. * @param array $sharedTrashcan list to process
  6970. *
  6971. * @return void
  6972. */
  6973. protected function processSharedTrashcan( $bean, $sharedTrashcan )
  6974. {
  6975. foreach ( $sharedTrashcan as $trash ) {
  6976. $this->oodb->getAssociationManager()->unassociate( $trash, $bean );
  6977. }
  6978. }
  6979.  
  6980. /**
  6981. * Stores all the beans in the residue group.
  6982. *
  6983. * @param OODBBean $bean bean to process
  6984. * @param array $sharedresidue list to process
  6985. *
  6986. * @return void
  6987. */
  6988. protected function processSharedResidue( $bean, $sharedresidue )
  6989. {
  6990. foreach ( $sharedresidue as $residue ) {
  6991. $this->store( $residue );
  6992. }
  6993. }
  6994.  
  6995. /**
  6996. * Determines whether the bean has 'loaded lists' or
  6997. * 'loaded embedded beans' that need to be processed
  6998. * by the store() method.
  6999. *
  7000. * @param OODBBean $bean bean to be examined
  7001. *
  7002. * @return boolean
  7003. */
  7004. protected function hasListsOrObjects( OODBBean $bean )
  7005. {
  7006. $processLists = FALSE;
  7007. foreach ( $bean as $value ) {
  7008. if ( is_array( $value ) || is_object( $value ) ) {
  7009. $processLists = TRUE;
  7010. break;
  7011. }
  7012. }
  7013.  
  7014. return $processLists;
  7015. }
  7016.  
  7017. /**
  7018. * Converts an embedded bean to an ID, removed the bean property and
  7019. * stores the bean in the embedded beans array.
  7020. *
  7021. * @param array $embeddedBeans destination array for embedded bean
  7022. * @param OODBBean $bean target bean to process
  7023. * @param string $property property that contains the embedded bean
  7024. * @param OODBBean $value embedded bean itself
  7025. *
  7026. * @return void
  7027. */
  7028. protected function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value )
  7029. {
  7030. $linkField = $property . '_id';
  7031. $id = $this->prepareEmbeddedBean( $value );
  7032. if ($bean->$linkField != $id) $bean->$linkField = $id;
  7033. $bean->setMeta( 'cast.' . $linkField, 'id' );
  7034. $embeddedBeans[$linkField] = $value;
  7035. unset( $bean->$property );
  7036. }
  7037.  
  7038. /**
  7039. * Constructor, requires a query writer.
  7040. * Creates a new instance of the bean respository class.
  7041. *
  7042. * @param QueryWriter $writer the Query Writer to use for this repository
  7043. *
  7044. * @return void
  7045. */
  7046. public function __construct( OODB $oodb, QueryWriter $writer )
  7047. {
  7048. $this->writer = $writer;
  7049. $this->oodb = $oodb;
  7050. }
  7051.  
  7052. /**
  7053. * Checks whether a OODBBean bean is valid.
  7054. * If the type is not valid or the ID is not valid it will
  7055. * throw an exception: Security.
  7056. *
  7057. * @param OODBBean $bean the bean that needs to be checked
  7058. *
  7059. * @return void
  7060. */
  7061. public function check( OODBBean $bean )
  7062. {
  7063. //Is all meta information present?
  7064. if ( !isset( $bean->id ) ) {
  7065. throw new RedException( 'Bean has incomplete Meta Information id ' );
  7066. }
  7067. if ( !( $bean->getMeta( 'type' ) ) ) {
  7068. throw new RedException( 'Bean has incomplete Meta Information II' );
  7069. }
  7070. //Pattern of allowed characters
  7071. $pattern = '/[^a-z0-9_]/i';
  7072. //Does the type contain invalid characters?
  7073. if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) {
  7074. throw new RedException( 'Bean Type is invalid' );
  7075. }
  7076. //Are the properties and values valid?
  7077. foreach ( $bean as $prop => $value ) {
  7078. if (
  7079. is_array( $value )
  7080. || ( is_object( $value ) )
  7081. ) {
  7082. throw new RedException( "Invalid Bean value: property $prop" );
  7083. } else if (
  7084. strlen( $prop ) < 1
  7085. || preg_match( $pattern, $prop )
  7086. ) {
  7087. throw new RedException( "Invalid Bean property: property $prop" );
  7088. }
  7089. }
  7090. }
  7091.  
  7092. /**
  7093. * Searches the database for a bean that matches conditions $conditions and sql $addSQL
  7094. * and returns an array containing all the beans that have been found.
  7095. *
  7096. * Conditions need to take form:
  7097. *
  7098. * <code>
  7099. * array(
  7100. * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
  7101. * 'PROPERTY' => array( POSSIBLE VALUES... )
  7102. * );
  7103. * </code>
  7104. *
  7105. * All conditions are glued together using the AND-operator, while all value lists
  7106. * are glued using IN-operators thus acting as OR-conditions.
  7107. *
  7108. * Note that you can use property names; the columns will be extracted using the
  7109. * appropriate bean formatter.
  7110. *
  7111. * @param string $type type of beans you are looking for
  7112. * @param array $conditions list of conditions
  7113. * @param string $addSQL SQL to be used in query
  7114. * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
  7115. *
  7116. * @return array
  7117. */
  7118. public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
  7119. {
  7120. //for backward compatibility, allow mismatch arguments:
  7121. if ( is_array( $sql ) ) {
  7122. if ( isset( $sql[1] ) ) {
  7123. $bindings = $sql[1];
  7124. }
  7125. $sql = $sql[0];
  7126. }
  7127. try {
  7128. $beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) );
  7129.  
  7130. return $beans;
  7131. } catch ( SQLException $exception ) {
  7132. $this->handleException( $exception );
  7133. }
  7134.  
  7135. return array();
  7136. }
  7137.  
  7138. /**
  7139. * Finds a BeanCollection.
  7140. *
  7141. * @param string $type type of beans you are looking for
  7142. * @param string $sql SQL to be used in query
  7143. * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
  7144. *
  7145. * @return BeanCollection
  7146. */
  7147. public function findCollection( $type, $sql, $bindings = array() )
  7148. {
  7149. try {
  7150. $cursor = $this->writer->queryRecordWithCursor( $type, $sql, $bindings );
  7151. return new BeanCollection( $type, $this, $cursor );
  7152. } catch ( SQLException $exception ) {
  7153. $this->handleException( $exception );
  7154. }
  7155. return new BeanCollection( $type, $this, new NullCursor );
  7156. }
  7157.  
  7158. /**
  7159. * Stores a bean in the database. This method takes a
  7160. * OODBBean Bean Object $bean and stores it
  7161. * in the database. If the database schema is not compatible
  7162. * with this bean and RedBean runs in fluid mode the schema
  7163. * will be altered to store the bean correctly.
  7164. * If the database schema is not compatible with this bean and
  7165. * RedBean runs in frozen mode it will throw an exception.
  7166. * This function returns the primary key ID of the inserted
  7167. * bean.
  7168. *
  7169. * The return value is an integer if possible. If it is not possible to
  7170. * represent the value as an integer a string will be returned. We use
  7171. * explicit casts instead of functions to preserve performance
  7172. * (0.13 vs 0.28 for 10000 iterations on Core i3).
  7173. *
  7174. * @param OODBBean|SimpleModel $bean bean to store
  7175. *
  7176. * @return integer|string
  7177. */
  7178. public function store( $bean )
  7179. {
  7180. $processLists = $this->hasListsOrObjects( $bean );
  7181. if ( !$processLists && !$bean->getMeta( 'tainted' ) ) {
  7182. return $bean->getID(); //bail out!
  7183. }
  7184. $this->oodb->signal( 'update', $bean );
  7185. $processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model!
  7186. if ( $processLists ) {
  7187. $this->storeBeanWithLists( $bean );
  7188. } else {
  7189. $this->storeBean( $bean );
  7190. }
  7191. $this->oodb->signal( 'after_update', $bean );
  7192.  
  7193. return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id;
  7194. }
  7195.  
  7196. /**
  7197. * Returns an array of beans. Pass a type and a series of ids and
  7198. * this method will bring you the corresponding beans.
  7199. *
  7200. * important note: Because this method loads beans using the load()
  7201. * function (but faster) it will return empty beans with ID 0 for
  7202. * every bean that could not be located. The resulting beans will have the
  7203. * passed IDs as their keys.
  7204. *
  7205. * @param string $type type of beans
  7206. * @param array $ids ids to load
  7207. *
  7208. * @return array
  7209. */
  7210. public function batch( $type, $ids )
  7211. {
  7212. if ( !$ids ) {
  7213. return array();
  7214. }
  7215. $collection = array();
  7216. try {
  7217. $rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) );
  7218. } catch ( SQLException $e ) {
  7219. $this->handleException( $e );
  7220. $rows = FALSE;
  7221. }
  7222. $this->stash[$this->nesting] = array();
  7223. if ( !$rows ) {
  7224. return array();
  7225. }
  7226. foreach ( $rows as $row ) {
  7227. $this->stash[$this->nesting][$row['id']] = $row;
  7228. }
  7229. foreach ( $ids as $id ) {
  7230. $collection[$id] = $this->load( $type, $id );
  7231. }
  7232. $this->stash[$this->nesting] = NULL;
  7233.  
  7234. return $collection;
  7235. }
  7236.  
  7237. /**
  7238. * This is a convenience method; it converts database rows
  7239. * (arrays) into beans. Given a type and a set of rows this method
  7240. * will return an array of beans of the specified type loaded with
  7241. * the data fields provided by the result set from the database.
  7242. *
  7243. * New in 4.3.2: meta mask. The meta mask is a special mask to send
  7244. * data from raw result rows to the meta store of the bean. This is
  7245. * useful for bundling additional information with custom queries.
  7246. * Values of every column whos name starts with $mask will be
  7247. * transferred to the meta section of the bean under key 'data.bundle'.
  7248. *
  7249. * @param string $type type of beans you would like to have
  7250. * @param array $rows rows from the database result
  7251. * @param string $mask meta mask to apply (optional)
  7252. *
  7253. * @return array
  7254. */
  7255. public function convertToBeans( $type, $rows, $mask = NULL )
  7256. {
  7257. $masklen = 0;
  7258. if ( $mask !== NULL ) $masklen = mb_strlen( $mask );
  7259.  
  7260. $collection = array();
  7261. $this->stash[$this->nesting] = array();
  7262. foreach ( $rows as $row ) {
  7263. $meta = array();
  7264. if ( !is_null( $mask ) ) {
  7265. foreach( $row as $key => $value ) {
  7266. if ( strpos( $key, $mask ) === 0 ) {
  7267. unset( $row[$key] );
  7268. $meta[$key] = $value;
  7269. }
  7270. }
  7271. }
  7272.  
  7273. $id = $row['id'];
  7274. $this->stash[$this->nesting][$id] = $row;
  7275. $collection[$id] = $this->load( $type, $id );
  7276.  
  7277. if ( $mask !== NULL ) {
  7278. $collection[$id]->setMeta( 'data.bundle', $meta );
  7279. }
  7280. }
  7281. $this->stash[$this->nesting] = NULL;
  7282.  
  7283. return $collection;
  7284. }
  7285.  
  7286. /**
  7287. * Counts the number of beans of type $type.
  7288. * This method accepts a second argument to modify the count-query.
  7289. * A third argument can be used to provide bindings for the SQL snippet.
  7290. *
  7291. * @param string $type type of bean we are looking for
  7292. * @param string $addSQL additional SQL snippet
  7293. * @param array $bindings parameters to bind to SQL
  7294. *
  7295. * @return integer
  7296. */
  7297. public function count( $type, $addSQL = '', $bindings = array() )
  7298. {
  7299. $type = AQueryWriter::camelsSnake( $type );
  7300. if ( count( explode( '_', $type ) ) > 2 ) {
  7301. throw new RedException( 'Invalid type for count.' );
  7302. }
  7303.  
  7304. try {
  7305. return (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings );
  7306. } catch ( SQLException $exception ) {
  7307. if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array(
  7308. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  7309. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) ) ) {
  7310. throw $exception;
  7311. }
  7312. }
  7313.  
  7314. return 0;
  7315. }
  7316.  
  7317. /**
  7318. * Removes a bean from the database.
  7319. * This function will remove the specified OODBBean
  7320. * Bean Object from the database.
  7321. *
  7322. * @param OODBBean|SimpleModel $bean bean you want to remove from database
  7323. *
  7324. * @return void
  7325. */
  7326. public function trash( $bean )
  7327. {
  7328. $this->oodb->signal( 'delete', $bean );
  7329. foreach ( $bean as $property => $value ) {
  7330. if ( $value instanceof OODBBean ) {
  7331. unset( $bean->$property );
  7332. }
  7333. if ( is_array( $value ) ) {
  7334. if ( strpos( $property, 'own' ) === 0 ) {
  7335. unset( $bean->$property );
  7336. } elseif ( strpos( $property, 'shared' ) === 0 ) {
  7337. unset( $bean->$property );
  7338. }
  7339. }
  7340. }
  7341. try {
  7342. $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ), NULL );
  7343. } catch ( SQLException $exception ) {
  7344. $this->handleException( $exception );
  7345. }
  7346. $bean->id = 0;
  7347. $this->oodb->signal( 'after_delete', $bean );
  7348. }
  7349.  
  7350. /**
  7351. * Checks whether the specified table already exists in the database.
  7352. * Not part of the Object Database interface!
  7353. *
  7354. * @deprecated Use AQueryWriter::typeExists() instead.
  7355. *
  7356. * @param string $table table name
  7357. *
  7358. * @return boolean
  7359. */
  7360. public function tableExists( $table )
  7361. {
  7362. return $this->writer->tableExists( $table );
  7363. }
  7364.  
  7365. /**
  7366. * Trash all beans of a given type. Wipes an entire type of bean.
  7367. *
  7368. * @param string $type type of bean you wish to delete all instances of
  7369. *
  7370. * @return boolean
  7371. */
  7372. public function wipe( $type )
  7373. {
  7374. try {
  7375. $this->writer->wipe( $type );
  7376.  
  7377. return TRUE;
  7378. } catch ( SQLException $exception ) {
  7379. if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ) ) ) {
  7380. throw $exception;
  7381. }
  7382.  
  7383. return FALSE;
  7384. }
  7385. }
  7386. }
  7387. }
  7388.  
  7389. namespace RedBeanPHP\Repository {
  7390.  
  7391. use RedBeanPHP\OODBBean as OODBBean;
  7392. use RedBeanPHP\QueryWriter as QueryWriter;
  7393. use RedBeanPHP\RedException as RedException;
  7394. use RedBeanPHP\BeanHelper as BeanHelper;
  7395. use RedBeanPHP\RedException\SQL as SQLException;
  7396. use RedBeanPHP\Repository as Repository;
  7397.  
  7398. /**
  7399. * Fluid Repository.
  7400. * OODB manages two repositories, a fluid one that
  7401. * adjust the database schema on-the-fly to accomodate for
  7402. * new bean types (tables) and new properties (columns) and
  7403. * a frozen one for use in a production environment. OODB
  7404. * allows you to swap the repository instances using the freeze()
  7405. * method.
  7406. *
  7407. * @file RedBeanPHP/Repository/Fluid.php
  7408. * @author Gabor de Mooij and the RedBeanPHP community
  7409. * @license BSD/GPLv2
  7410. *
  7411. * @copyright
  7412. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  7413. * This source file is subject to the BSD/GPLv2 License that is bundled
  7414. * with this source code in the file license.txt.
  7415. */
  7416. class Fluid extends Repository
  7417. {
  7418. /**
  7419. * Figures out the desired type given the cast string ID.
  7420. *
  7421. * @param string $cast cast identifier
  7422. *
  7423. * @return integer
  7424. */
  7425. private function getTypeFromCast( $cast )
  7426. {
  7427. if ( $cast == 'string' ) {
  7428. $typeno = $this->writer->scanType( 'STRING' );
  7429. } elseif ( $cast == 'id' ) {
  7430. $typeno = $this->writer->getTypeForID();
  7431. } elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
  7432. $typeno = $this->writer->sqltype_typeno[$cast];
  7433. } else {
  7434. throw new RedException( 'Invalid Cast' );
  7435. }
  7436.  
  7437. return $typeno;
  7438. }
  7439.  
  7440. /**
  7441. * Orders the Query Writer to create a table if it does not exist already and
  7442. * adds a note in the build report about the creation.
  7443. *
  7444. * @param OODBBean $bean bean to update report of
  7445. * @param string $table table to check and create if not exists
  7446. *
  7447. * @return void
  7448. */
  7449. private function createTableIfNotExists( OODBBean $bean, $table )
  7450. {
  7451. //Does table exist? If not, create
  7452. if ( !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
  7453. $this->writer->createTable( $table );
  7454. $bean->setMeta( 'buildreport.flags.created', TRUE );
  7455. }
  7456. }
  7457.  
  7458. /**
  7459. * Modifies the table to fit the bean data.
  7460. * Given a property and a value and the bean, this method will
  7461. * adjust the table structure to fit the requirements of the property and value.
  7462. * This may include adding a new column or widening an existing column to hold a larger
  7463. * or different kind of value. This method employs the writer to adjust the table
  7464. * structure in the database. Schema updates are recorded in meta properties of the bean.
  7465. *
  7466. * This method will also apply indexes, unique constraints and foreign keys.
  7467. *
  7468. * @param OODBBean $bean bean to get cast data from and store meta in
  7469. * @param string $property property to store
  7470. * @param mixed $value value to store
  7471. *
  7472. * @return void
  7473. */
  7474. private function modifySchema( OODBBean $bean, $property, $value )
  7475. {
  7476. $doFKStuff = FALSE;
  7477. $table = $bean->getMeta( 'type' );
  7478. $columns = $this->writer->getColumns( $table );
  7479. $columnNoQ = $this->writer->esc( $property, TRUE );
  7480. if ( !$this->oodb->isChilled( $bean->getMeta( 'type' ) ) ) {
  7481. if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
  7482. $cast = $bean->getMeta( "cast.$property" );
  7483. $typeno = $this->getTypeFromCast( $cast );
  7484. } else {
  7485. $cast = FALSE;
  7486. $typeno = $this->writer->scanType( $value, TRUE );
  7487. }
  7488. if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
  7489. if ( !$cast ) { //rescan without taking into account special types >80
  7490. $typeno = $this->writer->scanType( $value, FALSE );
  7491. }
  7492. $sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
  7493. if ( $typeno > $sqlt ) { //no, we have to widen the database column type
  7494. $this->writer->widenColumn( $table, $property, $typeno );
  7495. $bean->setMeta( 'buildreport.flags.widen', TRUE );
  7496. $doFKStuff = TRUE;
  7497. }
  7498. } else {
  7499. $this->writer->addColumn( $table, $property, $typeno );
  7500. $bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
  7501. $doFKStuff = TRUE;
  7502. }
  7503. if ($doFKStuff) {
  7504. if (strrpos($columnNoQ, '_id')===(strlen($columnNoQ)-3)) {
  7505. $destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ)-3);
  7506. $indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}";
  7507. $this->writer->addIndex($table, $indexName, $columnNoQ);
  7508. $typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ);
  7509. $isLink = $bean->getMeta( 'sys.buildcommand.unique', FALSE );
  7510. //Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean
  7511. $isDep = ( $bean->moveMeta( 'sys.buildcommand.fkdependson' ) === $typeof || is_array( $isLink ) );
  7512. $result = $this->writer->addFK( $table, $typeof, $columnNoQ, 'id', $isDep );
  7513. //If this is a link bean and all unique columns have been added already, then apply unique constraint
  7514. if ( is_array( $isLink ) && !count( array_diff( $isLink, array_keys( $this->writer->getColumns( $table ) ) ) ) ) {
  7515. $this->writer->addUniqueConstraint( $table, $bean->moveMeta('sys.buildcommand.unique') );
  7516. $bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL);
  7517. }
  7518. }
  7519. }
  7520. }
  7521. }
  7522.  
  7523. /**
  7524. * Part of the store() functionality.
  7525. * Handles all new additions after the bean has been saved.
  7526. * Stores addition bean in own-list, extracts the id and
  7527. * adds a foreign key. Also adds a constraint in case the type is
  7528. * in the dependent list.
  7529. *
  7530. * Note that this method raises a custom exception if the bean
  7531. * is not an instance of OODBBean. Therefore it does not use
  7532. * a type hint. This allows the user to take action in case
  7533. * invalid objects are passed in the list.
  7534. *
  7535. * @param OODBBean $bean bean to process
  7536. * @param array $ownAdditions list of addition beans in own-list
  7537. *
  7538. * @return void
  7539. */
  7540. protected function processAdditions( $bean, $ownAdditions )
  7541. {
  7542. $beanType = $bean->getMeta( 'type' );
  7543.  
  7544. foreach ( $ownAdditions as $addition ) {
  7545. if ( $addition instanceof OODBBean ) {
  7546.  
  7547. $myFieldLink = $beanType . '_id';
  7548. $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
  7549. if ( $alias ) $myFieldLink = $alias . '_id';
  7550.  
  7551. $addition->$myFieldLink = $bean->id;
  7552. $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
  7553.  
  7554. if ($alias) {
  7555. $addition->setMeta( "sys.typeof.{$alias}", $beanType );
  7556. } else {
  7557. $addition->setMeta( "sys.typeof.{$beanType}", $beanType );
  7558. }
  7559.  
  7560. $this->store( $addition );
  7561. } else {
  7562. throw new RedException( 'Array may only contain OODBBeans' );
  7563. }
  7564. }
  7565. }
  7566.  
  7567. /**
  7568. * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
  7569. * method. When all lists and embedded beans (parent objects) have been processed and
  7570. * removed from the original bean the bean is passed to this method to be stored
  7571. * in the database.
  7572. *
  7573. * @param OODBBean $bean the clean bean
  7574. *
  7575. * @return void
  7576. */
  7577. protected function storeBean( OODBBean $bean )
  7578. {
  7579. if ( $bean->getMeta( 'changed' ) ) {
  7580. $this->check( $bean );
  7581. $table = $bean->getMeta( 'type' );
  7582. $this->createTableIfNotExists( $bean, $table );
  7583.  
  7584. $updateValues = array();
  7585. foreach ( $bean as $property => $value ) {
  7586. if ( $property !== 'id' ) {
  7587. $this->modifySchema( $bean, $property, $value );
  7588. }
  7589. if ( $property !== 'id' ) {
  7590. $updateValues[] = array( 'property' => $property, 'value' => $value );
  7591. }
  7592. }
  7593.  
  7594. $bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
  7595. $bean->setMeta( 'changed', FALSE );
  7596. }
  7597. $bean->setMeta( 'tainted', FALSE );
  7598. }
  7599.  
  7600. /**
  7601. * Handles exceptions. Suppresses exceptions caused by missing structures.
  7602. *
  7603. * @param Exception $exception exception
  7604. *
  7605. * @return void
  7606. */
  7607. protected function handleException( \Exception $exception )
  7608. {
  7609. if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
  7610. array(
  7611. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  7612. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) )
  7613. ) {
  7614. throw $exception;
  7615. }
  7616. }
  7617.  
  7618. /**
  7619. * Dispenses a new bean (a OODBBean Bean Object)
  7620. * of the specified type. Always
  7621. * use this function to get an empty bean object. Never
  7622. * instantiate a OODBBean yourself because it needs
  7623. * to be configured before you can use it with RedBean. This
  7624. * function applies the appropriate initialization /
  7625. * configuration for you.
  7626. *
  7627. * @param string $type type of bean you want to dispense
  7628. * @param string $number number of beans you would like to get
  7629. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  7630. *
  7631. * @return OODBBean
  7632. */
  7633. public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
  7634. {
  7635. $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
  7636. $beans = array();
  7637. for ( $i = 0; $i < $number; $i++ ) {
  7638. $bean = new $OODBBEAN;
  7639. $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
  7640. $this->check( $bean );
  7641. $this->oodb->signal( 'dispense', $bean );
  7642. $beans[] = $bean;
  7643. }
  7644.  
  7645. return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
  7646. }
  7647.  
  7648. /**
  7649. * Loads a bean from the object database.
  7650. * It searches for a OODBBean Bean Object in the
  7651. * database. It does not matter how this bean has been stored.
  7652. * RedBean uses the primary key ID $id and the string $type
  7653. * to find the bean. The $type specifies what kind of bean you
  7654. * are looking for; this is the same type as used with the
  7655. * dispense() function. If RedBean finds the bean it will return
  7656. * the OODB Bean object; if it cannot find the bean
  7657. * RedBean will return a new bean of type $type and with
  7658. * primary key ID 0. In the latter case it acts basically the
  7659. * same as dispense().
  7660. *
  7661. * Important note:
  7662. * If the bean cannot be found in the database a new bean of
  7663. * the specified type will be generated and returned.
  7664. *
  7665. * @param string $type type of bean you want to load
  7666. * @param integer $id ID of the bean you want to load
  7667. *
  7668. * @return OODBBean
  7669. */
  7670. public function load( $type, $id )
  7671. {
  7672. $bean = $this->dispense( $type );
  7673. if ( isset( $this->stash[$this->nesting][$id] ) ) {
  7674. $row = $this->stash[$this->nesting][$id];
  7675. } else {
  7676. try {
  7677. $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
  7678. } catch ( SQLException $exception ) {
  7679. if ( $this->writer->sqlStateIn( $exception->getSQLState(),
  7680. array(
  7681. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  7682. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
  7683. )
  7684. ) {
  7685. $rows = 0;
  7686. }
  7687. }
  7688. if ( empty( $rows ) ) {
  7689. return $bean;
  7690. }
  7691. $row = array_pop( $rows );
  7692. }
  7693. $bean->importRow( $row );
  7694. $this->nesting++;
  7695. $this->oodb->signal( 'open', $bean );
  7696. $this->nesting--;
  7697.  
  7698. return $bean->setMeta( 'tainted', FALSE );
  7699. }
  7700. }
  7701. }
  7702.  
  7703. namespace RedBeanPHP\Repository {
  7704.  
  7705. use RedBeanPHP\OODBBean as OODBBean;
  7706. use RedBeanPHP\QueryWriter as QueryWriter;
  7707. use RedBeanPHP\RedException as RedException;
  7708. use RedBeanPHP\BeanHelper as BeanHelper;
  7709. use RedBeanPHP\RedException\SQL as SQLException;
  7710. use RedBeanPHP\Repository as Repository;
  7711.  
  7712. /**
  7713. * Frozen Repository.
  7714. * OODB manages two repositories, a fluid one that
  7715. * adjust the database schema on-the-fly to accomodate for
  7716. * new bean types (tables) and new properties (columns) and
  7717. * a frozen one for use in a production environment. OODB
  7718. * allows you to swap the repository instances using the freeze()
  7719. * method.
  7720. *
  7721. * @file RedBeanPHP/Repository/Frozen.php
  7722. * @author Gabor de Mooij and the RedBeanPHP community
  7723. * @license BSD/GPLv2
  7724. *
  7725. * @copyright
  7726. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  7727. * This source file is subject to the BSD/GPLv2 License that is bundled
  7728. * with this source code in the file license.txt.
  7729. */
  7730. class Frozen extends Repository
  7731. {
  7732. /**
  7733. * Handles exceptions. Suppresses exceptions caused by missing structures.
  7734. *
  7735. * @param \Exception $exception exception to handle
  7736. *
  7737. * @return void
  7738. * @throws \Exception
  7739. */
  7740. protected function handleException( \Exception $exception )
  7741. {
  7742. throw $exception;
  7743. }
  7744.  
  7745. /**
  7746. * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
  7747. * method. When all lists and embedded beans (parent objects) have been processed and
  7748. * removed from the original bean the bean is passed to this method to be stored
  7749. * in the database.
  7750. *
  7751. * @param OODBBean $bean the clean bean
  7752. *
  7753. * @return void
  7754. */
  7755. protected function storeBean( OODBBean $bean )
  7756. {
  7757. if ( $bean->getMeta( 'changed' ) ) {
  7758.  
  7759. list( $properties, $table ) = $bean->getPropertiesAndType();
  7760. $id = $properties['id'];
  7761. unset($properties['id']);
  7762. $updateValues = array();
  7763. $k1 = 'property';
  7764. $k2 = 'value';
  7765. foreach( $properties as $key => $value ) {
  7766. $updateValues[] = array( $k1 => $key, $k2 => $value );
  7767. }
  7768. $bean->id = $this->writer->updateRecord( $table, $updateValues, $id );
  7769. $bean->setMeta( 'changed', FALSE );
  7770. }
  7771. $bean->setMeta( 'tainted', FALSE );
  7772. }
  7773.  
  7774. /**
  7775. * Part of the store() functionality.
  7776. * Handles all new additions after the bean has been saved.
  7777. * Stores addition bean in own-list, extracts the id and
  7778. * adds a foreign key. Also adds a constraint in case the type is
  7779. * in the dependent list.
  7780. *
  7781. * Note that this method raises a custom exception if the bean
  7782. * is not an instance of OODBBean. Therefore it does not use
  7783. * a type hint. This allows the user to take action in case
  7784. * invalid objects are passed in the list.
  7785. *
  7786. * @param OODBBean $bean bean to process
  7787. * @param array $ownAdditions list of addition beans in own-list
  7788. *
  7789. * @return void
  7790. * @throws RedException
  7791. */
  7792. protected function processAdditions( $bean, $ownAdditions )
  7793. {
  7794. $beanType = $bean->getMeta( 'type' );
  7795.  
  7796. $cachedIndex = array();
  7797. foreach ( $ownAdditions as $addition ) {
  7798. if ( $addition instanceof OODBBean ) {
  7799.  
  7800. $myFieldLink = $beanType . '_id';
  7801. $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
  7802. if ( $alias ) $myFieldLink = $alias . '_id';
  7803.  
  7804. $addition->$myFieldLink = $bean->id;
  7805. $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
  7806. $this->store( $addition );
  7807.  
  7808. } else {
  7809. throw new RedException( 'Array may only contain OODBBeans' );
  7810. }
  7811. }
  7812. }
  7813.  
  7814. /**
  7815. * Dispenses a new bean (a OODBBean Bean Object)
  7816. * of the specified type. Always
  7817. * use this function to get an empty bean object. Never
  7818. * instantiate a OODBBean yourself because it needs
  7819. * to be configured before you can use it with RedBean. This
  7820. * function applies the appropriate initialization /
  7821. * configuration for you.
  7822. *
  7823. * @param string $type type of bean you want to dispense
  7824. * @param int $number number of beans you would like to get
  7825. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  7826. *
  7827. * @return OODBBean
  7828. */
  7829. public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
  7830. {
  7831. $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
  7832. $beans = array();
  7833. for ( $i = 0; $i < $number; $i++ ) {
  7834. /** @var \RedBeanPHP\OODBBean $bean */
  7835. $bean = new $OODBBEAN;
  7836. $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
  7837. $this->oodb->signal( 'dispense', $bean );
  7838. $beans[] = $bean;
  7839. }
  7840.  
  7841. return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
  7842. }
  7843.  
  7844. /**
  7845. * Loads a bean from the object database.
  7846. * It searches for a OODBBean Bean Object in the
  7847. * database. It does not matter how this bean has been stored.
  7848. * RedBean uses the primary key ID $id and the string $type
  7849. * to find the bean. The $type specifies what kind of bean you
  7850. * are looking for; this is the same type as used with the
  7851. * dispense() function. If RedBean finds the bean it will return
  7852. * the OODB Bean object; if it cannot find the bean
  7853. * RedBean will return a new bean of type $type and with
  7854. * primary key ID 0. In the latter case it acts basically the
  7855. * same as dispense().
  7856. *
  7857. * Important note:
  7858. * If the bean cannot be found in the database a new bean of
  7859. * the specified type will be generated and returned.
  7860. *
  7861. * @param string $type type of bean you want to load
  7862. * @param integer $id ID of the bean you want to load
  7863. *
  7864. * @return OODBBean
  7865. * @throws SQLException
  7866. */
  7867. public function load( $type, $id )
  7868. {
  7869. $bean = $this->dispense( $type );
  7870. if ( isset( $this->stash[$this->nesting][$id] ) ) {
  7871. $row = $this->stash[$this->nesting][$id];
  7872. } else {
  7873. try {
  7874. $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
  7875. } catch ( SQLException $exception ) {
  7876. if ( $this->writer->sqlStateIn( $exception->getSQLState(),
  7877. array(
  7878. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  7879. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
  7880. )
  7881. ) {
  7882. throw $exception; //only throw if frozen
  7883. }
  7884. }
  7885. if ( empty( $rows ) ) {
  7886. return $bean;
  7887. }
  7888. $row = array_pop( $rows );
  7889. }
  7890. $bean->importRow( $row );
  7891. $this->nesting++;
  7892. $this->oodb->signal( 'open', $bean );
  7893. $this->nesting--;
  7894.  
  7895. return $bean->setMeta( 'tainted', FALSE );
  7896. }
  7897. }
  7898. }
  7899.  
  7900. namespace RedBeanPHP {
  7901.  
  7902. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  7903. use RedBeanPHP\QueryWriter as QueryWriter;
  7904. use RedBeanPHP\BeanHelper as BeanHelper;
  7905. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  7906. use RedBeanPHP\Repository as Repository;
  7907. use RedBeanPHP\Repository\Fluid as FluidRepo;
  7908. use RedBeanPHP\Repository\Frozen as FrozenRepo;
  7909.  
  7910. /**
  7911. * RedBean Object Oriented DataBase.
  7912. *
  7913. * The RedBean OODB Class is the main class of RedBeanPHP.
  7914. * It takes OODBBean objects and stores them to and loads them from the
  7915. * database as well as providing other CRUD functions. This class acts as a
  7916. * object database.
  7917. *
  7918. * @file RedBeanPHP/OODB.php
  7919. * @author Gabor de Mooij and the RedBeanPHP community
  7920. * @license BSD/GPLv2
  7921. *
  7922. * @copyright
  7923. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  7924. * This source file is subject to the BSD/GPLv2 License that is bundled
  7925. * with this source code in the file license.txt.
  7926. */
  7927. class OODB extends Observable
  7928. {
  7929. /**
  7930. * @var array
  7931. */
  7932. private static $sqlFilters = array();
  7933.  
  7934. /**
  7935. * @var array
  7936. */
  7937. protected $chillList = array();
  7938.  
  7939. /**
  7940. * @var array
  7941. */
  7942. protected $stash = NULL;
  7943.  
  7944. /*
  7945. * @var integer
  7946. */
  7947. protected $nesting = 0;
  7948.  
  7949. /**
  7950. * @var DBAdapter
  7951. */
  7952. protected $writer;
  7953.  
  7954. /**
  7955. * @var boolean
  7956. */
  7957. protected $isFrozen = FALSE;
  7958.  
  7959. /**
  7960. * @var FacadeBeanHelper
  7961. */
  7962. protected $beanhelper = NULL;
  7963.  
  7964. /**
  7965. * @var AssociationManager
  7966. */
  7967. protected $assocManager = NULL;
  7968.  
  7969. /**
  7970. * @var Repository
  7971. */
  7972. protected $repository = NULL;
  7973.  
  7974. /**
  7975. * @var FrozenRepo
  7976. */
  7977. protected $frozenRepository = NULL;
  7978.  
  7979. /**
  7980. * @var FluidRepo
  7981. */
  7982. protected $fluidRepository = NULL;
  7983.  
  7984. /**
  7985. * @var boolean
  7986. */
  7987. protected static $autoClearHistoryAfterStore = FALSE;
  7988.  
  7989. /**
  7990. * If set to TRUE, this method will call clearHistory every time
  7991. * the bean gets stored.
  7992. *
  7993. * @param boolean $autoClear auto clear option
  7994. *
  7995. * @return void
  7996. */
  7997. public static function autoClearHistoryAfterStore( $autoClear = TRUE )
  7998. {
  7999. self::$autoClearHistoryAfterStore = (boolean) $autoClear;
  8000. }
  8001.  
  8002. /**
  8003. * Unboxes a bean from a FUSE model if needed and checks whether the bean is
  8004. * an instance of OODBBean.
  8005. *
  8006. * @param OODBBean $bean bean you wish to unbox
  8007. *
  8008. * @return OODBBean
  8009. */
  8010. protected function unboxIfNeeded( $bean )
  8011. {
  8012. if ( $bean instanceof SimpleModel ) {
  8013. $bean = $bean->unbox();
  8014. }
  8015. if ( !( $bean instanceof OODBBean ) ) {
  8016. throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
  8017. }
  8018.  
  8019. return $bean;
  8020. }
  8021.  
  8022. /**
  8023. * Constructor, requires a query writer.
  8024. *
  8025. * @param QueryWriter $writer writer
  8026. * @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
  8027. */
  8028. public function __construct( QueryWriter $writer, $frozen = FALSE )
  8029. {
  8030. if ( $writer instanceof QueryWriter ) {
  8031. $this->writer = $writer;
  8032. }
  8033.  
  8034. $this->freeze( $frozen );
  8035. }
  8036.  
  8037. /**
  8038. * Toggles fluid or frozen mode. In fluid mode the database
  8039. * structure is adjusted to accomodate your objects. In frozen mode
  8040. * this is not the case.
  8041. *
  8042. * You can also pass an array containing a selection of frozen types.
  8043. * Let's call this chilly mode, it's just like fluid mode except that
  8044. * certain types (i.e. tables) aren't touched.
  8045. *
  8046. * @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
  8047. *
  8048. * @return void
  8049. */
  8050. public function freeze( $toggle )
  8051. {
  8052. if ( is_array( $toggle ) ) {
  8053. $this->chillList = $toggle;
  8054. $this->isFrozen = FALSE;
  8055. } else {
  8056. $this->isFrozen = (boolean) $toggle;
  8057. }
  8058.  
  8059. if ( $this->isFrozen ) {
  8060. if ( !$this->frozenRepository ) {
  8061. $this->frozenRepository = new FrozenRepo( $this, $this->writer );
  8062. }
  8063.  
  8064. $this->repository = $this->frozenRepository;
  8065.  
  8066. } else {
  8067. if ( !$this->fluidRepository ) {
  8068. $this->fluidRepository = new FluidRepo( $this, $this->writer );
  8069. }
  8070.  
  8071. $this->repository = $this->fluidRepository;
  8072. }
  8073.  
  8074. if ( count( self::$sqlFilters ) ) {
  8075. AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
  8076. }
  8077.  
  8078. }
  8079.  
  8080. /**
  8081. * Returns the current mode of operation of RedBean.
  8082. * In fluid mode the database
  8083. * structure is adjusted to accomodate your objects.
  8084. * In frozen mode
  8085. * this is not the case.
  8086. *
  8087. * @return boolean
  8088. */
  8089. public function isFrozen()
  8090. {
  8091. return (bool) $this->isFrozen;
  8092. }
  8093.  
  8094. /**
  8095. * Determines whether a type is in the chill list.
  8096. * If a type is 'chilled' it's frozen, so its schema cannot be
  8097. * changed anymore. However other bean types may still be modified.
  8098. * This method is a convenience method for other objects to check if
  8099. * the schema of a certain type is locked for modification.
  8100. *
  8101. * @param string $type the type you wish to check
  8102. *
  8103. * @return boolean
  8104. */
  8105. public function isChilled( $type )
  8106. {
  8107. return (boolean) ( in_array( $type, $this->chillList ) );
  8108. }
  8109.  
  8110. /**
  8111. * Dispenses a new bean (a OODBBean Bean Object)
  8112. * of the specified type. Always
  8113. * use this function to get an empty bean object. Never
  8114. * instantiate a OODBBean yourself because it needs
  8115. * to be configured before you can use it with RedBean. This
  8116. * function applies the appropriate initialization /
  8117. * configuration for you.
  8118. *
  8119. * @param string $type type of bean you want to dispense
  8120. * @param string $number number of beans you would like to get
  8121. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  8122. *
  8123. * @return OODBBean
  8124. */
  8125. public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
  8126. {
  8127. if ( $number < 1 ) {
  8128. if ( $alwaysReturnArray ) return array();
  8129. return NULL;
  8130. }
  8131.  
  8132. return $this->repository->dispense( $type, $number, $alwaysReturnArray );
  8133. }
  8134.  
  8135. /**
  8136. * Sets bean helper to be given to beans.
  8137. * Bean helpers assist beans in getting a reference to a toolbox.
  8138. *
  8139. * @param BeanHelper $beanhelper helper
  8140. *
  8141. * @return void
  8142. */
  8143. public function setBeanHelper( BeanHelper $beanhelper )
  8144. {
  8145. $this->beanhelper = $beanhelper;
  8146. }
  8147.  
  8148. /**
  8149. * Returns the current bean helper.
  8150. * Bean helpers assist beans in getting a reference to a toolbox.
  8151. *
  8152. * @return BeanHelper
  8153. */
  8154. public function getBeanHelper()
  8155. {
  8156. return $this->beanhelper;
  8157. }
  8158.  
  8159. /**
  8160. * Checks whether a OODBBean bean is valid.
  8161. * If the type is not valid or the ID is not valid it will
  8162. * throw an exception: Security.
  8163. *
  8164. * @param OODBBean $bean the bean that needs to be checked
  8165. *
  8166. * @return void
  8167. */
  8168. public function check( OODBBean $bean )
  8169. {
  8170. $this->repository->check( $bean );
  8171. }
  8172.  
  8173. /**
  8174. * Searches the database for a bean that matches conditions $conditions and sql $addSQL
  8175. * and returns an array containing all the beans that have been found.
  8176. *
  8177. * Conditions need to take form:
  8178. *
  8179. * <code>
  8180. * array(
  8181. * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
  8182. * 'PROPERTY' => array( POSSIBLE VALUES... )
  8183. * );
  8184. * </code>
  8185. *
  8186. * All conditions are glued together using the AND-operator, while all value lists
  8187. * are glued using IN-operators thus acting as OR-conditions.
  8188. *
  8189. * Note that you can use property names; the columns will be extracted using the
  8190. * appropriate bean formatter.
  8191. *
  8192. * @param string $type type of beans you are looking for
  8193. * @param array $conditions list of conditions
  8194. * @param string $addSQL SQL to be used in query
  8195. * @param array $bindings a list of values to bind to query parameters
  8196. *
  8197. * @return array
  8198. */
  8199. public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
  8200. {
  8201. return $this->repository->find( $type, $conditions, $sql, $bindings );
  8202. }
  8203.  
  8204. /**
  8205. * Same as find() but returns a BeanCollection.
  8206. *
  8207. * @param string $type type of beans you are looking for
  8208. * @param string $addSQL SQL to be used in query
  8209. * @param array $bindings a list of values to bind to query parameters
  8210. *
  8211. * @return array
  8212. */
  8213. public function findCollection( $type, $sql = NULL, $bindings = array() )
  8214. {
  8215. return $this->repository->findCollection( $type, $sql, $bindings );
  8216. }
  8217.  
  8218. /**
  8219. * Checks whether the specified table already exists in the database.
  8220. * Not part of the Object Database interface!
  8221. *
  8222. * @deprecated Use AQueryWriter::typeExists() instead.
  8223. *
  8224. * @param string $table table name
  8225. *
  8226. * @return boolean
  8227. */
  8228. public function tableExists( $table )
  8229. {
  8230. return $this->repository->tableExists( $table );
  8231. }
  8232.  
  8233. /**
  8234. * Stores a bean in the database. This method takes a
  8235. * OODBBean Bean Object $bean and stores it
  8236. * in the database. If the database schema is not compatible
  8237. * with this bean and RedBean runs in fluid mode the schema
  8238. * will be altered to store the bean correctly.
  8239. * If the database schema is not compatible with this bean and
  8240. * RedBean runs in frozen mode it will throw an exception.
  8241. * This function returns the primary key ID of the inserted
  8242. * bean.
  8243. *
  8244. * The return value is an integer if possible. If it is not possible to
  8245. * represent the value as an integer a string will be returned. We use
  8246. * explicit casts instead of functions to preserve performance
  8247. * (0.13 vs 0.28 for 10000 iterations on Core i3).
  8248. *
  8249. * @param OODBBean|SimpleModel $bean bean to store
  8250. *
  8251. * @return integer|string
  8252. */
  8253. public function store( $bean )
  8254. {
  8255. $bean = $this->unboxIfNeeded( $bean );
  8256. $id = $this->repository->store( $bean );
  8257. if ( self::$autoClearHistoryAfterStore ) {
  8258. $bean->clearHistory();
  8259. }
  8260. return $id;
  8261. }
  8262.  
  8263. /**
  8264. * Loads a bean from the object database.
  8265. * It searches for a OODBBean Bean Object in the
  8266. * database. It does not matter how this bean has been stored.
  8267. * RedBean uses the primary key ID $id and the string $type
  8268. * to find the bean. The $type specifies what kind of bean you
  8269. * are looking for; this is the same type as used with the
  8270. * dispense() function. If RedBean finds the bean it will return
  8271. * the OODB Bean object; if it cannot find the bean
  8272. * RedBean will return a new bean of type $type and with
  8273. * primary key ID 0. In the latter case it acts basically the
  8274. * same as dispense().
  8275. *
  8276. * Important note:
  8277. * If the bean cannot be found in the database a new bean of
  8278. * the specified type will be generated and returned.
  8279. *
  8280. * @param string $type type of bean you want to load
  8281. * @param integer $id ID of the bean you want to load
  8282. *
  8283. * @return OODBBean
  8284. */
  8285. public function load( $type, $id )
  8286. {
  8287. return $this->repository->load( $type, $id );
  8288. }
  8289.  
  8290. /**
  8291. * Removes a bean from the database.
  8292. * This function will remove the specified OODBBean
  8293. * Bean Object from the database.
  8294. *
  8295. * @param OODBBean|SimpleModel $bean bean you want to remove from database
  8296. *
  8297. * @return void
  8298. */
  8299. public function trash( $bean )
  8300. {
  8301. $bean = $this->unboxIfNeeded( $bean );
  8302. return $this->repository->trash( $bean );
  8303. }
  8304.  
  8305. /**
  8306. * Returns an array of beans. Pass a type and a series of ids and
  8307. * this method will bring you the corresponding beans.
  8308. *
  8309. * important note: Because this method loads beans using the load()
  8310. * function (but faster) it will return empty beans with ID 0 for
  8311. * every bean that could not be located. The resulting beans will have the
  8312. * passed IDs as their keys.
  8313. *
  8314. * @param string $type type of beans
  8315. * @param array $ids ids to load
  8316. *
  8317. * @return array
  8318. */
  8319. public function batch( $type, $ids )
  8320. {
  8321. return $this->repository->batch( $type, $ids );
  8322. }
  8323.  
  8324. /**
  8325. * This is a convenience method; it converts database rows
  8326. * (arrays) into beans. Given a type and a set of rows this method
  8327. * will return an array of beans of the specified type loaded with
  8328. * the data fields provided by the result set from the database.
  8329. *
  8330. * @param string $type type of beans you would like to have
  8331. * @param array $rows rows from the database result
  8332. * @param string $mask mask to apply for meta data
  8333. *
  8334. * @return array
  8335. */
  8336. public function convertToBeans( $type, $rows, $mask = NULL )
  8337. {
  8338. return $this->repository->convertToBeans( $type, $rows, $mask );
  8339. }
  8340.  
  8341. /**
  8342. * Counts the number of beans of type $type.
  8343. * This method accepts a second argument to modify the count-query.
  8344. * A third argument can be used to provide bindings for the SQL snippet.
  8345. *
  8346. * @param string $type type of bean we are looking for
  8347. * @param string $addSQL additional SQL snippet
  8348. * @param array $bindings parameters to bind to SQL
  8349. *
  8350. * @return integer
  8351. */
  8352. public function count( $type, $addSQL = '', $bindings = array() )
  8353. {
  8354. return $this->repository->count( $type, $addSQL, $bindings );
  8355. }
  8356.  
  8357. /**
  8358. * Trash all beans of a given type. Wipes an entire type of bean.
  8359. *
  8360. * @param string $type type of bean you wish to delete all instances of
  8361. *
  8362. * @return boolean
  8363. */
  8364. public function wipe( $type )
  8365. {
  8366. return $this->repository->wipe( $type );
  8367. }
  8368.  
  8369. /**
  8370. * Returns an Association Manager for use with OODB.
  8371. * A simple getter function to obtain a reference to the association manager used for
  8372. * storage and more.
  8373. *
  8374. * @return AssociationManager
  8375. */
  8376. public function getAssociationManager()
  8377. {
  8378. if ( !isset( $this->assocManager ) ) {
  8379. throw new RedException( 'No association manager available.' );
  8380. }
  8381.  
  8382. return $this->assocManager;
  8383. }
  8384.  
  8385. /**
  8386. * Sets the association manager instance to be used by this OODB.
  8387. * A simple setter function to set the association manager to be used for storage and
  8388. * more.
  8389. *
  8390. * @param AssociationManager $assoc sets the association manager to be used
  8391. *
  8392. * @return void
  8393. */
  8394. public function setAssociationManager( AssociationManager $assocManager )
  8395. {
  8396. $this->assocManager = $assocManager;
  8397. }
  8398.  
  8399. /**
  8400. * Returns the currently used repository instance.
  8401. * For testing purposes only.
  8402. *
  8403. * @return Repository
  8404. */
  8405. public function getCurrentRepository()
  8406. {
  8407. return $this->repository;
  8408. }
  8409.  
  8410. /**
  8411. * Binds an SQL function to a column.
  8412. * This method can be used to setup a decode/encode scheme or
  8413. * perform UUID insertion. This method is especially useful for handling
  8414. * MySQL spatial columns, because they need to be processed first using
  8415. * the asText/GeomFromText functions.
  8416. *
  8417. * @param string $mode mode to set function for, i.e. read or write
  8418. * @param string $field field (table.column) to bind SQL function to
  8419. * @param string $function SQL function to bind to field
  8420. *
  8421. * @return void
  8422. */
  8423. public function bindFunc( $mode, $field, $function )
  8424. {
  8425. list( $type, $property ) = explode( '.', $field );
  8426. $mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
  8427.  
  8428. if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
  8429. if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
  8430.  
  8431. if ( is_null( $function ) ) {
  8432. unset( self::$sqlFilters[$mode][$type][$property] );
  8433. } else {
  8434. if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
  8435. self::$sqlFilters[$mode][$type][$property] = $function.'(?)';
  8436. } else {
  8437. self::$sqlFilters[$mode][$type][$property] = $function."($field)";
  8438. }
  8439. }
  8440.  
  8441. AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
  8442. }
  8443. }
  8444. }
  8445.  
  8446. namespace RedBeanPHP {
  8447.  
  8448. use RedBeanPHP\OODB as OODB;
  8449. use RedBeanPHP\QueryWriter as QueryWriter;
  8450. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  8451. use RedBeanPHP\Adapter as Adapter;
  8452.  
  8453. /**
  8454. * ToolBox.
  8455. *
  8456. * The toolbox is an integral part of RedBeanPHP providing the basic
  8457. * architectural building blocks to manager objects, helpers and additional tools
  8458. * like plugins. A toolbox contains the three core components of RedBeanPHP:
  8459. * the adapter, the query writer and the core functionality of RedBeanPHP in
  8460. * OODB.
  8461. *
  8462. * @file RedBeanPHP/ToolBox.php
  8463. * @author Gabor de Mooij and the RedBeanPHP community
  8464. * @license BSD/GPLv2
  8465. *
  8466. * @copyright
  8467. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  8468. * This source file is subject to the BSD/GPLv2 License that is bundled
  8469. * with this source code in the file license.txt.
  8470. */
  8471. class ToolBox
  8472. {
  8473.  
  8474. /**
  8475. * @var OODB
  8476. */
  8477. protected $oodb;
  8478.  
  8479. /**
  8480. * @var QueryWriter
  8481. */
  8482. protected $writer;
  8483.  
  8484. /**
  8485. * @var DBAdapter
  8486. */
  8487. protected $adapter;
  8488.  
  8489. /**
  8490. * Constructor.
  8491. * The toolbox is an integral part of RedBeanPHP providing the basic
  8492. * architectural building blocks to manager objects, helpers and additional tools
  8493. * like plugins. A toolbox contains the three core components of RedBeanPHP:
  8494. * the adapter, the query writer and the core functionality of RedBeanPHP in
  8495. * OODB.
  8496. *
  8497. * @param OODB $oodb Object Database, OODB
  8498. * @param DBAdapter $adapter Database Adapter
  8499. * @param QueryWriter $writer Query Writer
  8500. */
  8501. public function __construct( OODB $oodb, Adapter $adapter, QueryWriter $writer )
  8502. {
  8503. $this->oodb = $oodb;
  8504. $this->adapter = $adapter;
  8505. $this->writer = $writer;
  8506. return $this;
  8507. }
  8508.  
  8509. /**
  8510. * Returns the query writer in this toolbox.
  8511. * The Query Writer is responsible for building the queries for a
  8512. * specific database and executing them through the adapter.
  8513. *
  8514. * @return QueryWriter
  8515. */
  8516. public function getWriter()
  8517. {
  8518. return $this->writer;
  8519. }
  8520.  
  8521. /**
  8522. * Returns the OODB instance in this toolbox.
  8523. * OODB is responsible for creating, storing, retrieving and deleting
  8524. * single beans. Other components rely
  8525. * on OODB for their basic functionality.
  8526. *
  8527. * @return OODB
  8528. */
  8529. public function getRedBean()
  8530. {
  8531. return $this->oodb;
  8532. }
  8533.  
  8534. /**
  8535. * Returns the database adapter in this toolbox.
  8536. * The adapter is responsible for executing the query and binding the values.
  8537. * The adapter also takes care of transaction handling.
  8538. *
  8539. * @return DBAdapter
  8540. */
  8541. public function getDatabaseAdapter()
  8542. {
  8543. return $this->adapter;
  8544. }
  8545. }
  8546. }
  8547.  
  8548. namespace RedBeanPHP {
  8549.  
  8550.  
  8551. /**
  8552. * RedBeanPHP Finder.
  8553. * Service class to find beans. For the most part this class
  8554. * offers user friendly utility methods for interacting with the
  8555. * OODB::find() method, which is rather complex. This class can be
  8556. * used to find beans using plain old SQL queries.
  8557. *
  8558. * @file RedBeanPHP/Finder.php
  8559. * @author Gabor de Mooij and the RedBeanPHP Community
  8560. * @license BSD/GPLv2
  8561. *
  8562. * @copyright
  8563. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  8564. * This source file is subject to the BSD/GPLv2 License that is bundled
  8565. * with this source code in the file license.txt.
  8566. */
  8567. class Finder
  8568. {
  8569. /**
  8570. * @var ToolBox
  8571. */
  8572. protected $toolbox;
  8573.  
  8574. /**
  8575. * @var OODB
  8576. */
  8577. protected $redbean;
  8578.  
  8579. /**
  8580. * Constructor.
  8581. * The Finder requires a toolbox.
  8582. *
  8583. * @param ToolBox $toolbox
  8584. */
  8585. public function __construct( ToolBox $toolbox )
  8586. {
  8587. $this->toolbox = $toolbox;
  8588. $this->redbean = $toolbox->getRedBean();
  8589. }
  8590.  
  8591. /**
  8592. * Finds a bean using a type and a where clause (SQL).
  8593. * As with most Query tools in RedBean you can provide values to
  8594. * be inserted in the SQL statement by populating the value
  8595. * array parameter; you can either use the question mark notation
  8596. * or the slot-notation (:keyname).
  8597. *
  8598. * @param string $type type the type of bean you are looking for
  8599. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  8600. * @param array $bindings values array of values to be bound to parameters in query
  8601. *
  8602. * @return array
  8603. */
  8604. public function find( $type, $sql = NULL, $bindings = array() )
  8605. {
  8606. if ( !is_array( $bindings ) ) {
  8607. throw new RedException(
  8608. 'Expected array, ' . gettype( $bindings ) . ' given.'
  8609. );
  8610. }
  8611.  
  8612. return $this->redbean->find( $type, array(), $sql, $bindings );
  8613. }
  8614.  
  8615. /**
  8616. * Like find() but also exports the beans as an array.
  8617. * This method will perform a find-operation. For every bean
  8618. * in the result collection this method will call the export() method.
  8619. * This method returns an array containing the array representations
  8620. * of every bean in the result set.
  8621. *
  8622. * @see Finder::find
  8623. *
  8624. * @param string $type type the type of bean you are looking for
  8625. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  8626. * @param array $bindings values array of values to be bound to parameters in query
  8627. *
  8628. * @return array
  8629. */
  8630. public function findAndExport( $type, $sql = NULL, $bindings = array() )
  8631. {
  8632. $arr = array();
  8633. foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
  8634. $arr[] = $item->export();
  8635. }
  8636.  
  8637. return $arr;
  8638. }
  8639.  
  8640. /**
  8641. * Like find() but returns just one bean instead of an array of beans.
  8642. * This method will return only the first bean of the array.
  8643. * If no beans are found, this method will return NULL.
  8644. *
  8645. * @see Finder::find
  8646. *
  8647. * @param string $type type the type of bean you are looking for
  8648. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  8649. * @param array $bindings values array of values to be bound to parameters in query
  8650. *
  8651. * @return OODBBean
  8652. */
  8653. public function findOne( $type, $sql = NULL, $bindings = array() )
  8654. {
  8655. $sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
  8656.  
  8657. $items = $this->find( $type, $sql, $bindings );
  8658.  
  8659. if ( empty($items) ) {
  8660. return NULL;
  8661. }
  8662.  
  8663. return reset( $items );
  8664. }
  8665.  
  8666. /**
  8667. * Like find() but returns the last bean of the result array.
  8668. * Opposite of Finder::findLast().
  8669. * If no beans are found, this method will return NULL.
  8670. *
  8671. * @see Finder::find
  8672. *
  8673. * @param string $type the type of bean you are looking for
  8674. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  8675. * @param array $bindings values array of values to be bound to parameters in query
  8676. *
  8677. * @return OODBBean
  8678. */
  8679. public function findLast( $type, $sql = NULL, $bindings = array() )
  8680. {
  8681. $items = $this->find( $type, $sql, $bindings );
  8682.  
  8683. if ( empty($items) ) {
  8684. return NULL;
  8685. }
  8686.  
  8687. return end( $items );
  8688. }
  8689.  
  8690. /**
  8691. * Tries to find beans of a certain type,
  8692. * if no beans are found, it dispenses a bean of that type.
  8693. *
  8694. * @see Finder::find
  8695. *
  8696. * @param string $type the type of bean you are looking for
  8697. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  8698. * @param array $bindings values array of values to be bound to parameters in query
  8699. *
  8700. * @return array
  8701. */
  8702. public function findOrDispense( $type, $sql = NULL, $bindings = array() )
  8703. {
  8704. $foundBeans = $this->find( $type, $sql, $bindings );
  8705.  
  8706. if ( empty( $foundBeans ) ) {
  8707. return array( $this->redbean->dispense( $type ) );
  8708. } else {
  8709. return $foundBeans;
  8710. }
  8711. }
  8712.  
  8713. /**
  8714. * Finds a BeanCollection using the repository.
  8715. * A bean collection can be used to retrieve one bean at a time using
  8716. * cursors - this is useful for processing large datasets. A bean collection
  8717. * will not load all beans into memory all at once, just one at a time.
  8718. *
  8719. * @param string $type the type of bean you are looking for
  8720. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  8721. * @param array $bindings values array of values to be bound to parameters in query
  8722. *
  8723. * @return BeanCollection
  8724. */
  8725. public function findCollection( $type, $sql, $bindings = array() )
  8726. {
  8727. return $this->redbean->findCollection( $type, $sql, $bindings );
  8728. }
  8729.  
  8730. /**
  8731. * Finds or creates a bean.
  8732. * Tries to find a bean with certain properties specified in the second
  8733. * parameter ($like). If the bean is found, it will be returned.
  8734. * If multiple beans are found, only the first will be returned.
  8735. * If no beans match the criteria, a new bean will be dispensed,
  8736. * the criteria will be imported as properties and this new bean
  8737. * will be stored and returned.
  8738. *
  8739. * Format of criteria set: property => value
  8740. * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
  8741. *
  8742. * @param string $type type of bean to search for
  8743. * @param array $like criteria set describing bean to search for
  8744. *
  8745. * @return OODBBean
  8746. */
  8747. public function findOrCreate( $type, $like = array() )
  8748. {
  8749. $beans = $this->findLike( $type, $like );
  8750. if ( count( $beans ) ) {
  8751. $bean = reset( $beans );
  8752. return $bean;
  8753. }
  8754.  
  8755. $bean = $this->redbean->dispense( $type );
  8756. $bean->import( $like );
  8757. $this->redbean->store( $bean );
  8758. return $bean;
  8759. }
  8760.  
  8761. /**
  8762. * Finds beans by its type and a certain criteria set.
  8763. *
  8764. * Format of criteria set: property => value
  8765. * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
  8766. *
  8767. * If the additional SQL is a condition, this condition will be glued to the rest
  8768. * of the query using an AND operator. Note that this is as far as this method
  8769. * can go, there is no way to glue additional SQL using an OR-condition.
  8770. * This method provides access to an underlying mechanism in the RedBeanPHP architecture
  8771. * to find beans using criteria sets. However, please do not use this method
  8772. * for complex queries, use plain SQL instead ( the regular find method ) as it is
  8773. * more suitable for the job. This method is
  8774. * meant for basic search-by-example operations.
  8775. *
  8776. * @param string $type type of bean to search for
  8777. * @param array $conditions criteria set describing the bean to search for
  8778. * @param string $sql additional SQL (for sorting)
  8779. *
  8780. * @return array
  8781. */
  8782. public function findLike( $type, $conditions = array(), $sql = '' )
  8783. {
  8784. if ( count( $conditions ) > 0 ) {
  8785. foreach( $conditions as $key => $condition ) {
  8786. if ( !count( $condition ) ) unset( $conditions[$key] );
  8787. }
  8788. }
  8789.  
  8790. return $this->redbean->find( $type, $conditions, $sql );
  8791. }
  8792.  
  8793. /**
  8794. * Returns a hashmap with bean arrays keyed by type using an SQL
  8795. * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
  8796. * this method will return movie and review beans.
  8797. *
  8798. * Example:
  8799. *
  8800. * <code>
  8801. * $stuff = $finder->findMulti('movie,review', '
  8802. * SELECT movie.*, review.* FROM movie
  8803. * LEFT JOIN review ON review.movie_id = movie.id');
  8804. * </code>
  8805. *
  8806. * After this operation, $stuff will contain an entry 'movie' containing all
  8807. * movies and an entry named 'review' containing all reviews (all beans).
  8808. * You can also pass bindings.
  8809. *
  8810. * If you want to re-map your beans, so you can use $movie->ownReviewList without
  8811. * having RedBeanPHP executing an SQL query you can use the fourth parameter to
  8812. * define a selection of remapping closures.
  8813. *
  8814. * The remapping argument (optional) should contain an array of arrays.
  8815. * Each array in the remapping array should contain the following entries:
  8816. *
  8817. * <code>
  8818. * array(
  8819. * 'a' => TYPE A
  8820. * 'b' => TYPE B
  8821. * 'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
  8822. * 'do' => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
  8823. * )
  8824. * </code>
  8825. *
  8826. * Using this mechanism you can build your own 'preloader' with tiny function
  8827. * snippets (and those can be re-used and shared online of course).
  8828. *
  8829. * Example:
  8830. *
  8831. * <code>
  8832. * array(
  8833. * 'a' => 'movie' //define A as movie
  8834. * 'b' => 'review' //define B as review
  8835. * 'matcher' => function( $a, $b ) {
  8836. * return ( $b->movie_id == $a->id ); //Perform action if review.movie_id equals movie.id
  8837. * }
  8838. * 'do' => function( $a, $b ) {
  8839. * $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
  8840. * $a->clearHistory(); //optional, act 'as if these beans have been loaded through ownReviewList'.
  8841. * }
  8842. * )
  8843. * </code>
  8844. *
  8845. * The Query Template parameter is optional as well but can be used to
  8846. * set a different SQL template (sprintf-style) for processing the original query.
  8847. *
  8848. * @note the SQL query provided IS NOT THE ONE used internally by this function,
  8849. * this function will pre-process the query to get all the data required to find the beans.
  8850. *
  8851. * @note if you use the 'book.*' notation make SURE you're
  8852. * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
  8853. * it's actually an SQL-like template SLOT, not real SQL.
  8854. *
  8855. * @note instead of an SQL query you can pass a result array as well.
  8856. *
  8857. * @param string|array $types a list of types (either array or comma separated string)
  8858. * @param string|array $sqlOrArr an SQL query or an array of prefetched records
  8859. * @param array $bindings optional, bindings for SQL query
  8860. * @param array $remappings optional, an array of remapping arrays
  8861. * @param string $queryTemplate optional, query template
  8862. *
  8863. * @return array
  8864. */
  8865. public function findMulti( $types, $sql, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
  8866. {
  8867. if ( !is_array( $types ) ) $types = explode( ',', $types );
  8868. if ( !is_array( $sql ) ) {
  8869. $writer = $this->toolbox->getWriter();
  8870. $adapter = $this->toolbox->getDatabaseAdapter();
  8871.  
  8872. //Repair the query, replace book.* with book.id AS book_id etc..
  8873. foreach( $types as $type ) {
  8874. $pattern = " {$type}.*";
  8875. if ( strpos( $sql, $pattern ) !== FALSE ) {
  8876. $newSelectorArray = array();
  8877. $columns = $writer->getColumns( $type );
  8878. foreach( $columns as $column => $definition ) {
  8879. $newSelectorArray[] = sprintf( $queryTemplate, $type, $column, $type, $column );
  8880. }
  8881. $newSelector = implode( ',', $newSelectorArray );
  8882. $sql = str_replace( $pattern, $newSelector, $sql );
  8883. }
  8884. }
  8885.  
  8886. $rows = $adapter->get( $sql, $bindings );
  8887. } else {
  8888. $rows = $sql;
  8889. }
  8890.  
  8891. //Gather the bean data from the query results using the prefix
  8892. $wannaBeans = array();
  8893. foreach( $types as $type ) {
  8894. $wannaBeans[$type] = array();
  8895. $prefix = "{$type}__";
  8896. foreach( $rows as $rowkey=>$row ) {
  8897. $wannaBean = array();
  8898. foreach( $row as $cell => $value ) {
  8899. if ( strpos( $cell, $prefix ) === 0 ) {
  8900. $property = substr( $cell, strlen( $prefix ) );
  8901. unset( $rows[$rowkey][$cell] );
  8902. $wannaBean[$property] = $value;
  8903. }
  8904. }
  8905. if ( !isset( $wannaBean['id'] ) ) continue;
  8906. if ( is_null( $wannaBean['id'] ) ) continue;
  8907. $wannaBeans[$type][$wannaBean['id']] = $wannaBean;
  8908. }
  8909. }
  8910.  
  8911. //Turn the rows into beans
  8912. $beans = array();
  8913. foreach( $wannaBeans as $type => $wannabees ) {
  8914. $beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
  8915. }
  8916.  
  8917. //Apply additional re-mappings
  8918. foreach($remappings as $remapping) {
  8919. $a = $remapping['a'];
  8920. $b = $remapping['b'];
  8921. $matcher = $remapping['matcher'];
  8922. $do = $remapping['do'];
  8923. foreach( $beans[$a] as $bean ) {
  8924. foreach( $beans[$b] as $putBean ) {
  8925. if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping );
  8926. }
  8927. }
  8928. }
  8929. return $beans;
  8930. }
  8931. }
  8932. }
  8933.  
  8934. namespace RedBeanPHP {
  8935.  
  8936. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  8937. use RedBeanPHP\QueryWriter as QueryWriter;
  8938. use RedBeanPHP\RedException as RedException;
  8939. use RedBeanPHP\RedException\SQL as SQLException;
  8940.  
  8941. /**
  8942. * Association Manager.
  8943. * Manages simple bean associations.
  8944. *
  8945. * @file RedBeanPHP/AssociationManager.php
  8946. * @author Gabor de Mooij and the RedBeanPHP Community
  8947. * @license BSD/GPLv2
  8948. *
  8949. * @copyright
  8950. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  8951. * This source file is subject to the BSD/GPLv2 License that is bundled
  8952. * with this source code in the file license.txt.
  8953. */
  8954. class AssociationManager extends Observable
  8955. {
  8956. /**
  8957. * @var OODB
  8958. */
  8959. protected $oodb;
  8960.  
  8961. /**
  8962. * @var DBAdapter
  8963. */
  8964. protected $adapter;
  8965.  
  8966. /**
  8967. * @var QueryWriter
  8968. */
  8969. protected $writer;
  8970.  
  8971. /**
  8972. * Handles exceptions. Suppresses exceptions caused by missing structures.
  8973. *
  8974. * @param Exception $exception exception to handle
  8975. *
  8976. * @return void
  8977. */
  8978. private function handleException( \Exception $exception )
  8979. {
  8980. if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
  8981. array(
  8982. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  8983. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN )
  8984. )
  8985. ) {
  8986. throw $exception;
  8987. }
  8988. }
  8989.  
  8990. /**
  8991. * Internal method.
  8992. * Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
  8993. * $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
  8994. *
  8995. * @param OODBBean $bean reference bean instance
  8996. * @param string $type target bean type
  8997. * @param string $sql additional SQL snippet
  8998. * @param array $bindings bindings for query
  8999. *
  9000. * @return array
  9001. */
  9002. private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
  9003. {
  9004. $ids = array( $bean->id );
  9005. $sourceType = $bean->getMeta( 'type' );
  9006. try {
  9007. return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
  9008. } catch ( SQLException $exception ) {
  9009. $this->handleException( $exception );
  9010. return array();
  9011. }
  9012. }
  9013.  
  9014. /**
  9015. * Associates a pair of beans. This method associates two beans, no matter
  9016. * what types. Accepts a base bean that contains data for the linking record.
  9017. * This method is used by associate. This method also accepts a base bean to be used
  9018. * as the template for the link record in the database.
  9019. *
  9020. * @param OODBBean $bean1 first bean
  9021. * @param OODBBean $bean2 second bean
  9022. * @param OODBBean $bean base bean (association record)
  9023. *
  9024. * @return mixed
  9025. */
  9026. protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
  9027. {
  9028. $type = $bean->getMeta( 'type' );
  9029. $property1 = $bean1->getMeta( 'type' ) . '_id';
  9030. $property2 = $bean2->getMeta( 'type' ) . '_id';
  9031.  
  9032. if ( $property1 == $property2 ) {
  9033. $property2 = $bean2->getMeta( 'type' ) . '2_id';
  9034. }
  9035.  
  9036. $this->oodb->store( $bean1 );
  9037. $this->oodb->store( $bean2 );
  9038.  
  9039. $bean->setMeta( "cast.$property1", "id" );
  9040. $bean->setMeta( "cast.$property2", "id" );
  9041. $bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
  9042.  
  9043. $bean->$property1 = $bean1->id;
  9044. $bean->$property2 = $bean2->id;
  9045.  
  9046. $results = array();
  9047.  
  9048. try {
  9049. $id = $this->oodb->store( $bean );
  9050. $results[] = $id;
  9051. } catch ( SQLException $exception ) {
  9052. if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
  9053. array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ) )
  9054. ) {
  9055. throw $exception;
  9056. }
  9057. }
  9058.  
  9059. return $results;
  9060. }
  9061.  
  9062. /**
  9063. * Constructor
  9064. *
  9065. * @param ToolBox $tools toolbox
  9066. */
  9067. public function __construct( ToolBox $tools )
  9068. {
  9069. $this->oodb = $tools->getRedBean();
  9070. $this->adapter = $tools->getDatabaseAdapter();
  9071. $this->writer = $tools->getWriter();
  9072. $this->toolbox = $tools;
  9073. }
  9074.  
  9075. /**
  9076. * Creates a table name based on a types array.
  9077. * Manages the get the correct name for the linking table for the
  9078. * types provided.
  9079. *
  9080. * @todo find a nice way to decouple this class from QueryWriter?
  9081. *
  9082. * @param array $types 2 types as strings
  9083. *
  9084. * @return string
  9085. */
  9086. public function getTable( $types )
  9087. {
  9088. return $this->writer->getAssocTable( $types );
  9089. }
  9090.  
  9091. /**
  9092. * Associates two beans in a many-to-many relation.
  9093. * This method will associate two beans and store the connection between the
  9094. * two in a link table. Instead of two single beans this method also accepts
  9095. * two sets of beans. Returns the ID or the IDs of the linking beans.
  9096. *
  9097. * @param OODBBean|array $beans1 one or more beans to form the association
  9098. * @param OODBBean|array $beans2 one or more beans to form the association
  9099. *
  9100. * @return array
  9101. */
  9102. public function associate( $beans1, $beans2 )
  9103. {
  9104. if ( !is_array( $beans1 ) ) {
  9105. $beans1 = array( $beans1 );
  9106. }
  9107.  
  9108. if ( !is_array( $beans2 ) ) {
  9109. $beans2 = array( $beans2 );
  9110. }
  9111.  
  9112. $results = array();
  9113. foreach ( $beans1 as $bean1 ) {
  9114. foreach ( $beans2 as $bean2 ) {
  9115. $table = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
  9116. $bean = $this->oodb->dispense( $table );
  9117. $results[] = $this->associateBeans( $bean1, $bean2, $bean );
  9118. }
  9119. }
  9120.  
  9121. return ( count( $results ) > 1 ) ? $results : reset( $results );
  9122. }
  9123.  
  9124. /**
  9125. * Counts the number of related beans in an N-M relation.
  9126. * This method returns the number of beans of type $type associated
  9127. * with reference bean(s) $bean. The query can be tuned using an
  9128. * SQL snippet for additional filtering.
  9129. *
  9130. * @param OODBBean|array $bean a bean object or an array of beans
  9131. * @param string $type type of bean you're interested in
  9132. * @param string $sql SQL snippet (optional)
  9133. * @param array $bindings bindings for your SQL string
  9134. *
  9135. * @return integer
  9136. */
  9137. public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
  9138. {
  9139. if ( !( $bean instanceof OODBBean ) ) {
  9140. throw new RedException(
  9141. 'Expected array or OODBBean but got:' . gettype( $bean )
  9142. );
  9143. }
  9144.  
  9145. if ( !$bean->id ) {
  9146. return 0;
  9147. }
  9148.  
  9149. $beanType = $bean->getMeta( 'type' );
  9150.  
  9151. try {
  9152. return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
  9153. } catch ( SQLException $exception ) {
  9154. $this->handleException( $exception );
  9155.  
  9156. return 0;
  9157. }
  9158. }
  9159.  
  9160. /**
  9161. * Breaks the association between two beans. This method unassociates two beans. If the
  9162. * method succeeds the beans will no longer form an association. In the database
  9163. * this means that the association record will be removed. This method uses the
  9164. * OODB trash() method to remove the association links, thus giving FUSE models the
  9165. * opportunity to hook-in additional business logic. If the $fast parameter is
  9166. * set to boolean TRUE this method will remove the beans without their consent,
  9167. * bypassing FUSE. This can be used to improve performance.
  9168. *
  9169. * @param OODBBean $bean1 first bean in target association
  9170. * @param OODBBean $bean2 second bean in target association
  9171. * @param boolean $fast if TRUE, removes the entries by query without FUSE
  9172. *
  9173. * @return void
  9174. */
  9175. public function unassociate( $beans1, $beans2, $fast = NULL )
  9176. {
  9177. $beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
  9178. $beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
  9179.  
  9180. foreach ( $beans1 as $bean1 ) {
  9181. foreach ( $beans2 as $bean2 ) {
  9182. try {
  9183. $this->oodb->store( $bean1 );
  9184. $this->oodb->store( $bean2 );
  9185.  
  9186. $type1 = $bean1->getMeta( 'type' );
  9187. $type2 = $bean2->getMeta( 'type' );
  9188.  
  9189. $row = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
  9190. $linkType = $this->getTable( array( $type1, $type2 ) );
  9191.  
  9192. if ( $fast ) {
  9193. $this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
  9194.  
  9195. return;
  9196. }
  9197.  
  9198. $beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
  9199.  
  9200. if ( count( $beans ) > 0 ) {
  9201. $bean = reset( $beans );
  9202. $this->oodb->trash( $bean );
  9203. }
  9204. } catch ( SQLException $exception ) {
  9205. $this->handleException( $exception );
  9206. }
  9207. }
  9208. }
  9209. }
  9210.  
  9211. /**
  9212. * Removes all relations for a bean. This method breaks every connection between
  9213. * a certain bean $bean and every other bean of type $type. Warning: this method
  9214. * is really fast because it uses a direct SQL query however it does not inform the
  9215. * models about this. If you want to notify FUSE models about deletion use a foreach-loop
  9216. * with unassociate() instead. (that might be slower though)
  9217. *
  9218. * @param OODBBean $bean reference bean
  9219. * @param string $type type of beans that need to be unassociated
  9220. *
  9221. * @return void
  9222. */
  9223. public function clearRelations( OODBBean $bean, $type )
  9224. {
  9225. $this->oodb->store( $bean );
  9226. try {
  9227. $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
  9228. } catch ( SQLException $exception ) {
  9229. $this->handleException( $exception );
  9230. }
  9231. }
  9232.  
  9233. /**
  9234. * Returns all the beans associated with $bean.
  9235. * This method will return an array containing all the beans that have
  9236. * been associated once with the associate() function and are still
  9237. * associated with the bean specified. The type parameter indicates the
  9238. * type of beans you are looking for. You can also pass some extra SQL and
  9239. * values for that SQL to filter your results after fetching the
  9240. * related beans.
  9241. *
  9242. * Don't try to make use of subqueries, a subquery using IN() seems to
  9243. * be slower than two queries!
  9244. *
  9245. * Since 3.2, you can now also pass an array of beans instead just one
  9246. * bean as the first parameter.
  9247. *
  9248. * @param OODBBean|array $bean the bean you have
  9249. * @param string $type the type of beans you want
  9250. * @param string $sql SQL snippet for extra filtering
  9251. * @param array $bindings values to be inserted in SQL slots
  9252. *
  9253. * @return array
  9254. */
  9255. public function related( $bean, $type, $sql = '', $bindings = array() )
  9256. {
  9257. $sql = $this->writer->glueSQLCondition( $sql );
  9258. $rows = $this->relatedRows( $bean, $type, $sql, $bindings );
  9259. $links = array();
  9260.  
  9261. foreach ( $rows as $key => $row ) {
  9262. if ( !isset( $links[$row['id']] ) ) $links[$row['id']] = array();
  9263. $links[$row['id']][] = $row['linked_by'];
  9264. unset( $rows[$key]['linked_by'] );
  9265. }
  9266.  
  9267. $beans = $this->oodb->convertToBeans( $type, $rows );
  9268. foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
  9269.  
  9270. return $beans;
  9271. }
  9272. }
  9273. }
  9274.  
  9275. namespace RedBeanPHP {
  9276.  
  9277. use RedBeanPHP\ToolBox as ToolBox;
  9278. use RedBeanPHP\OODBBean as OODBBean;
  9279.  
  9280. /**
  9281. * Bean Helper Interface.
  9282. *
  9283. * Interface for Bean Helper.
  9284. * A little bolt that glues the whole machinery together.
  9285. *
  9286. * @file RedBeanPHP/IBeanHelper.php
  9287. * @author Gabor de Mooij and the RedBeanPHP Community
  9288. * @license BSD/GPLv2
  9289. *
  9290. * @copyright
  9291. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  9292. * This source file is subject to the BSD/GPLv2 License that is bundled
  9293. * with this source code in the file license.txt.
  9294. */
  9295. interface BeanHelper
  9296. {
  9297. /**
  9298. * Returns a toolbox to empower the bean.
  9299. * This allows beans to perform OODB operations by themselves,
  9300. * as such the bean is a proxy for OODB. This allows beans to implement
  9301. * their magic getters and setters and return lists.
  9302. *
  9303. * @return ToolBox
  9304. */
  9305. public function getToolbox();
  9306.  
  9307. /**
  9308. * Does approximately the same as getToolbox but also extracts the
  9309. * toolbox for you.
  9310. * This method returns a list with all toolbox items in Toolbox Constructor order:
  9311. * OODB, adapter, writer and finally the toolbox itself!.
  9312. *
  9313. * @return array
  9314. */
  9315. public function getExtractedToolbox();
  9316.  
  9317. /**
  9318. * Given a certain bean this method will
  9319. * return the corresponding model.
  9320. * If no model is returned (NULL), RedBeanPHP might ask again.
  9321. *
  9322. * @note You can make RedBeanPHP faster by doing the setup wiring yourself.
  9323. * The event listeners take time, so to speed-up RedBeanPHP you can
  9324. * drop 'FUSE', if you're not interested in the Models.
  9325. *
  9326. * @note You can do funny stuff with this method but please be careful.
  9327. * You *could* create a model depending on properties of the bean, but
  9328. * it's a bit well... adventurous, here is an example:
  9329. *
  9330. * <code>
  9331. * class Book extends RedBeanPHP\SimpleModel {};
  9332. * class Booklet extends RedBeanPHP\SimpleModel {};
  9333. *
  9334. * class FlexBeanHelper extends RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper {
  9335. * public function getModelForBean( RedBeanPHP\OODBBean $bean ) {
  9336. * if (!isset($bean->pages)) return NULL; //will ask again
  9337. * if ($bean->pages <= 10) return new Booklet;
  9338. * return new Book;
  9339. * }
  9340. * }
  9341. *
  9342. * $h = new FlexBeanHelper;
  9343. * R::getRedBean()->setBeanHelper($h);
  9344. * $book = R::dispense('book');
  9345. * var_dump($book->box()); //NULL cant reach model
  9346. * $book->pages = 5;
  9347. * var_dump($book->box()); //Booklet
  9348. * $book->pages = 15;
  9349. * var_dump($book->box()); //still.. Booklet, model has been set
  9350. * $book2 = R::dispense('book');
  9351. * $book2->pages = 15;
  9352. * var_dump($book2->box()); //Book, more than 10 pages
  9353. * </code>
  9354. *
  9355. * @param OODBBean $bean bean to obtain the corresponding model of
  9356. *
  9357. * @return SimpleModel|CustomModel|NULL
  9358. */
  9359. public function getModelForBean( OODBBean $bean );
  9360. }
  9361. }
  9362.  
  9363. namespace RedBeanPHP\BeanHelper {
  9364.  
  9365. use RedBeanPHP\BeanHelper as BeanHelper;
  9366. use RedBeanPHP\Facade as Facade;
  9367. use RedBeanPHP\OODBBean as OODBBean;
  9368. use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
  9369.  
  9370. /**
  9371. * Bean Helper.
  9372. *
  9373. * The Bean helper helps beans to access access the toolbox and
  9374. * FUSE models. This Bean Helper makes use of the facade to obtain a
  9375. * reference to the toolbox.
  9376. *
  9377. * @file RedBeanPHP/BeanHelperFacade.php
  9378. * @author Gabor de Mooij and the RedBeanPHP Community
  9379. * @license BSD/GPLv2
  9380. *
  9381. * @copyright
  9382. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  9383. * This source file is subject to the BSD/GPLv2 License that is bundled
  9384. * with this source code in the file license.txt.
  9385. */
  9386. class SimpleFacadeBeanHelper implements BeanHelper
  9387. {
  9388. /**
  9389. * Factory function to create instance of Simple Model, if any.
  9390. *
  9391. * @var \Closure
  9392. */
  9393. private static $factory = null;
  9394.  
  9395. /**
  9396. * Factory method using a customizable factory function to create
  9397. * the instance of the Simple Model.
  9398. *
  9399. * @param string $modelClassName name of the class
  9400. *
  9401. * @return SimpleModel
  9402. */
  9403. public static function factory( $modelClassName )
  9404. {
  9405. $factory = self::$factory;
  9406. return ( $factory ) ? $factory( $modelClassName ) : new $modelClassName();
  9407. }
  9408.  
  9409. /**
  9410. * Sets the factory function to create the model when using FUSE
  9411. * to connect a bean to a model.
  9412. *
  9413. * @param \Closure $factory factory function
  9414. *
  9415. * @return void
  9416. */
  9417. public static function setFactoryFunction( $factory )
  9418. {
  9419. self::$factory = $factory;
  9420. }
  9421.  
  9422. /**
  9423. * @see BeanHelper::getToolbox
  9424. */
  9425. public function getToolbox()
  9426. {
  9427. return Facade::getToolBox();
  9428. }
  9429.  
  9430. /**
  9431. * @see BeanHelper::getModelForBean
  9432. */
  9433. public function getModelForBean( OODBBean $bean )
  9434. {
  9435. $model = $bean->getMeta( 'type' );
  9436. $prefix = defined( 'REDBEAN_MODEL_PREFIX' ) ? REDBEAN_MODEL_PREFIX : '\\Model_';
  9437.  
  9438. if ( strpos( $model, '_' ) !== FALSE ) {
  9439. $modelParts = explode( '_', $model );
  9440. $modelName = '';
  9441. foreach( $modelParts as $part ) {
  9442. $modelName .= ucfirst( $part );
  9443. }
  9444. $modelName = $prefix . $modelName;
  9445.  
  9446. if ( !class_exists( $modelName ) ) {
  9447. //second try
  9448. $modelName = $prefix . ucfirst( $model );
  9449.  
  9450. if ( !class_exists( $modelName ) ) {
  9451. return NULL;
  9452. }
  9453. }
  9454.  
  9455. } else {
  9456.  
  9457. $modelName = $prefix . ucfirst( $model );
  9458. if ( !class_exists( $modelName ) ) {
  9459. return NULL;
  9460. }
  9461. }
  9462. $obj = self::factory( $modelName );
  9463. $obj->loadBean( $bean );
  9464.  
  9465. return $obj;
  9466. }
  9467.  
  9468. /**
  9469. * @see BeanHelper::getExtractedToolbox
  9470. */
  9471. public function getExtractedToolbox()
  9472. {
  9473. return Facade::getExtractedToolbox();
  9474. }
  9475. }
  9476. }
  9477.  
  9478. namespace RedBeanPHP {
  9479.  
  9480. use RedBeanPHP\OODBBean as OODBBean;
  9481.  
  9482. /**
  9483. * SimpleModel
  9484. * Base Model For All RedBeanPHP Models using FUSE.
  9485. *
  9486. * RedBeanPHP FUSE is a mechanism to connect beans to posthoc
  9487. * models. Models are connected to beans by naming conventions.
  9488. * Actions on beans will result in actions on models.
  9489. *
  9490. * @file RedBeanPHP/SimpleModel.php
  9491. * @author Gabor de Mooij and the RedBeanPHP Team
  9492. * @license BSD/GPLv2
  9493. *
  9494. * @copyright
  9495. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  9496. * This source file is subject to the BSD/GPLv2 License that is bundled
  9497. * with this source code in the file license.txt.
  9498. */
  9499. class SimpleModel
  9500. {
  9501. /**
  9502. * @var OODBBean
  9503. */
  9504. protected $bean;
  9505.  
  9506. /**
  9507. * Used by FUSE: the ModelHelper class to connect a bean to a model.
  9508. * This method loads a bean in the model.
  9509. *
  9510. * @param OODBBean $bean bean to load
  9511. *
  9512. * @return void
  9513. */
  9514. public function loadBean( OODBBean $bean )
  9515. {
  9516. $this->bean = $bean;
  9517. }
  9518.  
  9519. /**
  9520. * Magic Getter to make the bean properties available from
  9521. * the $this-scope.
  9522. *
  9523. * @note this method returns a value, not a reference!
  9524. * To obtain a reference unbox the bean first!
  9525. *
  9526. * @param string $prop property to get
  9527. *
  9528. * @return mixed
  9529. */
  9530. public function __get( $prop )
  9531. {
  9532. return $this->bean->$prop;
  9533. }
  9534.  
  9535. /**
  9536. * Magic Setter.
  9537. * Sets the value directly as a bean property.
  9538. *
  9539. * @param string $prop property to set value of
  9540. * @param mixed $value value to set
  9541. *
  9542. * @return void
  9543. */
  9544. public function __set( $prop, $value )
  9545. {
  9546. $this->bean->$prop = $value;
  9547. }
  9548.  
  9549. /**
  9550. * Isset implementation.
  9551. * Implements the isset function for array-like access.
  9552. *
  9553. * @param string $key key to check
  9554. *
  9555. * @return boolean
  9556. */
  9557. public function __isset( $key )
  9558. {
  9559. return isset( $this->bean->$key );
  9560. }
  9561.  
  9562. /**
  9563. * Box the bean using the current model.
  9564. * This method wraps the current bean in this model.
  9565. * This method can be reached using FUSE through a simple
  9566. * OODBBean. The method returns a RedBeanPHP Simple Model.
  9567. * This is useful if you would like to rely on PHP type hinting.
  9568. * You can box your beans before passing them to functions or methods
  9569. * with typed parameters.
  9570. *
  9571. * @return SimpleModel
  9572. */
  9573. public function box()
  9574. {
  9575. return $this;
  9576. }
  9577.  
  9578. /**
  9579. * Unbox the bean from the model.
  9580. * This method returns the bean inside the model.
  9581. *
  9582. * @return OODBBean
  9583. */
  9584. public function unbox()
  9585. {
  9586. return $this->bean;
  9587. }
  9588. }
  9589. }
  9590.  
  9591. namespace RedBeanPHP {
  9592.  
  9593. use RedBeanPHP\Observer as Observer;
  9594. use RedBeanPHP\OODBBean as OODBBean;
  9595. use RedBeanPHP\Observable as Observable;
  9596.  
  9597. /**
  9598. * RedBean Model Helper.
  9599. *
  9600. * Connects beans to models.
  9601. * This is the core of so-called FUSE.
  9602. *
  9603. * @file RedBeanPHP/ModelHelper.php
  9604. * @author Gabor de Mooij and the RedBeanPHP Community
  9605. * @license BSD/GPLv2
  9606. *
  9607. * @copyright
  9608. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  9609. * This source file is subject to the BSD/GPLv2 License that is bundled
  9610. * with this source code in the file license.txt.
  9611. */
  9612. class SimpleModelHelper implements Observer
  9613. {
  9614. /**
  9615. * Gets notified by an observable.
  9616. * This method decouples the FUSE system from the actual beans.
  9617. * If a FUSE event happens 'update', this method will attempt to
  9618. * invoke the corresponding method on the bean.
  9619. *
  9620. * @param string $eventName i.e. 'delete', 'after_delete'
  9621. * @param OODBean $bean affected bean
  9622. *
  9623. * @return void
  9624. */
  9625. public function onEvent( $eventName, $bean )
  9626. {
  9627. $bean->$eventName();
  9628. }
  9629.  
  9630. /**
  9631. * Attaches the FUSE event listeners. Now the Model Helper will listen for
  9632. * CRUD events. If a CRUD event occurs it will send a signal to the model
  9633. * that belongs to the CRUD bean and this model will take over control from
  9634. * there. This method will attach the following event listeners to the observable:
  9635. *
  9636. * - 'update' (gets called by R::store, before the records gets inserted / updated)
  9637. * - 'after_update' (gets called by R::store, after the records have been inserted / updated)
  9638. * - 'open' (gets called by R::load, after the record has been retrieved)
  9639. * - 'delete' (gets called by R::trash, before deletion of record)
  9640. * - 'after_delete' (gets called by R::trash, after deletion)
  9641. * - 'dispense' (gets called by R::dispense)
  9642. *
  9643. * For every event type, this method will register this helper as a listener.
  9644. * The observable will notify the listener (this object) with the event ID and the
  9645. * affected bean. This helper will then process the event (onEvent) by invoking
  9646. * the event on the bean. If a bean offers a method with the same name as the
  9647. * event ID, this method will be invoked.
  9648. *
  9649. * @param Observable $observable object to observe
  9650. *
  9651. * @return void
  9652. */
  9653. public function attachEventListeners( Observable $observable )
  9654. {
  9655. foreach ( array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ) as $eventID ) {
  9656. $observable->addEventListener( $eventID, $this );
  9657. }
  9658. }
  9659. }
  9660. }
  9661.  
  9662. namespace RedBeanPHP {
  9663.  
  9664. use RedBeanPHP\ToolBox as ToolBox;
  9665. use RedBeanPHP\AssociationManager as AssociationManager;
  9666. use RedBeanPHP\OODBBean as OODBBean;
  9667.  
  9668. /**
  9669. * RedBeanPHP Tag Manager.
  9670. *
  9671. * The tag manager offers an easy way to quickly implement basic tagging
  9672. * functionality.
  9673. *
  9674. * Provides methods to tag beans and perform tag-based searches in the
  9675. * bean database.
  9676. *
  9677. * @file RedBeanPHP/TagManager.php
  9678. * @author Gabor de Mooij and the RedBeanPHP community
  9679. * @license BSD/GPLv2
  9680. *
  9681. * @copyright
  9682. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  9683. * This source file is subject to the BSD/GPLv2 License that is bundled
  9684. * with this source code in the file license.txt.
  9685. */
  9686. class TagManager
  9687. {
  9688. /**
  9689. * @var ToolBox
  9690. */
  9691. protected $toolbox;
  9692.  
  9693. /**
  9694. * @var AssociationManager
  9695. */
  9696. protected $associationManager;
  9697.  
  9698. /**
  9699. * @var OODBBean
  9700. */
  9701. protected $redbean;
  9702.  
  9703. /**
  9704. * Checks if the argument is a comma separated string, in this case
  9705. * it will split the string into words and return an array instead.
  9706. * In case of an array the argument will be returned 'as is'.
  9707. *
  9708. * @param array|string $tagList list of tags
  9709. *
  9710. * @return array
  9711. */
  9712. private function extractTagsIfNeeded( $tagList )
  9713. {
  9714. if ( $tagList !== FALSE && !is_array( $tagList ) ) {
  9715. $tags = explode( ',', (string) $tagList );
  9716. } else {
  9717. $tags = $tagList;
  9718. }
  9719.  
  9720. return $tags;
  9721. }
  9722.  
  9723. /**
  9724. * Finds a tag bean by it's title.
  9725. * Internal method.
  9726. *
  9727. * @param string $title title to search for
  9728. *
  9729. * @return OODBBean
  9730. */
  9731. protected function findTagByTitle( $title )
  9732. {
  9733. $beans = $this->redbean->find( 'tag', array( 'title' => array( $title ) ) );
  9734.  
  9735. if ( $beans ) {
  9736. $bean = reset( $beans );
  9737.  
  9738. return $bean;
  9739. }
  9740.  
  9741. return NULL;
  9742. }
  9743.  
  9744. /**
  9745. * Constructor.
  9746. * The tag manager offers an easy way to quickly implement basic tagging
  9747. * functionality.
  9748. *
  9749. * @param ToolBox $toolbox toolbox object
  9750. */
  9751. public function __construct( ToolBox $toolbox )
  9752. {
  9753. $this->toolbox = $toolbox;
  9754. $this->redbean = $toolbox->getRedBean();
  9755.  
  9756. $this->associationManager = $this->redbean->getAssociationManager();
  9757. }
  9758.  
  9759. /**
  9760. * Tests whether a bean has been associated with one ore more
  9761. * of the listed tags. If the third parameter is TRUE this method
  9762. * will return TRUE only if all tags that have been specified are indeed
  9763. * associated with the given bean, otherwise FALSE.
  9764. * If the third parameter is FALSE this
  9765. * method will return TRUE if one of the tags matches, FALSE if none
  9766. * match.
  9767. *
  9768. * Tag list can be either an array with tag names or a comma separated list
  9769. * of tag names.
  9770. *
  9771. * @param OODBBean $bean bean to check for tags
  9772. * @param array|string $tags list of tags
  9773. * @param boolean $all whether they must all match or just some
  9774. *
  9775. * @return boolean
  9776. */
  9777. public function hasTag( $bean, $tags, $all = FALSE )
  9778. {
  9779. $foundtags = $this->tag( $bean );
  9780.  
  9781. $tags = $this->extractTagsIfNeeded( $tags );
  9782. $same = array_intersect( $tags, $foundtags );
  9783.  
  9784. if ( $all ) {
  9785. return ( implode( ',', $same ) === implode( ',', $tags ) );
  9786. }
  9787.  
  9788. return (bool) ( count( $same ) > 0 );
  9789. }
  9790.  
  9791. /**
  9792. * Removes all sepcified tags from the bean. The tags specified in
  9793. * the second parameter will no longer be associated with the bean.
  9794. *
  9795. * Tag list can be either an array with tag names or a comma separated list
  9796. * of tag names.
  9797. *
  9798. * @param OODBBean $bean tagged bean
  9799. * @param array|string $tagList list of tags (names)
  9800. *
  9801. * @return void
  9802. */
  9803. public function untag( $bean, $tagList )
  9804. {
  9805. $tags = $this->extractTagsIfNeeded( $tagList );
  9806.  
  9807. foreach ( $tags as $tag ) {
  9808. if ( $t = $this->findTagByTitle( $tag ) ) {
  9809. $this->associationManager->unassociate( $bean, $t );
  9810. }
  9811. }
  9812. }
  9813.  
  9814. /**
  9815. * Tags a bean or returns tags associated with a bean.
  9816. * If $tagList is NULL or omitted this method will return a
  9817. * comma separated list of tags associated with the bean provided.
  9818. * If $tagList is a comma separated list (string) of tags all tags will
  9819. * be associated with the bean.
  9820. * You may also pass an array instead of a string.
  9821. *
  9822. * Tag list can be either an array with tag names or a comma separated list
  9823. * of tag names.
  9824. *
  9825. * @param OODBBean $bean bean to be tagged
  9826. * @param array|string $tagList a list of tags
  9827. *
  9828. * @return array
  9829. */
  9830. public function tag( OODBBean $bean, $tagList = NULL )
  9831. {
  9832. if ( is_null( $tagList ) ) {
  9833.  
  9834. $tags = $bean->sharedTag;
  9835. $foundTags = array();
  9836.  
  9837. foreach ( $tags as $tag ) {
  9838. $foundTags[] = $tag->title;
  9839. }
  9840.  
  9841. return $foundTags;
  9842. }
  9843.  
  9844. $this->associationManager->clearRelations( $bean, 'tag' );
  9845. $this->addTags( $bean, $tagList );
  9846.  
  9847. return $tagList;
  9848. }
  9849.  
  9850. /**
  9851. * Adds tags to a bean.
  9852. * If $tagList is a comma separated list of tags all tags will
  9853. * be associated with the bean.
  9854. * You may also pass an array instead of a string.
  9855. *
  9856. * Tag list can be either an array with tag names or a comma separated list
  9857. * of tag names.
  9858. *
  9859. * @param OODBBean $bean bean to add tags to
  9860. * @param array|string $tagList list of tags to add to bean
  9861. *
  9862. * @return void
  9863. */
  9864. public function addTags( OODBBean $bean, $tagList )
  9865. {
  9866. $tags = $this->extractTagsIfNeeded( $tagList );
  9867.  
  9868. if ( $tagList === FALSE ) {
  9869. return;
  9870. }
  9871.  
  9872. foreach ( $tags as $tag ) {
  9873. if ( !$t = $this->findTagByTitle( $tag ) ) {
  9874. $t = $this->redbean->dispense( 'tag' );
  9875. $t->title = $tag;
  9876.  
  9877. $this->redbean->store( $t );
  9878. }
  9879.  
  9880. $this->associationManager->associate( $bean, $t );
  9881. }
  9882. }
  9883.  
  9884. /**
  9885. * Returns all beans that have been tagged with one or more
  9886. * of the specified tags.
  9887. *
  9888. * Tag list can be either an array with tag names or a comma separated list
  9889. * of tag names.
  9890. *
  9891. * @param string $beanType type of bean you are looking for
  9892. * @param array|string $tagList list of tags to match
  9893. * @param string $sql additional SQL (use only for pagination)
  9894. * @param array $bindings bindings
  9895. *
  9896. * @return array
  9897. */
  9898. public function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
  9899. {
  9900. $tags = $this->extractTagsIfNeeded( $tagList );
  9901. $records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, FALSE, $sql, $bindings );
  9902.  
  9903. return $this->redbean->convertToBeans( $beanType, $records );
  9904. }
  9905.  
  9906. /**
  9907. * Returns all beans that have been tagged with ALL of the tags given.
  9908. *
  9909. * Tag list can be either an array with tag names or a comma separated list
  9910. * of tag names.
  9911. *
  9912. * @param string $beanType type of bean you are looking for
  9913. * @param array|string $tagList list of tags to match
  9914. *
  9915. * @return array
  9916. */
  9917. public function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
  9918. {
  9919. $tags = $this->extractTagsIfNeeded( $tagList );
  9920. $records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, TRUE, $sql, $bindings );
  9921.  
  9922. return $this->redbean->convertToBeans( $beanType, $records );
  9923. }
  9924. }
  9925. }
  9926.  
  9927. namespace RedBeanPHP {
  9928.  
  9929. use RedBeanPHP\ToolBox as ToolBox;
  9930. use RedBeanPHP\OODBBean as OODBBean;
  9931.  
  9932. /**
  9933. * Label Maker.
  9934. * Makes so-called label beans.
  9935. * A label is a bean with only an id, type and name property.
  9936. * Labels can be used to create simple entities like categories, tags or enums.
  9937. * This service class provides convenience methods to deal with this kind of
  9938. * beans.
  9939. *
  9940. * @file RedBeanPHP/LabelMaker.php
  9941. * @author Gabor de Mooij and the RedBeanPHP Community
  9942. * @license BSD/GPLv2
  9943. *
  9944. * @copyright
  9945. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  9946. * This source file is subject to the BSD/GPLv2 License that is bundled
  9947. * with this source code in the file license.txt.
  9948. */
  9949. class LabelMaker
  9950. {
  9951. /**
  9952. * @var ToolBox
  9953. */
  9954. protected $toolbox;
  9955.  
  9956. /**
  9957. * Constructor.
  9958. *
  9959. * @param ToolBox $toolbox
  9960. */
  9961. public function __construct( ToolBox $toolbox )
  9962. {
  9963. $this->toolbox = $toolbox;
  9964. }
  9965.  
  9966. /**
  9967. * A label is a bean with only an id, type and name property.
  9968. * This function will dispense beans for all entries in the array. The
  9969. * values of the array will be assigned to the name property of each
  9970. * individual bean.
  9971. *
  9972. * <code>
  9973. * $people = R::dispenseLabels( 'person', [ 'Santa', 'Claus' ] );
  9974. * </code>
  9975. *
  9976. * @param string $type type of beans you would like to have
  9977. * @param array $labels list of labels, names for each bean
  9978. *
  9979. * @return array
  9980. */
  9981. public function dispenseLabels( $type, $labels )
  9982. {
  9983. $labelBeans = array();
  9984. foreach ( $labels as $label ) {
  9985. $labelBean = $this->toolbox->getRedBean()->dispense( $type );
  9986. $labelBean->name = $label;
  9987. $labelBeans[] = $labelBean;
  9988. }
  9989.  
  9990. return $labelBeans;
  9991. }
  9992.  
  9993. /**
  9994. * Gathers labels from beans. This function loops through the beans,
  9995. * collects the value of the name property for each individual bean
  9996. * and stores the names in a new array. The array then gets sorted using the
  9997. * default sort function of PHP (sort).
  9998. *
  9999. * Usage:
  10000. *
  10001. * <code>
  10002. * $o1->name = 'hamburger';
  10003. * $o2->name = 'pizza';
  10004. * implode( ',', R::gatherLabels( [ $o1, $o2 ] ) ); //hamburger,pizza
  10005. * </code>
  10006. *
  10007. * Note that the return value is an array of strings, not beans.
  10008. *
  10009. * @param array $beans list of beans to loop through
  10010. *
  10011. * @return array
  10012. */
  10013. public function gatherLabels( $beans )
  10014. {
  10015. $labels = array();
  10016.  
  10017. foreach ( $beans as $bean ) {
  10018. $labels[] = $bean->name;
  10019. }
  10020.  
  10021. sort( $labels );
  10022.  
  10023. return $labels;
  10024. }
  10025.  
  10026. /**
  10027. * Fetches an ENUM from the database and creates it if necessary.
  10028. * An ENUM has the following format:
  10029. *
  10030. * <code>
  10031. * ENUM:VALUE
  10032. * </code>
  10033. *
  10034. * If you pass 'ENUM' only, this method will return an array of its
  10035. * values:
  10036. *
  10037. * <code>
  10038. * implode( ',', R::gatherLabels( R::enum( 'flavour' ) ) ) //'BANANA,MOCCA'
  10039. * </code>
  10040. *
  10041. * If you pass 'ENUM:VALUE' this method will return the specified enum bean
  10042. * and create it in the database if it does not exist yet:
  10043. *
  10044. * <code>
  10045. * $bananaFlavour = R::enum( 'flavour:banana' );
  10046. * $bananaFlavour->name;
  10047. * </code>
  10048. *
  10049. * So you can use this method to set an ENUM value in a bean:
  10050. *
  10051. * <code>
  10052. * $shake->flavour = R::enum( 'flavour:banana' );
  10053. * </code>
  10054. *
  10055. * the property flavour now contains the enum bean, a parent bean.
  10056. * In the database, flavour_id will point to the flavour record with name 'banana'.
  10057. *
  10058. * @param string $enum ENUM specification for label
  10059. *
  10060. * @return array|OODBBean
  10061. */
  10062. public function enum( $enum )
  10063. {
  10064. $oodb = $this->toolbox->getRedBean();
  10065.  
  10066. if ( strpos( $enum, ':' ) === FALSE ) {
  10067. $type = $enum;
  10068. $value = FALSE;
  10069. } else {
  10070. list( $type, $value ) = explode( ':', $enum );
  10071. $value = preg_replace( '/\W+/', '_', strtoupper( trim( $value ) ) );
  10072. }
  10073.  
  10074. /**
  10075. * We use simply find here, we could use inspect() in fluid mode etc,
  10076. * but this would be useless. At first sight it looks clean, you could even
  10077. * bake this into find(), however, find not only has to deal with the primary
  10078. * search type, people can also include references in the SQL part, so avoiding
  10079. * find failures does not matter, this is still the quickest way making use
  10080. * of existing functionality.
  10081. *
  10082. * @note There seems to be a bug in XDebug v2.3.2 causing suppressed
  10083. * exceptions like these to surface anyway, to prevent this use:
  10084. *
  10085. * "xdebug.default_enable = 0"
  10086. *
  10087. * Also see Github Issue #464
  10088. */
  10089. $values = $oodb->find( $type );
  10090.  
  10091. if ( $value === FALSE ) {
  10092. return $values;
  10093. }
  10094.  
  10095. foreach( $values as $enumItem ) {
  10096. if ( $enumItem->name === $value ) return $enumItem;
  10097. }
  10098.  
  10099. $newEnumItems = $this->dispenseLabels( $type, array( $value ) );
  10100. $newEnumItem = reset( $newEnumItems );
  10101.  
  10102. $oodb->store( $newEnumItem );
  10103.  
  10104. return $newEnumItem;
  10105. }
  10106. }
  10107. }
  10108.  
  10109. namespace RedBeanPHP {
  10110.  
  10111. use RedBeanPHP\QueryWriter as QueryWriter;
  10112. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  10113. use RedBeanPHP\RedException\SQL as SQLException;
  10114. use RedBeanPHP\Logger as Logger;
  10115. use RedBeanPHP\Logger\RDefault as RDefault;
  10116. use RedBeanPHP\Logger\RDefault\Debug as Debug;
  10117. use RedBeanPHP\Adapter as Adapter;
  10118. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  10119. use RedBeanPHP\RedException as RedException;
  10120. use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
  10121. use RedBeanPHP\Driver\RPDO as RPDO;
  10122. use RedBeanPHP\Util\MultiLoader as MultiLoader;
  10123. use RedBeanPHP\Util\Transaction as Transaction;
  10124. use RedBeanPHP\Util\Dump as Dump;
  10125. use RedBeanPHP\Util\DispenseHelper as DispenseHelper;
  10126. use RedBeanPHP\Util\ArrayTool as ArrayTool;
  10127.  
  10128. /**
  10129. * RedBean Facade
  10130. *
  10131. * Version Information
  10132. * RedBean Version @version 4.3
  10133. *
  10134. * This class hides the object landscape of
  10135. * RedBeanPHP behind a single letter class providing
  10136. * almost all functionality with simple static calls.
  10137. *
  10138. * @file RedBeanPHP/Facade.php
  10139. * @author Gabor de Mooij and the RedBeanPHP Community
  10140. * @license BSD/GPLv2
  10141. *
  10142. * @copyright
  10143. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  10144. * This source file is subject to the BSD/GPLv2 License that is bundled
  10145. * with this source code in the file license.txt.
  10146. */
  10147. class Facade
  10148. {
  10149. /**
  10150. * RedBeanPHP version constant.
  10151. */
  10152. const C_REDBEANPHP_VERSION = '4.3';
  10153.  
  10154. /**
  10155. * @var ToolBox
  10156. */
  10157. public static $toolbox;
  10158.  
  10159. /**
  10160. * @var OODB
  10161. */
  10162. private static $redbean;
  10163.  
  10164. /**
  10165. * @var QueryWriter
  10166. */
  10167. private static $writer;
  10168.  
  10169. /**
  10170. * @var DBAdapter
  10171. */
  10172. private static $adapter;
  10173.  
  10174. /**
  10175. * @var AssociationManager
  10176. */
  10177. private static $associationManager;
  10178.  
  10179. /**
  10180. * @var TagManager
  10181. */
  10182. private static $tagManager;
  10183.  
  10184. /**
  10185. * @var DuplicationManager
  10186. */
  10187. private static $duplicationManager;
  10188.  
  10189. /**
  10190. * @var LabelMaker
  10191. */
  10192. private static $labelMaker;
  10193.  
  10194. /**
  10195. * @var Finder
  10196. */
  10197. private static $finder;
  10198.  
  10199. /**
  10200. * @var Logger
  10201. */
  10202. private static $logger;
  10203.  
  10204. /**
  10205. * @var array
  10206. */
  10207. private static $plugins = array();
  10208.  
  10209. /**
  10210. * @var string
  10211. */
  10212. private static $exportCaseStyle = 'default';
  10213.  
  10214. /**
  10215. * Not in use (backward compatibility SQLHelper)
  10216. */
  10217. public static $f;
  10218.  
  10219. /**
  10220. * @var string
  10221. */
  10222. public static $currentDB = '';
  10223.  
  10224. /**
  10225. * @var array
  10226. */
  10227. public static $toolboxes = array();
  10228.  
  10229. /**
  10230. * Internal Query function, executes the desired query. Used by
  10231. * all facade query functions. This keeps things DRY.
  10232. *
  10233. * @param string $method desired query method (i.e. 'cell', 'col', 'exec' etc..)
  10234. * @param string $sql the sql you want to execute
  10235. * @param array $bindings array of values to be bound to query statement
  10236. *
  10237. * @return array
  10238. */
  10239. private static function query( $method, $sql, $bindings )
  10240. {
  10241. if ( !self::$redbean->isFrozen() ) {
  10242. try {
  10243. $rs = Facade::$adapter->$method( $sql, $bindings );
  10244. } catch ( SQLException $exception ) {
  10245. if ( self::$writer->sqlStateIn( $exception->getSQLState(),
  10246. array(
  10247. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  10248. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
  10249. )
  10250. ) {
  10251. return ( $method === 'getCell' ) ? NULL : array();
  10252. } else {
  10253. throw $exception;
  10254. }
  10255. }
  10256.  
  10257. return $rs;
  10258. } else {
  10259. return Facade::$adapter->$method( $sql, $bindings );
  10260. }
  10261. }
  10262.  
  10263. /**
  10264. * Returns the RedBeanPHP version string.
  10265. * The RedBeanPHP version string always has the same format "X.Y"
  10266. * where X is the major version number and Y is the minor version number.
  10267. * Point releases are not mentioned in the version string.
  10268. *
  10269. * @return string
  10270. */
  10271. public static function getVersion()
  10272. {
  10273. return self::C_REDBEANPHP_VERSION;
  10274. }
  10275.  
  10276. /**
  10277. * Tests the connection.
  10278. * Returns TRUE if connection has been established and
  10279. * FALSE otherwise.
  10280. *
  10281. * @return boolean
  10282. */
  10283. public static function testConnection()
  10284. {
  10285. if ( !isset( self::$adapter ) ) return FALSE;
  10286.  
  10287. $database = self::$adapter->getDatabase();
  10288. try {
  10289. @$database->connect();
  10290. } catch ( \Exception $e ) {}
  10291. return $database->isConnected();
  10292. }
  10293.  
  10294. /**
  10295. * Kickstarts redbean for you. This method should be called before you start using
  10296. * RedBean. The Setup() method can be called without any arguments, in this case it will
  10297. * try to create a SQLite database in /tmp called red.db (this only works on UNIX-like systems).
  10298. *
  10299. * @param string $dsn Database connection string
  10300. * @param string $username Username for database
  10301. * @param string $password Password for database
  10302. * @param boolean $frozen TRUE if you want to setup in frozen mode
  10303. *
  10304. * @return ToolBox
  10305. */
  10306. public static function setup( $dsn = NULL, $username = NULL, $password = NULL, $frozen = FALSE )
  10307. {
  10308. if ( is_null( $dsn ) ) {
  10309. $dsn = 'sqlite:/' . sys_get_temp_dir() . '/red.db';
  10310. }
  10311.  
  10312. self::addDatabase( 'default', $dsn, $username, $password, $frozen );
  10313. self::selectDatabase( 'default' );
  10314.  
  10315. return self::$toolbox;
  10316. }
  10317.  
  10318. /**
  10319. * Toggles Narrow Field Mode.
  10320. * See documentation in QueryWriter.
  10321. *
  10322. * @param boolean $mode TRUE = Narrow Field Mode
  10323. *
  10324. * @return void
  10325. */
  10326. public static function setNarrowFieldMode( $mode )
  10327. {
  10328. AQueryWriter::setNarrowFieldMode( $mode );
  10329. }
  10330.  
  10331. /**
  10332. * Wraps a transaction around a closure or string callback.
  10333. * If an Exception is thrown inside, the operation is automatically rolled back.
  10334. * If no Exception happens, it commits automatically.
  10335. * It also supports (simulated) nested transactions (that is useful when
  10336. * you have many methods that needs transactions but are unaware of
  10337. * each other).
  10338. *
  10339. * Example:
  10340. *
  10341. * <code>
  10342. * $from = 1;
  10343. * $to = 2;
  10344. * $amount = 300;
  10345. *
  10346. * R::transaction(function() use($from, $to, $amount)
  10347. * {
  10348. * $accountFrom = R::load('account', $from);
  10349. * $accountTo = R::load('account', $to);
  10350. * $accountFrom->money -= $amount;
  10351. * $accountTo->money += $amount;
  10352. * R::store($accountFrom);
  10353. * R::store($accountTo);
  10354. * });
  10355. * </code>
  10356. *
  10357. * @param callable $callback Closure (or other callable) with the transaction logic
  10358. *
  10359. * @return mixed
  10360. */
  10361. public static function transaction( $callback )
  10362. {
  10363. return Transaction::transaction( self::$adapter, $callback );
  10364. }
  10365.  
  10366. /**
  10367. * Adds a database to the facade, afterwards you can select the database using
  10368. * selectDatabase($key), where $key is the name you assigned to this database.
  10369. *
  10370. * Usage:
  10371. *
  10372. * <code>
  10373. * R::addDatabase( 'database-1', 'sqlite:/tmp/db1.txt' );
  10374. * R::selectDatabase( 'database-1' ); //to select database again
  10375. * </code>
  10376. *
  10377. * This method allows you to dynamically add (and select) new databases
  10378. * to the facade. Adding a database with the same key will cause an exception.
  10379. *
  10380. * @param string $key ID for the database
  10381. * @param string $dsn DSN for the database
  10382. * @param string $user user for connection
  10383. * @param NULL|string $pass password for connection
  10384. * @param bool $frozen whether this database is frozen or not
  10385. *
  10386. * @return void
  10387. */
  10388. public static function addDatabase( $key, $dsn, $user = NULL, $pass = NULL, $frozen = FALSE )
  10389. {
  10390. if ( isset( self::$toolboxes[$key] ) ) {
  10391. throw new RedException( 'A database has already been specified for this key.' );
  10392. }
  10393.  
  10394. if ( is_object($dsn) ) {
  10395. $db = new RPDO( $dsn );
  10396. $dbType = $db->getDatabaseType();
  10397. } else {
  10398. $db = new RPDO( $dsn, $user, $pass, TRUE );
  10399. $dbType = substr( $dsn, 0, strpos( $dsn, ':' ) );
  10400. }
  10401.  
  10402. $adapter = new DBAdapter( $db );
  10403.  
  10404. $writers = array(
  10405. 'pgsql' => 'PostgreSQL',
  10406. 'sqlite' => 'SQLiteT',
  10407. 'cubrid' => 'CUBRID',
  10408. 'mysql' => 'MySQL',
  10409. 'sqlsrv' => 'SQLServer',
  10410. );
  10411.  
  10412. $wkey = trim( strtolower( $dbType ) );
  10413. if ( !isset( $writers[$wkey] ) ) {
  10414. $wkey = preg_replace( '/\W/', '' , $wkey );
  10415. throw new RedException( 'Unsupported database ('.$wkey.').' );
  10416. }
  10417. $writerClass = '\\RedBeanPHP\\QueryWriter\\'.$writers[$wkey];
  10418. $writer = new $writerClass( $adapter );
  10419. $redbean = new OODB( $writer, $frozen );
  10420.  
  10421. self::$toolboxes[$key] = new ToolBox( $redbean, $adapter, $writer );
  10422. }
  10423.  
  10424. /**
  10425. * Determines whether a database identified with the specified key has
  10426. * already been added to the facade. This function will return TRUE
  10427. * if the database indicated by the key is available and FALSE otherwise.
  10428. *
  10429. * @param string $key the key/name of the database to check for
  10430. *
  10431. * @return boolean
  10432. */
  10433. public static function hasDatabase( $key )
  10434. {
  10435. return ( isset( self::$toolboxes[$key] ) );
  10436. }
  10437.  
  10438. /**
  10439. * Selects a different database for the Facade to work with.
  10440. * If you use the R::setup() you don't need this method. This method is meant
  10441. * for multiple database setups. This method selects the database identified by the
  10442. * database ID ($key). Use addDatabase() to add a new database, which in turn
  10443. * can be selected using selectDatabase(). If you use R::setup(), the resulting
  10444. * database will be stored under key 'default', to switch (back) to this database
  10445. * use R::selectDatabase( 'default' ). This method returns TRUE if the database has been
  10446. * switched and FALSE otherwise (for instance if you already using the specified database).
  10447. *
  10448. * @param string $key Key of the database to select
  10449. *
  10450. * @return boolean
  10451. */
  10452. public static function selectDatabase( $key )
  10453. {
  10454. if ( self::$currentDB === $key ) {
  10455. return FALSE;
  10456. }
  10457.  
  10458. if ( !isset( self::$toolboxes[$key] ) ) {
  10459. throw new RedException( 'Database not found in registry. Add database using R::addDatabase().' );
  10460. }
  10461.  
  10462. self::configureFacadeWithToolbox( self::$toolboxes[$key] );
  10463. self::$currentDB = $key;
  10464.  
  10465. return TRUE;
  10466. }
  10467.  
  10468. /**
  10469. * Toggles DEBUG mode.
  10470. * In Debug mode all SQL that happens under the hood will
  10471. * be printed to the screen and/or logged.
  10472. * If no database connection has been configured using R::setup() or
  10473. * R::selectDatabase() this method will throw an exception.
  10474. *
  10475. * There are 2 debug styles:
  10476. *
  10477. * Classic: separate parameter bindings, explicit and complete but less readable
  10478. * Fancy: interpersed bindings, truncates large strings, highlighted schema changes
  10479. *
  10480. * Fancy style is more readable but sometimes incomplete.
  10481. *
  10482. * The first parameter turns debugging ON or OFF.
  10483. * The second parameter indicates the mode of operation:
  10484. *
  10485. * 0 Log and write to STDOUT classic style (default)
  10486. * 1 Log only, class style
  10487. * 2 Log and write to STDOUT fancy style
  10488. * 3 Log only, fancy style
  10489. *
  10490. * This function always returns the logger instance created to generate the
  10491. * debug messages.
  10492. *
  10493. * @param boolean $tf debug mode (TRUE or FALSE)
  10494. * @param integer $mode mode of operation
  10495. *
  10496. * @return RDefault
  10497. * @throws RedException
  10498. */
  10499. public static function debug( $tf = TRUE, $mode = 0 )
  10500. {
  10501. if ($mode > 1) {
  10502. $mode -= 2;
  10503. $logger = new Debug;
  10504. } else {
  10505. $logger = new RDefault;
  10506. }
  10507.  
  10508. if ( !isset( self::$adapter ) ) {
  10509. throw new RedException( 'Use R::setup() first.' );
  10510. }
  10511. $logger->setMode($mode);
  10512. self::$adapter->getDatabase()->setDebugMode( $tf, $logger );
  10513.  
  10514. return $logger;
  10515. }
  10516.  
  10517. /**
  10518. * Turns on the fancy debugger.
  10519. * In 'fancy' mode the debugger will output queries with bound
  10520. * parameters inside the SQL itself. This method has been added to
  10521. * offer a convenient way to activate the fancy debugger system
  10522. * in one call.
  10523. *
  10524. * @param boolean $toggle TRUE to activate debugger and select 'fancy' mode
  10525. *
  10526. * @return void
  10527. */
  10528. public static function fancyDebug( $toggle = TRUE )
  10529. {
  10530. self::debug( $toggle, 2 );
  10531. }
  10532.  
  10533. /**
  10534. * Inspects the database schema. If you pass the type of a bean this
  10535. * method will return the fields of its table in the database.
  10536. * The keys of this array will be the field names and the values will be
  10537. * the column types used to store their values.
  10538. * If no type is passed, this method returns a list of all tables in the database.
  10539. *
  10540. * @param string $type Type of bean (i.e. table) you want to inspect
  10541. *
  10542. * @return array
  10543. */
  10544. public static function inspect( $type = NULL )
  10545. {
  10546. return ($type === NULL) ? self::$writer->getTables() : self::$writer->getColumns( $type );
  10547. }
  10548.  
  10549. /**
  10550. * Stores a bean in the database. This method takes a
  10551. * OODBBean Bean Object $bean and stores it
  10552. * in the database. If the database schema is not compatible
  10553. * with this bean and RedBean runs in fluid mode the schema
  10554. * will be altered to store the bean correctly.
  10555. * If the database schema is not compatible with this bean and
  10556. * RedBean runs in frozen mode it will throw an exception.
  10557. * This function returns the primary key ID of the inserted
  10558. * bean.
  10559. *
  10560. * The return value is an integer if possible. If it is not possible to
  10561. * represent the value as an integer a string will be returned.
  10562. *
  10563. * @param OODBBean|SimpleModel $bean bean to store
  10564. *
  10565. * @return integer|string
  10566. */
  10567. public static function store( $bean )
  10568. {
  10569. return self::$redbean->store( $bean );
  10570. }
  10571.  
  10572. /**
  10573. * Toggles fluid or frozen mode. In fluid mode the database
  10574. * structure is adjusted to accomodate your objects. In frozen mode
  10575. * this is not the case.
  10576. *
  10577. * You can also pass an array containing a selection of frozen types.
  10578. * Let's call this chilly mode, it's just like fluid mode except that
  10579. * certain types (i.e. tables) aren't touched.
  10580. *
  10581. * @param boolean|array $trueFalse
  10582. */
  10583. public static function freeze( $tf = TRUE )
  10584. {
  10585. self::$redbean->freeze( $tf );
  10586. }
  10587.  
  10588. /**
  10589. * Loads multiple types of beans with the same ID.
  10590. * This might look like a strange method, however it can be useful
  10591. * for loading a one-to-one relation.
  10592. *
  10593. * Usage:
  10594. * list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
  10595. *
  10596. * @param string|array $types the set of types to load at once
  10597. * @param mixed $id the common ID
  10598. *
  10599. * @return OODBBean
  10600. */
  10601. public static function loadMulti( $types, $id )
  10602. {
  10603. return MultiLoader::load( self::$redbean, $types, $id );
  10604. }
  10605.  
  10606. /**
  10607. * Loads a bean from the object database.
  10608. * It searches for a OODBBean Bean Object in the
  10609. * database. It does not matter how this bean has been stored.
  10610. * RedBean uses the primary key ID $id and the string $type
  10611. * to find the bean. The $type specifies what kind of bean you
  10612. * are looking for; this is the same type as used with the
  10613. * dispense() function. If RedBean finds the bean it will return
  10614. * the OODB Bean object; if it cannot find the bean
  10615. * RedBean will return a new bean of type $type and with
  10616. * primary key ID 0. In the latter case it acts basically the
  10617. * same as dispense().
  10618. *
  10619. * Important note:
  10620. * If the bean cannot be found in the database a new bean of
  10621. * the specified type will be generated and returned.
  10622. *
  10623. * @param string $type type of bean you want to load
  10624. * @param integer $id ID of the bean you want to load
  10625. *
  10626. * @return OODBBean
  10627. */
  10628. public static function load( $type, $id )
  10629. {
  10630. return self::$redbean->load( $type, $id );
  10631. }
  10632.  
  10633. /**
  10634. * Removes a bean from the database.
  10635. * This function will remove the specified OODBBean
  10636. * Bean Object from the database.
  10637. *
  10638. * This facade method also accepts a type-id combination,
  10639. * in the latter case this method will attempt to load the specified bean
  10640. * and THEN trash it.
  10641. *
  10642. * @param string|OODBBean|SimpleModel $bean bean you want to remove from database
  10643. * @param integer $id ID if the bean to trash (optional, type-id variant only)
  10644. *
  10645. * @return void
  10646. */
  10647. public static function trash( $beanOrType, $id = NULL )
  10648. {
  10649. if ( is_string( $beanOrType ) ) return self::trash( self::load( $beanOrType, $id ) );
  10650. return self::$redbean->trash( $beanOrType );
  10651. }
  10652.  
  10653. /**
  10654. * Dispenses a new RedBean OODB Bean for use with
  10655. * the rest of the methods.
  10656. *
  10657. * @param string|array $typeOrBeanArray type or bean array to import
  10658. * @param integer $number number of beans to dispense
  10659. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  10660. *
  10661. * @return array|OODBBean
  10662. */
  10663. public static function dispense( $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE )
  10664. {
  10665. return DispenseHelper::dispense( self::$redbean, $typeOrBeanArray, $num, $alwaysReturnArray );
  10666. }
  10667.  
  10668. /**
  10669. * Takes a comma separated list of bean types
  10670. * and dispenses these beans. For each type in the list
  10671. * you can specify the number of beans to be dispensed.
  10672. *
  10673. * Usage:
  10674. *
  10675. * <code>
  10676. * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
  10677. * </code>
  10678. *
  10679. * This will dispense a book, a page and a text. This way you can
  10680. * quickly dispense beans of various types in just one line of code.
  10681. *
  10682. * Usage:
  10683. *
  10684. * <code>
  10685. * list($book, $pages) = R::dispenseAll('book,page*100');
  10686. * </code>
  10687. *
  10688. * This returns an array with a book bean and then another array
  10689. * containing 100 page beans.
  10690. *
  10691. * @param string $order a description of the desired dispense order using the syntax above
  10692. * @param boolean $onlyArrays return only arrays even if amount < 2
  10693. *
  10694. * @return array
  10695. */
  10696. public static function dispenseAll( $order, $onlyArrays = FALSE )
  10697. {
  10698. return DispenseHelper::dispenseAll( self::$redbean, $order, $onlyArrays );
  10699. }
  10700.  
  10701. /**
  10702. * Convience method. Tries to find beans of a certain type,
  10703. * if no beans are found, it dispenses a bean of that type.
  10704. *
  10705. * @param string $type type of bean you are looking for
  10706. * @param string $sql SQL code for finding the bean
  10707. * @param array $bindings parameters to bind to SQL
  10708. *
  10709. * @return array
  10710. */
  10711. public static function findOrDispense( $type, $sql = NULL, $bindings = array() )
  10712. {
  10713. return self::$finder->findOrDispense( $type, $sql, $bindings );
  10714. }
  10715.  
  10716. /**
  10717. * Finds a bean using a type and a where clause (SQL).
  10718. * As with most Query tools in RedBean you can provide values to
  10719. * be inserted in the SQL statement by populating the value
  10720. * array parameter; you can either use the question mark notation
  10721. * or the slot-notation (:keyname).
  10722. *
  10723. * @param string $type the type of bean you are looking for
  10724. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  10725. * @param array $bindings array of values to be bound to parameters in query
  10726. *
  10727. * @return array
  10728. */
  10729. public static function find( $type, $sql = NULL, $bindings = array() )
  10730. {
  10731. return self::$finder->find( $type, $sql, $bindings );
  10732. }
  10733.  
  10734. /**
  10735. * @see Facade::find
  10736. * The findAll() method differs from the find() method in that it does
  10737. * not assume a WHERE-clause, so this is valid:
  10738. *
  10739. * R::findAll('person',' ORDER BY name DESC ');
  10740. *
  10741. * Your SQL does not have to start with a valid WHERE-clause condition.
  10742. *
  10743. * @param string $type the type of bean you are looking for
  10744. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  10745. * @param array $bindings array of values to be bound to parameters in query
  10746. *
  10747. * @return array
  10748. */
  10749. public static function findAll( $type, $sql = NULL, $bindings = array() )
  10750. {
  10751. return self::$finder->find( $type, $sql, $bindings );
  10752. }
  10753.  
  10754. /**
  10755. * @see Facade::find
  10756. * The variation also exports the beans (i.e. it returns arrays).
  10757. *
  10758. * @param string $type the type of bean you are looking for
  10759. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  10760. * @param array $bindings array of values to be bound to parameters in query
  10761. *
  10762. * @return array
  10763. */
  10764. public static function findAndExport( $type, $sql = NULL, $bindings = array() )
  10765. {
  10766. return self::$finder->findAndExport( $type, $sql, $bindings );
  10767. }
  10768.  
  10769. /**
  10770. * @see Facade::find
  10771. * This variation returns the first bean only.
  10772. *
  10773. * @param string $type the type of bean you are looking for
  10774. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  10775. * @param array $bindings array of values to be bound to parameters in query
  10776. *
  10777. * @return OODBBean
  10778. */
  10779. public static function findOne( $type, $sql = NULL, $bindings = array() )
  10780. {
  10781. return self::$finder->findOne( $type, $sql, $bindings );
  10782. }
  10783.  
  10784. /**
  10785. * @see Facade::find
  10786. * This variation returns the last bean only.
  10787. *
  10788. * @param string $type the type of bean you are looking for
  10789. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  10790. * @param array $bindings array of values to be bound to parameters in query
  10791. *
  10792. * @return OODBBean
  10793. */
  10794. public static function findLast( $type, $sql = NULL, $bindings = array() )
  10795. {
  10796. return self::$finder->findLast( $type, $sql, $bindings );
  10797. }
  10798.  
  10799. /**
  10800. * Finds a bean collection.
  10801. * Use this for large datasets.
  10802. *
  10803. * @param string $type the type of bean you are looking for
  10804. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  10805. * @param array $bindings array of values to be bound to parameters in query
  10806. *
  10807. * @return BeanCollection
  10808. */
  10809. public static function findCollection( $type, $sql = NULL, $bindings = array() )
  10810. {
  10811. return self::$finder->findCollection( $type, $sql, $bindings );
  10812. }
  10813.  
  10814. /**
  10815. * Finds multiple types of beans at once and offers additional
  10816. * remapping functionality. This is a very powerful yet complex function.
  10817. * For details see Finder::findMulti().
  10818. *
  10819. * @see Finder::findMulti()
  10820. *
  10821. * @param array|string $types a list of bean types to find
  10822. * @param string|array $sqlOrArr SQL query string or result set array
  10823. * @param array $bindings SQL bindings
  10824. * @param array $remappings an array of remapping arrays containing closures
  10825. *
  10826. * @return array
  10827. */
  10828. public static function findMulti( $types, $sql, $bindings = array(), $remappings = array() )
  10829. {
  10830. return self::$finder->findMulti( $types, $sql, $bindings, $remappings );
  10831. }
  10832.  
  10833. /**
  10834. * Returns an array of beans. Pass a type and a series of ids and
  10835. * this method will bring you the corresponding beans.
  10836. *
  10837. * important note: Because this method loads beans using the load()
  10838. * function (but faster) it will return empty beans with ID 0 for
  10839. * every bean that could not be located. The resulting beans will have the
  10840. * passed IDs as their keys.
  10841. *
  10842. * @param string $type type of beans
  10843. * @param array $ids ids to load
  10844. *
  10845. * @return array
  10846. */
  10847. public static function batch( $type, $ids )
  10848. {
  10849. return self::$redbean->batch( $type, $ids );
  10850. }
  10851.  
  10852. /**
  10853. * @see Facade::batch
  10854. *
  10855. * Alias for batch(). Batch method is older but since we added so-called *All
  10856. * methods like storeAll, trashAll, dispenseAll and findAll it seemed logical to
  10857. * improve the consistency of the Facade API and also add an alias for batch() called
  10858. * loadAll.
  10859. *
  10860. * @param string $type type of beans
  10861. * @param array $ids ids to load
  10862. *
  10863. * @return array
  10864. */
  10865. public static function loadAll( $type, $ids )
  10866. {
  10867. return self::$redbean->batch( $type, $ids );
  10868. }
  10869.  
  10870. /**
  10871. * Convenience function to execute Queries directly.
  10872. * Executes SQL.
  10873. *
  10874. * @param string $sql SQL query to execute
  10875. * @param array $bindings a list of values to be bound to query parameters
  10876. *
  10877. * @return integer
  10878. */
  10879. public static function exec( $sql, $bindings = array() )
  10880. {
  10881. return self::query( 'exec', $sql, $bindings );
  10882. }
  10883.  
  10884. /**
  10885. * Convenience function to execute Queries directly.
  10886. * Executes SQL.
  10887. *
  10888. * @param string $sql SQL query to execute
  10889. * @param array $bindings a list of values to be bound to query parameters
  10890. *
  10891. * @return array
  10892. */
  10893. public static function getAll( $sql, $bindings = array() )
  10894. {
  10895. return self::query( 'get', $sql, $bindings );
  10896. }
  10897.  
  10898. /**
  10899. * Convenience function to execute Queries directly.
  10900. * Executes SQL.
  10901. *
  10902. * @param string $sql SQL query to execute
  10903. * @param array $bindings a list of values to be bound to query parameters
  10904. *
  10905. * @return string
  10906. */
  10907. public static function getCell( $sql, $bindings = array() )
  10908. {
  10909. return self::query( 'getCell', $sql, $bindings );
  10910. }
  10911.  
  10912. /**
  10913. * Convenience function to execute Queries directly.
  10914. * Executes SQL.
  10915. *
  10916. * @param string $sql SQL query to execute
  10917. * @param array $bindings a list of values to be bound to query parameters
  10918. *
  10919. * @return array
  10920. */
  10921. public static function getRow( $sql, $bindings = array() )
  10922. {
  10923. return self::query( 'getRow', $sql, $bindings );
  10924. }
  10925.  
  10926. /**
  10927. * Convenience function to execute Queries directly.
  10928. * Executes SQL.
  10929. *
  10930. * @param string $sql SQL query to execute
  10931. * @param array $bindings a list of values to be bound to query parameters
  10932. *
  10933. * @return array
  10934. */
  10935. public static function getCol( $sql, $bindings = array() )
  10936. {
  10937. return self::query( 'getCol', $sql, $bindings );
  10938. }
  10939.  
  10940. /**
  10941. * Convenience function to execute Queries directly.
  10942. * Executes SQL.
  10943. * Results will be returned as an associative array. The first
  10944. * column in the select clause will be used for the keys in this array and
  10945. * the second column will be used for the values. If only one column is
  10946. * selected in the query, both key and value of the array will have the
  10947. * value of this field for each row.
  10948. *
  10949. * @param string $sql SQL query to execute
  10950. * @param array $bindings a list of values to be bound to query parameters
  10951. *
  10952. * @return array
  10953. */
  10954. public static function getAssoc( $sql, $bindings = array() )
  10955. {
  10956. return self::query( 'getAssoc', $sql, $bindings );
  10957. }
  10958.  
  10959. /**
  10960. * Convenience function to execute Queries directly.
  10961. * Executes SQL.
  10962. * Results will be returned as an associative array indexed by the first
  10963. * column in the select.
  10964. *
  10965. * @param string $sql SQL query to execute
  10966. * @param array $bindings a list of values to be bound to query parameters
  10967. *
  10968. * @return array
  10969. */
  10970. public static function getAssocRow( $sql, $bindings = array() )
  10971. {
  10972. return self::query( 'getAssocRow', $sql, $bindings );
  10973. }
  10974.  
  10975. /**
  10976. * Returns the insert ID for databases that support/require this
  10977. * functionality. Alias for R::getAdapter()->getInsertID().
  10978. *
  10979. * @return mixed
  10980. */
  10981. public static function getInsertID()
  10982. {
  10983. return self::$adapter->getInsertID();
  10984. }
  10985.  
  10986. /**
  10987. * Makes a copy of a bean. This method makes a deep copy
  10988. * of the bean.The copy will have the following features.
  10989. * - All beans in own-lists will be duplicated as well
  10990. * - All references to shared beans will be copied but not the shared beans themselves
  10991. * - All references to parent objects (_id fields) will be copied but not the parents themselves
  10992. * In most cases this is the desired scenario for copying beans.
  10993. * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
  10994. * (i.e. one that already has been processed) the ID of the bean will be returned.
  10995. * This should not happen though.
  10996. *
  10997. * Note:
  10998. * This function does a reflectional database query so it may be slow.
  10999. *
  11000. * @deprecated
  11001. * This function is deprecated in favour of R::duplicate().
  11002. * This function has a confusing method signature, the R::duplicate() function
  11003. * only accepts two arguments: bean and filters.
  11004. *
  11005. * @param OODBBean $bean bean to be copied
  11006. * @param array $trail for internal usage, pass array()
  11007. * @param boolean $pid for internal usage
  11008. * @param array $white white list filter with bean types to duplicate
  11009. *
  11010. * @return array
  11011. */
  11012. public static function dup( $bean, $trail = array(), $pid = FALSE, $filters = array() )
  11013. {
  11014. self::$duplicationManager->setFilters( $filters );
  11015. return self::$duplicationManager->dup( $bean, $trail, $pid );
  11016. }
  11017.  
  11018. /**
  11019. * Makes a deep copy of a bean. This method makes a deep copy
  11020. * of the bean.The copy will have the following:
  11021. *
  11022. * * All beans in own-lists will be duplicated as well
  11023. * * All references to shared beans will be copied but not the shared beans themselves
  11024. * * All references to parent objects (_id fields) will be copied but not the parents themselves
  11025. *
  11026. * In most cases this is the desired scenario for copying beans.
  11027. * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
  11028. * (i.e. one that already has been processed) the ID of the bean will be returned.
  11029. * This should not happen though.
  11030. *
  11031. * Note:
  11032. * This function does a reflectional database query so it may be slow.
  11033. *
  11034. * Note:
  11035. * This is a simplified version of the deprecated R::dup() function.
  11036. *
  11037. * @param OODBBean $bean bean to be copied
  11038. * @param array $white white list filter with bean types to duplicate
  11039. *
  11040. * @return array
  11041. */
  11042. public static function duplicate( $bean, $filters = array() )
  11043. {
  11044. return self::dup( $bean, array(), FALSE, $filters );
  11045. }
  11046.  
  11047. /**
  11048. * Exports a collection of beans. Handy for XML/JSON exports with a
  11049. * Javascript framework like Dojo or ExtJS.
  11050. * What will be exported:
  11051. *
  11052. * * contents of the bean
  11053. * * all own bean lists (recursively)
  11054. * * all shared beans (not THEIR own lists)
  11055. *
  11056. * @param array|OODBBean $beans beans to be exported
  11057. * @param boolean $parents whether you want parent beans to be exported
  11058. * @param array $filters whitelist of types
  11059. *
  11060. * @return array
  11061. */
  11062. public static function exportAll( $beans, $parents = FALSE, $filters = array())
  11063. {
  11064. return self::$duplicationManager->exportAll( $beans, $parents, $filters, self::$exportCaseStyle );
  11065. }
  11066.  
  11067. /**
  11068. * Selects case style for export.
  11069. * This will determine the case style for the keys of exported beans (see exportAll).
  11070. * The following options are accepted:
  11071. *
  11072. * * 'default' RedBeanPHP by default enforces Snake Case (i.e. book_id is_valid )
  11073. * * 'camel' Camel Case (i.e. bookId isValid )
  11074. * * 'dolphin' Dolphin Case (i.e. bookID isValid ) Like CamelCase but ID is written all uppercase
  11075. *
  11076. * @warning RedBeanPHP transforms camelCase to snake_case using a slightly different
  11077. * algorithm, it also converts isACL to is_acl (not is_a_c_l) and bookID to book_id.
  11078. * Due to information loss this cannot be corrected. However if you might try
  11079. * DolphinCase for IDs it takes into account the exception concerning IDs.
  11080. *
  11081. * @param string $caseStyle case style identifier
  11082. *
  11083. * @return void
  11084. */
  11085. public static function useExportCase( $caseStyle = 'default' )
  11086. {
  11087. if ( !in_array( $caseStyle, array( 'default', 'camel', 'dolphin' ) ) ) throw new RedException( 'Invalid case selected.' );
  11088. self::$exportCaseStyle = $caseStyle;
  11089. }
  11090.  
  11091. /**
  11092. * Converts a series of rows to beans.
  11093. * This method converts a series of rows to beans.
  11094. * The type of the desired output beans can be specified in the
  11095. * first parameter. The second parameter is meant for the database
  11096. * result rows.
  11097. *
  11098. * Usage:
  11099. *
  11100. * <code>
  11101. * $rows = R::getAll( 'SELECT * FROM ...' )
  11102. * $beans = R::convertToBeans( $rows );
  11103. * </code>
  11104. *
  11105. * As of version 4.3.2 you can specify a meta-mask.
  11106. * Data from columns with names starting with the value specified in the mask
  11107. * will be transferred to the meta section of a bean (under data.bundle).
  11108. *
  11109. * <code>
  11110. * $rows = R::getAll( 'SELECT FROM... COUNT(*) AS extra_count ...' );
  11111. * $beans = R::convertToBeans( $rows );
  11112. * $bean = reset( $beans );
  11113. * $data = $bean->getMeta( 'data.bundle' );
  11114. * $extra_count = $data['extra_count'];
  11115. * </code>
  11116. *
  11117. * @param string $type type of beans to produce
  11118. * @param array $rows must contain an array of array
  11119. *
  11120. * @return array
  11121. */
  11122. public static function convertToBeans( $type, $rows, $metamask = NULL )
  11123. {
  11124. return self::$redbean->convertToBeans( $type, $rows, $metamask );
  11125. }
  11126.  
  11127. /**
  11128. * Just like converToBeans, but for one bean.
  11129. * @see convertToBeans for more details.
  11130. *
  11131. * @param string $type type of beans to produce
  11132. * @param array $row one row from the database
  11133. *
  11134. * @return array
  11135. */
  11136. public static function convertToBean( $type, $row, $metamask = NULL )
  11137. {
  11138. $beans = self::$redbean->convertToBeans( $type, array( $row ), $metamask );
  11139. $bean = reset( $beans );
  11140. return $bean;
  11141. }
  11142.  
  11143. /**
  11144. * Part of RedBeanPHP Tagging API.
  11145. * Tests whether a bean has been associated with one ore more
  11146. * of the listed tags. If the third parameter is TRUE this method
  11147. * will return TRUE only if all tags that have been specified are indeed
  11148. * associated with the given bean, otherwise FALSE.
  11149. * If the third parameter is FALSE this
  11150. * method will return TRUE if one of the tags matches, FALSE if none
  11151. * match.
  11152. *
  11153. * @param OODBBean $bean bean to check for tags
  11154. * @param array $tags list of tags
  11155. * @param boolean $all whether they must all match or just some
  11156. *
  11157. * @return boolean
  11158. */
  11159. public static function hasTag( $bean, $tags, $all = FALSE )
  11160. {
  11161. return self::$tagManager->hasTag( $bean, $tags, $all );
  11162. }
  11163.  
  11164. /**
  11165. * Part of RedBeanPHP Tagging API.
  11166. * Removes all specified tags from the bean. The tags specified in
  11167. * the second parameter will no longer be associated with the bean.
  11168. *
  11169. * @param OODBBean $bean tagged bean
  11170. * @param array $tagList list of tags (names)
  11171. *
  11172. * @return void
  11173. */
  11174. public static function untag( $bean, $tagList )
  11175. {
  11176. self::$tagManager->untag( $bean, $tagList );
  11177. }
  11178.  
  11179. /**
  11180. * Part of RedBeanPHP Tagging API.
  11181. * Tags a bean or returns tags associated with a bean.
  11182. * If $tagList is NULL or omitted this method will return a
  11183. * comma separated list of tags associated with the bean provided.
  11184. * If $tagList is a comma separated list (string) of tags all tags will
  11185. * be associated with the bean.
  11186. * You may also pass an array instead of a string.
  11187. *
  11188. * @param OODBBean $bean bean to tag
  11189. * @param mixed $tagList tags to attach to the specified bean
  11190. *
  11191. * @return string
  11192. */
  11193. public static function tag( OODBBean $bean, $tagList = NULL )
  11194. {
  11195. return self::$tagManager->tag( $bean, $tagList );
  11196. }
  11197.  
  11198. /**
  11199. * Part of RedBeanPHP Tagging API.
  11200. * Adds tags to a bean.
  11201. * If $tagList is a comma separated list of tags all tags will
  11202. * be associated with the bean.
  11203. * You may also pass an array instead of a string.
  11204. *
  11205. * @param OODBBean $bean bean to tag
  11206. * @param array $tagList list of tags to add to bean
  11207. *
  11208. * @return void
  11209. */
  11210. public static function addTags( OODBBean $bean, $tagList )
  11211. {
  11212. self::$tagManager->addTags( $bean, $tagList );
  11213. }
  11214.  
  11215. /**
  11216. * Part of RedBeanPHP Tagging API.
  11217. * Returns all beans that have been tagged with one of the tags given.
  11218. *
  11219. * @param string $beanType type of bean you are looking for
  11220. * @param array $tagList list of tags to match
  11221. * @param string $sql additional SQL query snippet
  11222. * @param array $bindings a list of values to bind to the query parameters
  11223. *
  11224. * @return array
  11225. */
  11226. public static function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
  11227. {
  11228. return self::$tagManager->tagged( $beanType, $tagList, $sql, $bindings );
  11229. }
  11230.  
  11231. /**
  11232. * Part of RedBeanPHP Tagging API.
  11233. * Returns all beans that have been tagged with ALL of the tags given.
  11234. *
  11235. * @param string $beanType type of bean you are looking for
  11236. * @param array $tagList list of tags to match
  11237. * @param string $sql additional SQL query snippet
  11238. * @param array $bindings a list of values to bind to the query parameters
  11239. *
  11240. * @return array
  11241. */
  11242. public static function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
  11243. {
  11244. return self::$tagManager->taggedAll( $beanType, $tagList, $sql, $bindings );
  11245. }
  11246.  
  11247. /**
  11248. * Wipes all beans of type $beanType.
  11249. *
  11250. * @param string $beanType type of bean you want to destroy entirely
  11251. *
  11252. * @return boolean
  11253. */
  11254. public static function wipe( $beanType )
  11255. {
  11256. return Facade::$redbean->wipe( $beanType );
  11257. }
  11258.  
  11259. /**
  11260. * Counts the number of beans of type $type.
  11261. * This method accepts a second argument to modify the count-query.
  11262. * A third argument can be used to provide bindings for the SQL snippet.
  11263. *
  11264. * @param string $type type of bean we are looking for
  11265. * @param string $addSQL additional SQL snippet
  11266. * @param array $bindings parameters to bind to SQL
  11267. *
  11268. * @return integer
  11269. */
  11270. public static function count( $type, $addSQL = '', $bindings = array() )
  11271. {
  11272. return Facade::$redbean->count( $type, $addSQL, $bindings );
  11273. }
  11274.  
  11275. /**
  11276. * Configures the facade, want to have a new Writer? A new Object Database or a new
  11277. * Adapter and you want it on-the-fly? Use this method to hot-swap your facade with a new
  11278. * toolbox.
  11279. *
  11280. * @param ToolBox $tb toolbox to configure facade with
  11281. *
  11282. * @return ToolBox
  11283. */
  11284. public static function configureFacadeWithToolbox( ToolBox $tb )
  11285. {
  11286. $oldTools = self::$toolbox;
  11287. self::$toolbox = $tb;
  11288. self::$writer = self::$toolbox->getWriter();
  11289. self::$adapter = self::$toolbox->getDatabaseAdapter();
  11290. self::$redbean = self::$toolbox->getRedBean();
  11291. self::$finder = new Finder( self::$toolbox );
  11292. self::$associationManager = new AssociationManager( self::$toolbox );
  11293. self::$redbean->setAssociationManager( self::$associationManager );
  11294. self::$labelMaker = new LabelMaker( self::$toolbox );
  11295. $helper = new SimpleModelHelper();
  11296. $helper->attachEventListeners( self::$redbean );
  11297. self::$redbean->setBeanHelper( new SimpleFacadeBeanHelper );
  11298. self::$duplicationManager = new DuplicationManager( self::$toolbox );
  11299. self::$tagManager = new TagManager( self::$toolbox );
  11300. return $oldTools;
  11301. }
  11302.  
  11303. /**
  11304. * Facade Convience method for adapter transaction system.
  11305. * Begins a transaction.
  11306. *
  11307. * @return bool
  11308. */
  11309. public static function begin()
  11310. {
  11311. if ( !self::$redbean->isFrozen() ) return FALSE;
  11312. self::$adapter->startTransaction();
  11313. return TRUE;
  11314. }
  11315.  
  11316. /**
  11317. * Facade Convience method for adapter transaction system.
  11318. * Commits a transaction.
  11319. *
  11320. * @return bool
  11321. */
  11322. public static function commit()
  11323. {
  11324. if ( !self::$redbean->isFrozen() ) return FALSE;
  11325. self::$adapter->commit();
  11326. return TRUE;
  11327. }
  11328.  
  11329. /**
  11330. * Facade Convience method for adapter transaction system.
  11331. * Rolls back a transaction.
  11332. *
  11333. * @return bool
  11334. */
  11335. public static function rollback()
  11336. {
  11337. if ( !self::$redbean->isFrozen() ) return FALSE;
  11338. self::$adapter->rollback();
  11339. return TRUE;
  11340. }
  11341.  
  11342. /**
  11343. * Returns a list of columns. Format of this array:
  11344. * array( fieldname => type )
  11345. * Note that this method only works in fluid mode because it might be
  11346. * quite heavy on production servers!
  11347. *
  11348. * @param string $table name of the table (not type) you want to get columns of
  11349. *
  11350. * @return array
  11351. */
  11352. public static function getColumns( $table )
  11353. {
  11354. return self::$writer->getColumns( $table );
  11355. }
  11356.  
  11357. /**
  11358. * Generates question mark slots for an array of values.
  11359. *
  11360. * @param array $array array to generate question mark slots for
  11361. *
  11362. * @return string
  11363. */
  11364. public static function genSlots( $array, $template = NULL )
  11365. {
  11366. return ArrayTool::genSlots( $array, $template );
  11367. }
  11368.  
  11369. /**
  11370. * Flattens a multi dimensional bindings array for use with genSlots().
  11371. *
  11372. * @param array $array array to flatten
  11373. *
  11374. * @return array
  11375. */
  11376. public static function flat( $array, $result = array() )
  11377. {
  11378. return ArrayTool::flat( $array, $result );
  11379. }
  11380.  
  11381. /**
  11382. * Nukes the entire database.
  11383. * This will remove all schema structures from the database.
  11384. * Only works in fluid mode. Be careful with this method.
  11385. *
  11386. * @warning dangerous method, will remove all tables, columns etc.
  11387. *
  11388. * @return void
  11389. */
  11390. public static function nuke()
  11391. {
  11392. if ( !self::$redbean->isFrozen() ) {
  11393. self::$writer->wipeAll();
  11394. }
  11395. }
  11396.  
  11397. /**
  11398. * Short hand function to store a set of beans at once, IDs will be
  11399. * returned as an array. For information please consult the R::store()
  11400. * function.
  11401. * A loop saver.
  11402. *
  11403. * @param array $beans list of beans to be stored
  11404. *
  11405. * @return array
  11406. */
  11407. public static function storeAll( $beans )
  11408. {
  11409. $ids = array();
  11410. foreach ( $beans as $bean ) {
  11411. $ids[] = self::store( $bean );
  11412. }
  11413. return $ids;
  11414. }
  11415.  
  11416. /**
  11417. * Short hand function to trash a set of beans at once.
  11418. * For information please consult the R::trash() function.
  11419. * A loop saver.
  11420. *
  11421. * @param array $beans list of beans to be trashed
  11422. *
  11423. * @return void
  11424. */
  11425. public static function trashAll( $beans )
  11426. {
  11427. foreach ( $beans as $bean ) {
  11428. self::trash( $bean );
  11429. }
  11430. }
  11431.  
  11432. /**
  11433. * Toggles Writer Cache.
  11434. * Turns the Writer Cache on or off. The Writer Cache is a simple
  11435. * query based caching system that may improve performance without the need
  11436. * for cache management. This caching system will cache non-modifying queries
  11437. * that are marked with special SQL comments. As soon as a non-marked query
  11438. * gets executed the cache will be flushed. Only non-modifying select queries
  11439. * have been marked therefore this mechanism is a rather safe way of caching, requiring
  11440. * no explicit flushes or reloads. Of course this does not apply if you intend to test
  11441. * or simulate concurrent querying.
  11442. *
  11443. * @param boolean $yesNo TRUE to enable cache, FALSE to disable cache
  11444. *
  11445. * @return void
  11446. */
  11447. public static function useWriterCache( $yesNo )
  11448. {
  11449. self::getWriter()->setUseCache( $yesNo );
  11450. }
  11451.  
  11452. /**
  11453. * A label is a bean with only an id, type and name property.
  11454. * This function will dispense beans for all entries in the array. The
  11455. * values of the array will be assigned to the name property of each
  11456. * individual bean.
  11457. *
  11458. * @param string $type type of beans you would like to have
  11459. * @param array $labels list of labels, names for each bean
  11460. *
  11461. * @return array
  11462. */
  11463. public static function dispenseLabels( $type, $labels )
  11464. {
  11465. return self::$labelMaker->dispenseLabels( $type, $labels );
  11466. }
  11467.  
  11468. /**
  11469. * Generates and returns an ENUM value. This is how RedBeanPHP handles ENUMs.
  11470. * Either returns a (newly created) bean respresenting the desired ENUM
  11471. * value or returns a list of all enums for the type.
  11472. *
  11473. * To obtain (and add if necessary) an ENUM value:
  11474. *
  11475. * <code>
  11476. * $tea->flavour = R::enum( 'flavour:apple' );
  11477. * </code>
  11478. *
  11479. * Returns a bean of type 'flavour' with name = apple.
  11480. * This will add a bean with property name (set to APPLE) to the database
  11481. * if it does not exist yet.
  11482. *
  11483. * To obtain all flavours:
  11484. *
  11485. * <code>
  11486. * R::enum('flavour');
  11487. * </code>
  11488. *
  11489. * To get a list of all flavour names:
  11490. *
  11491. * <code>
  11492. * R::gatherLabels( R::enum( 'flavour' ) );
  11493. * </code>
  11494. *
  11495. * @param string $enum either type or type-value
  11496. *
  11497. * @return array|OODBBean
  11498. */
  11499. public static function enum( $enum )
  11500. {
  11501. return self::$labelMaker->enum( $enum );
  11502. }
  11503.  
  11504. /**
  11505. * Gathers labels from beans. This function loops through the beans,
  11506. * collects the values of the name properties of each individual bean
  11507. * and stores the names in a new array. The array then gets sorted using the
  11508. * default sort function of PHP (sort).
  11509. *
  11510. * @param array $beans list of beans to loop
  11511. *
  11512. * @return array
  11513. */
  11514. public static function gatherLabels( $beans )
  11515. {
  11516. return self::$labelMaker->gatherLabels( $beans );
  11517. }
  11518.  
  11519. /**
  11520. * Closes the database connection.
  11521. *
  11522. * @return void
  11523. */
  11524. public static function close()
  11525. {
  11526. if ( isset( self::$adapter ) ) {
  11527. self::$adapter->close();
  11528. }
  11529. }
  11530.  
  11531. /**
  11532. * Simple convenience function, returns ISO date formatted representation
  11533. * of $time.
  11534. *
  11535. * @param mixed $time UNIX timestamp
  11536. *
  11537. * @return string
  11538. */
  11539. public static function isoDate( $time = NULL )
  11540. {
  11541. if ( !$time ) {
  11542. $time = time();
  11543. }
  11544.  
  11545. return @date( 'Y-m-d', $time );
  11546. }
  11547.  
  11548. /**
  11549. * Simple convenience function, returns ISO date time
  11550. * formatted representation
  11551. * of $time.
  11552. *
  11553. * @param mixed $time UNIX timestamp
  11554. *
  11555. * @return string
  11556. */
  11557. public static function isoDateTime( $time = NULL )
  11558. {
  11559. if ( !$time ) $time = time();
  11560. return @date( 'Y-m-d H:i:s', $time );
  11561. }
  11562.  
  11563. /**
  11564. * Optional accessor for neat code.
  11565. * Sets the database adapter you want to use.
  11566. *
  11567. * @param Adapter $adapter Database Adapter for facade to use
  11568. *
  11569. * @return void
  11570. */
  11571. public static function setDatabaseAdapter( Adapter $adapter )
  11572. {
  11573. self::$adapter = $adapter;
  11574. }
  11575.  
  11576. /**
  11577. * Optional accessor for neat code.
  11578. * Sets the database adapter you want to use.
  11579. *
  11580. * @param QueryWriter $writer Query Writer instance for facade to use
  11581. *
  11582. * @return void
  11583. */
  11584. public static function setWriter( QueryWriter $writer )
  11585. {
  11586. self::$writer = $writer;
  11587. }
  11588.  
  11589. /**
  11590. * Optional accessor for neat code.
  11591. * Sets the database adapter you want to use.
  11592. *
  11593. * @param OODB $redbean Object Database for facade to use
  11594. */
  11595. public static function setRedBean( OODB $redbean )
  11596. {
  11597. self::$redbean = $redbean;
  11598. }
  11599.  
  11600. /**
  11601. * Optional accessor for neat code.
  11602. * Sets the database adapter you want to use.
  11603. *
  11604. * @return DBAdapter
  11605. */
  11606. public static function getDatabaseAdapter()
  11607. {
  11608. return self::$adapter;
  11609. }
  11610.  
  11611. /**
  11612. * In case you use PDO (which is recommended and the default but not mandatory, hence
  11613. * the database adapter), you can use this method to obtain the PDO object directly.
  11614. * This is a convenience method, it will do the same as:
  11615. *
  11616. * <code>
  11617. * R::getDatabaseAdapter()->getDatabase()->getPDO();
  11618. * </code>
  11619. *
  11620. * If the PDO object could not be found, for whatever reason, this method
  11621. * will return NULL instead.
  11622. *
  11623. * @return NULL|PDO
  11624. */
  11625. public static function getPDO()
  11626. {
  11627. $databaseAdapter = self::getDatabaseAdapter();
  11628. if ( is_null( $databaseAdapter ) ) return NULL;
  11629. $database = $databaseAdapter->getDatabase();
  11630. if ( is_null( $database ) ) return NULL;
  11631. if ( !method_exists( $database, 'getPDO' ) ) return NULL;
  11632. return $database->getPDO();
  11633. }
  11634.  
  11635. /**
  11636. * Returns the current duplication manager instance.
  11637. *
  11638. * @return DuplicationManager
  11639. */
  11640. public static function getDuplicationManager()
  11641. {
  11642. return self::$duplicationManager;
  11643. }
  11644.  
  11645. /**
  11646. * Optional accessor for neat code.
  11647. * Sets the database adapter you want to use.
  11648. *
  11649. * @return QueryWriter
  11650. */
  11651. public static function getWriter()
  11652. {
  11653. return self::$writer;
  11654. }
  11655.  
  11656. /**
  11657. * Optional accessor for neat code.
  11658. * Sets the database adapter you want to use.
  11659. *
  11660. * @return OODB
  11661. */
  11662. public static function getRedBean()
  11663. {
  11664. return self::$redbean;
  11665. }
  11666.  
  11667. /**
  11668. * Returns the toolbox currently used by the facade.
  11669. * To set the toolbox use R::setup() or R::configureFacadeWithToolbox().
  11670. * To create a toolbox use Setup::kickstart(). Or create a manual
  11671. * toolbox using the ToolBox class.
  11672. *
  11673. * @return ToolBox
  11674. */
  11675. public static function getToolBox()
  11676. {
  11677. return self::$toolbox;
  11678. }
  11679.  
  11680. /**
  11681. * Mostly for internal use, but might be handy
  11682. * for some users.
  11683. * This returns all the components of the currently
  11684. * selected toolbox.
  11685. *
  11686. * Returns the components in the following order:
  11687. *
  11688. * # OODB instance (getRedBean())
  11689. * # Database Adapter
  11690. * # Query Writer
  11691. * # Toolbox itself
  11692. *
  11693. * @return array
  11694. */
  11695. public static function getExtractedToolbox()
  11696. {
  11697. return array( self::$redbean, self::$adapter, self::$writer, self::$toolbox );
  11698. }
  11699.  
  11700. /**
  11701. * Facade method for AQueryWriter::renameAssociation()
  11702. *
  11703. * @param string|array $from
  11704. * @param string $to
  11705. *
  11706. * @return void
  11707. */
  11708. public static function renameAssociation( $from, $to = NULL )
  11709. {
  11710. AQueryWriter::renameAssociation( $from, $to );
  11711. }
  11712.  
  11713. /**
  11714. * Little helper method for Resty Bean Can server and others.
  11715. * Takes an array of beans and exports each bean.
  11716. * Unlike exportAll this method does not recurse into own lists
  11717. * and shared lists, the beans are exported as-is, only loaded lists
  11718. * are exported.
  11719. *
  11720. * @param array $beans beans
  11721. *
  11722. * @return array
  11723. */
  11724. public static function beansToArray( $beans )
  11725. {
  11726. $list = array();
  11727. foreach( $beans as $bean ) $list[] = $bean->export();
  11728. return $list;
  11729. }
  11730.  
  11731. /**
  11732. * Sets the error mode for FUSE.
  11733. * What to do if a FUSE model method does not exist?
  11734. * You can set the following options:
  11735. *
  11736. * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
  11737. * * OODBBean::C_ERR_LOG, logs the incident using error_log
  11738. * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
  11739. * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
  11740. * * OODBBean::C_ERR_EXCEPTION, throws an exception
  11741. * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
  11742. * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
  11743. *
  11744. * <code>
  11745. * Custom handler method signature: handler( array (
  11746. * 'message' => string
  11747. * 'bean' => OODBBean
  11748. * 'method' => string
  11749. * ) )
  11750. * </code>
  11751. *
  11752. * This method returns the old mode and handler as an array.
  11753. *
  11754. * @param integer $mode mode, determines how to handle errors
  11755. * @param callable|NULL $func custom handler (if applicable)
  11756. *
  11757. * @return array
  11758. */
  11759. public static function setErrorHandlingFUSE( $mode, $func = NULL )
  11760. {
  11761. return OODBBean::setErrorHandlingFUSE( $mode, $func );
  11762. }
  11763.  
  11764. /**
  11765. * Simple but effective debug function.
  11766. * Given a one or more beans this method will
  11767. * return an array containing first part of the string
  11768. * representation of each item in the array.
  11769. *
  11770. * @param OODBBean|array $data either a bean or an array of beans
  11771. *
  11772. * @return array
  11773. */
  11774. public static function dump( $data )
  11775. {
  11776. return Dump::dump( $data );
  11777. }
  11778.  
  11779. /**
  11780. * Binds an SQL function to a column.
  11781. * This method can be used to setup a decode/encode scheme or
  11782. * perform UUID insertion. This method is especially useful for handling
  11783. * MySQL spatial columns, because they need to be processed first using
  11784. * the asText/GeomFromText functions.
  11785. *
  11786. * Example:
  11787. *
  11788. * <code>
  11789. * R::bindFunc( 'read', 'location.point', 'asText' );
  11790. * R::bindFunc( 'write', 'location.point', 'GeomFromText' );
  11791. * </code>
  11792. *
  11793. * Passing NULL as the function will reset (clear) the function
  11794. * for this column/mode.
  11795. *
  11796. * @param string $mode mode for function: i.e. read or write
  11797. * @param string $field field (table.column) to bind function to
  11798. * @param string $function SQL function to bind to specified column
  11799. *
  11800. * @return void
  11801. */
  11802. public static function bindFunc( $mode, $field, $function )
  11803. {
  11804. self::$redbean->bindFunc( $mode, $field, $function );
  11805. }
  11806.  
  11807. /**
  11808. * Sets global aliases.
  11809. * Registers a batch of aliases in one go. This works the same as
  11810. * fetchAs and setAutoResolve but explicitly. For instance if you register
  11811. * the alias 'cover' for 'page' a property containing a reference to a
  11812. * page bean called 'cover' will correctly return the page bean and not
  11813. * a (non-existant) cover bean.
  11814. *
  11815. * <code>
  11816. * R::aliases( array( 'cover' => 'page' ) );
  11817. * $book = R::dispense( 'book' );
  11818. * $page = R::dispense( 'page' );
  11819. * $book->cover = $page;
  11820. * R::store( $book );
  11821. * $book = $book->fresh();
  11822. * $cover = $book->cover;
  11823. * echo $cover->getMeta( 'type' ); //page
  11824. * </code>
  11825. *
  11826. * The format of the aliases registration array is:
  11827. *
  11828. * {alias} => {actual type}
  11829. *
  11830. * In the example above we use:
  11831. *
  11832. * cover => page
  11833. *
  11834. * From that point on, every bean reference to a cover
  11835. * will return a 'page' bean. Note that with autoResolve this
  11836. * feature along with fetchAs() is no longer very important, although
  11837. * relying on explicit aliases can be a bit faster.
  11838. *
  11839. * @param array $list list of global aliases to use
  11840. *
  11841. * @return void
  11842. */
  11843. public static function aliases( $list )
  11844. {
  11845. OODBBean::aliases( $list );
  11846. }
  11847.  
  11848. /**
  11849. * Tries to find a bean matching a certain type and
  11850. * criteria set. If no beans are found a new bean
  11851. * will be created, the criteria will be imported into this
  11852. * bean and the bean will be stored and returned.
  11853. * If multiple beans match the criteria only the first one
  11854. * will be returned.
  11855. *
  11856. * @param string $type type of bean to search for
  11857. * @param array $like criteria set describing the bean to search for
  11858. *
  11859. * @return OODBBean
  11860. */
  11861. public static function findOrCreate( $type, $like = array() )
  11862. {
  11863. return self::$finder->findOrCreate( $type, $like );
  11864. }
  11865.  
  11866. /**
  11867. * Tries to find beans matching the specified type and
  11868. * criteria set.
  11869. *
  11870. * If the optional additional SQL snippet is a condition, it will
  11871. * be glued to the rest of the query using the AND operator.
  11872. *
  11873. * @param string $type type of bean to search for
  11874. * @param array $like optional criteria set describing the bean to search for
  11875. * @param string $sql optional additional SQL for sorting
  11876. *
  11877. * @return array
  11878. */
  11879. public static function findLike( $type, $like = array(), $sql = '' )
  11880. {
  11881. return self::$finder->findLike( $type, $like, $sql );
  11882. }
  11883.  
  11884. /**
  11885. * Starts logging queries.
  11886. * Use this method to start logging SQL queries being
  11887. * executed by the adapter.
  11888. *
  11889. * @note you cannot use R::debug and R::startLogging
  11890. * at the same time because R::debug is essentially a
  11891. * special kind of logging.
  11892. *
  11893. * @return void
  11894. */
  11895. public static function startLogging()
  11896. {
  11897. self::debug( TRUE, RDefault::C_LOGGER_ARRAY );
  11898. }
  11899.  
  11900. /**
  11901. * Stops logging, comfortable method to stop logging of queries.
  11902. *
  11903. * @return void
  11904. */
  11905. public static function stopLogging()
  11906. {
  11907. self::debug( FALSE );
  11908. }
  11909.  
  11910. /**
  11911. * Returns the log entries written after the startLogging.
  11912. *
  11913. * @return array
  11914. */
  11915. public static function getLogs()
  11916. {
  11917. return self::getLogger()->getLogs();
  11918. }
  11919.  
  11920. /**
  11921. * Resets the Query counter.
  11922. *
  11923. * @return integer
  11924. */
  11925. public static function resetQueryCount()
  11926. {
  11927. self::$adapter->getDatabase()->resetCounter();
  11928. }
  11929.  
  11930. /**
  11931. * Returns the number of SQL queries processed.
  11932. *
  11933. * @return integer
  11934. */
  11935. public static function getQueryCount()
  11936. {
  11937. return self::$adapter->getDatabase()->getQueryCount();
  11938. }
  11939.  
  11940. /**
  11941. * Returns the current logger instance being used by the
  11942. * database object.
  11943. *
  11944. * @return Logger
  11945. */
  11946. public static function getLogger()
  11947. {
  11948. return self::$adapter->getDatabase()->getLogger();
  11949. }
  11950.  
  11951. /**
  11952. * Alias for setAutoResolve() method on OODBBean.
  11953. * Enables or disables auto-resolving fetch types.
  11954. * Auto-resolving aliased parent beans is convenient but can
  11955. * be slower and can create infinite recursion if you
  11956. * used aliases to break cyclic relations in your domain.
  11957. *
  11958. * @param boolean $automatic TRUE to enable automatic resolving aliased parents
  11959. *
  11960. * @return void
  11961. */
  11962. public static function setAutoResolve( $automatic = TRUE )
  11963. {
  11964. OODBBean::setAutoResolve( (boolean) $automatic );
  11965. }
  11966.  
  11967. /**
  11968. * Dynamically extends the facade with a plugin.
  11969. * Using this method you can register your plugin with the facade and then
  11970. * use the plugin by invoking the name specified plugin name as a method on
  11971. * the facade.
  11972. *
  11973. * Usage:
  11974. *
  11975. * <code>
  11976. * R::ext( 'makeTea', function() { ... } );
  11977. * </code>
  11978. *
  11979. * Now you can use your makeTea plugin like this:
  11980. *
  11981. * <code>
  11982. * R::makeTea();
  11983. * </code>
  11984. *
  11985. * @param string $pluginName name of the method to call the plugin
  11986. * @param callable $callable a PHP callable
  11987. *
  11988. * @return void
  11989. */
  11990. public static function ext( $pluginName, $callable )
  11991. {
  11992. if ( !ctype_alnum( $pluginName ) ) {
  11993. throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
  11994. }
  11995. self::$plugins[$pluginName] = $callable;
  11996. }
  11997.  
  11998. /**
  11999. * Call static for use with dynamic plugins. This magic method will
  12000. * intercept static calls and route them to the specified plugin.
  12001. *
  12002. * @param string $pluginName name of the plugin
  12003. * @param array $params list of arguments to pass to plugin method
  12004. *
  12005. * @return mixed
  12006. */
  12007. public static function __callStatic( $pluginName, $params )
  12008. {
  12009. if ( !ctype_alnum( $pluginName) ) {
  12010. throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
  12011. }
  12012. if ( !isset( self::$plugins[$pluginName] ) ) {
  12013. throw new RedException( 'Plugin \''.$pluginName.'\' does not exist, add this plugin using: R::ext(\''.$pluginName.'\')' );
  12014. }
  12015. return call_user_func_array( self::$plugins[$pluginName], $params );
  12016. }
  12017. }
  12018.  
  12019. }
  12020.  
  12021. namespace RedBeanPHP {
  12022.  
  12023. use RedBeanPHP\ToolBox as ToolBox;
  12024. use RedBeanPHP\AssociationManager as AssociationManager;
  12025. use RedBeanPHP\OODB as OODB;
  12026. use RedBeanPHP\OODBBean as OODBBean;
  12027. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  12028.  
  12029. /**
  12030. * Duplication Manager.
  12031. * Creates deep copies of beans.
  12032. *
  12033. * @file RedBeanPHP/DuplicationManager.php
  12034. * @author Gabor de Mooij and the RedBeanPHP Community
  12035. * @license BSD/GPLv2
  12036. *
  12037. * @copyright
  12038. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12039. * This source file is subject to the BSD/GPLv2 License that is bundled
  12040. * with this source code in the file license.txt.
  12041. */
  12042. class DuplicationManager
  12043. {
  12044. /**
  12045. * @var ToolBox
  12046. */
  12047. protected $toolbox;
  12048.  
  12049. /**
  12050. * @var AssociationManager
  12051. */
  12052. protected $associationManager;
  12053.  
  12054. /**
  12055. * @var OODB
  12056. */
  12057. protected $redbean;
  12058.  
  12059. /**
  12060. * @var array
  12061. */
  12062. protected $tables = array();
  12063.  
  12064. /**
  12065. * @var array
  12066. */
  12067. protected $columns = array();
  12068.  
  12069. /**
  12070. * @var array
  12071. */
  12072. protected $filters = array();
  12073.  
  12074. /**
  12075. * @var array
  12076. */
  12077. protected $cacheTables = FALSE;
  12078.  
  12079. /**
  12080. * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
  12081. *
  12082. * @param OODBBean $copy target bean to copy lists to
  12083. * @param string $shared name of the shared list
  12084. * @param array $beans array with shared beans to copy
  12085. *
  12086. * @return void
  12087. */
  12088. private function copySharedBeans( OODBBean $copy, $shared, $beans )
  12089. {
  12090. $copy->$shared = array();
  12091.  
  12092. foreach ( $beans as $subBean ) {
  12093. array_push( $copy->$shared, $subBean );
  12094. }
  12095. }
  12096.  
  12097. /**
  12098. * Copies the own beans in a bean, i.e. all the ownBean-lists.
  12099. * Each bean in the own-list belongs exclusively to its owner so
  12100. * we need to invoke the duplicate method again to duplicate each bean here.
  12101. *
  12102. * @param OODBBean $copy target bean to copy lists to
  12103. * @param string $owned name of the own list
  12104. * @param array $beans array with shared beans to copy
  12105. * @param array $trail array with former beans to detect recursion
  12106. * @param boolean $preserveIDs TRUE means preserve IDs, for export only
  12107. *
  12108. * @return void
  12109. */
  12110. private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
  12111. {
  12112. $copy->$owned = array();
  12113. foreach ( $beans as $subBean ) {
  12114. array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
  12115. }
  12116. }
  12117.  
  12118. /**
  12119. * Creates a copy of bean $bean and copies all primitive properties (not lists)
  12120. * and the parents beans to the newly created bean. Also sets the ID of the bean
  12121. * to 0.
  12122. *
  12123. * @param OODBBean $bean bean to copy
  12124. *
  12125. * @return OODBBean
  12126. */
  12127. private function createCopy( OODBBean $bean )
  12128. {
  12129. $type = $bean->getMeta( 'type' );
  12130.  
  12131. $copy = $this->redbean->dispense( $type );
  12132. $copy->setMeta( 'sys.dup-from-id', $bean->id );
  12133. $copy->setMeta( 'sys.old-id', $bean->id );
  12134. $copy->importFrom( $bean );
  12135. $copy->id = 0;
  12136.  
  12137. return $copy;
  12138. }
  12139.  
  12140. /**
  12141. * Generates a key from the bean type and its ID and determines if the bean
  12142. * occurs in the trail, if not the bean will be added to the trail.
  12143. * Returns TRUE if the bean occurs in the trail and FALSE otherwise.
  12144. *
  12145. * @param array $trail list of former beans
  12146. * @param OODBBean $bean currently selected bean
  12147. *
  12148. * @return boolean
  12149. */
  12150. private function inTrailOrAdd( &$trail, OODBBean $bean )
  12151. {
  12152. $type = $bean->getMeta( 'type' );
  12153. $key = $type . $bean->getID();
  12154.  
  12155. if ( isset( $trail[$key] ) ) {
  12156. return TRUE;
  12157. }
  12158.  
  12159. $trail[$key] = $bean;
  12160.  
  12161. return FALSE;
  12162. }
  12163.  
  12164. /**
  12165. * Given the type name of a bean this method returns the canonical names
  12166. * of the own-list and the shared-list properties respectively.
  12167. * Returns a list with two elements: name of the own-list, and name
  12168. * of the shared list.
  12169. *
  12170. * @param string $typeName bean type name
  12171. *
  12172. * @return array
  12173. */
  12174. private function getListNames( $typeName )
  12175. {
  12176. $owned = 'own' . ucfirst( $typeName );
  12177. $shared = 'shared' . ucfirst( $typeName );
  12178.  
  12179. return array( $owned, $shared );
  12180. }
  12181.  
  12182. /**
  12183. * Determines whether the bean has an own list based on
  12184. * schema inspection from realtime schema or cache.
  12185. *
  12186. * @param string $type bean type to get list for
  12187. * @param string $target type of list you want to detect
  12188. *
  12189. * @return boolean
  12190. */
  12191. protected function hasOwnList( $type, $target )
  12192. {
  12193. return isset( $this->columns[$target][$type . '_id'] );
  12194. }
  12195.  
  12196. /**
  12197. * Determines whether the bea has a shared list based on
  12198. * schema inspection from realtime schema or cache.
  12199. *
  12200. * @param string $type bean type to get list for
  12201. * @param string $target type of list you are looking for
  12202. *
  12203. * @return boolean
  12204. */
  12205. protected function hasSharedList( $type, $target )
  12206. {
  12207. return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
  12208. }
  12209.  
  12210. /**
  12211. * @see DuplicationManager::dup
  12212. *
  12213. * @param OODBBean $bean bean to be copied
  12214. * @param array $trail trail to prevent infinite loops
  12215. * @param boolean $preserveIDs preserve IDs
  12216. *
  12217. * @return OODBBean
  12218. */
  12219. protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
  12220. {
  12221. if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
  12222.  
  12223. $type = $bean->getMeta( 'type' );
  12224.  
  12225. $copy = $this->createCopy( $bean );
  12226. foreach ( $this->tables as $table ) {
  12227.  
  12228. if ( !empty( $this->filters ) ) {
  12229. if ( !in_array( $table, $this->filters ) ) continue;
  12230. }
  12231.  
  12232. list( $owned, $shared ) = $this->getListNames( $table );
  12233.  
  12234. if ( $this->hasSharedList( $type, $table ) ) {
  12235. if ( $beans = $bean->$shared ) {
  12236. $this->copySharedBeans( $copy, $shared, $beans );
  12237. }
  12238. } elseif ( $this->hasOwnList( $type, $table ) ) {
  12239. if ( $beans = $bean->$owned ) {
  12240. $this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
  12241. }
  12242.  
  12243. $copy->setMeta( 'sys.shadow.' . $owned, NULL );
  12244. }
  12245.  
  12246. $copy->setMeta( 'sys.shadow.' . $shared, NULL );
  12247. }
  12248.  
  12249. $copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
  12250.  
  12251. return $copy;
  12252. }
  12253.  
  12254. /**
  12255. * Constructor,
  12256. * creates a new instance of DupManager.
  12257. *
  12258. * @param ToolBox $toolbox
  12259. */
  12260. public function __construct( ToolBox $toolbox )
  12261. {
  12262. $this->toolbox = $toolbox;
  12263. $this->redbean = $toolbox->getRedBean();
  12264. $this->associationManager = $this->redbean->getAssociationManager();
  12265. }
  12266.  
  12267. /**
  12268. * Recursively turns the keys of an array into
  12269. * camelCase.
  12270. *
  12271. * @param array $array array to camelize
  12272. * @param boolean $dolphinMode whether you want the exception for IDs.
  12273. *
  12274. * @return array
  12275. */
  12276. public function camelfy( $array, $dolphinMode = false ) {
  12277. $newArray = array();
  12278. foreach( $array as $key => $element ) {
  12279. $newKey = preg_replace_callback( '/_(\w)/', function( &$matches ){
  12280. return strtoupper( $matches[1] );
  12281. }, $key);
  12282.  
  12283. if ( $dolphinMode ) {
  12284. $newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
  12285. }
  12286.  
  12287. $newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
  12288. }
  12289. return $newArray;
  12290. }
  12291.  
  12292. /**
  12293. * For better performance you can pass the tables in an array to this method.
  12294. * If the tables are available the duplication manager will not query them so
  12295. * this might be beneficial for performance.
  12296. *
  12297. * This method allows two array formats:
  12298. *
  12299. * <code>
  12300. * array( TABLE1, TABLE2 ... )
  12301. * </code>
  12302. *
  12303. * or
  12304. *
  12305. * <code>
  12306. * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
  12307. * </code>
  12308. *
  12309. * @param array $tables a table cache array
  12310. *
  12311. * @return void
  12312. */
  12313. public function setTables( $tables )
  12314. {
  12315. foreach ( $tables as $key => $value ) {
  12316. if ( is_numeric( $key ) ) {
  12317. $this->tables[] = $value;
  12318. } else {
  12319. $this->tables[] = $key;
  12320. $this->columns[$key] = $value;
  12321. }
  12322. }
  12323.  
  12324. $this->cacheTables = TRUE;
  12325. }
  12326.  
  12327. /**
  12328. * Returns a schema array for cache.
  12329. * You can use the return value of this method as a cache,
  12330. * store it in RAM or on disk and pass it to setTables later.
  12331. *
  12332. * @return array
  12333. */
  12334. public function getSchema()
  12335. {
  12336. return $this->columns;
  12337. }
  12338.  
  12339. /**
  12340. * Indicates whether you want the duplication manager to cache the database schema.
  12341. * If this flag is set to TRUE the duplication manager will query the database schema
  12342. * only once. Otherwise the duplicationmanager will, by default, query the schema
  12343. * every time a duplication action is performed (dup()).
  12344. *
  12345. * @param boolean $yesNo TRUE to use caching, FALSE otherwise
  12346. */
  12347. public function setCacheTables( $yesNo )
  12348. {
  12349. $this->cacheTables = $yesNo;
  12350. }
  12351.  
  12352. /**
  12353. * A filter array is an array with table names.
  12354. * By setting a table filter you can make the duplication manager only take into account
  12355. * certain bean types. Other bean types will be ignored when exporting or making a
  12356. * deep copy. If no filters are set all types will be taking into account, this is
  12357. * the default behavior.
  12358. *
  12359. * @param array $filters list of tables to be filtered
  12360. *
  12361. * @return void
  12362. */
  12363. public function setFilters( $filters )
  12364. {
  12365. if ( !is_array( $filters ) ) {
  12366. $filters = array( $filters );
  12367. }
  12368.  
  12369. $this->filters = $filters;
  12370. }
  12371.  
  12372. /**
  12373. * Makes a copy of a bean. This method makes a deep copy
  12374. * of the bean.The copy will have the following features.
  12375. * - All beans in own-lists will be duplicated as well
  12376. * - All references to shared beans will be copied but not the shared beans themselves
  12377. * - All references to parent objects (_id fields) will be copied but not the parents themselves
  12378. * In most cases this is the desired scenario for copying beans.
  12379. * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
  12380. * (i.e. one that already has been processed) the ID of the bean will be returned.
  12381. * This should not happen though.
  12382. *
  12383. * Note:
  12384. * This function does a reflectional database query so it may be slow.
  12385. *
  12386. * Note:
  12387. * this function actually passes the arguments to a protected function called
  12388. * duplicate() that does all the work. This method takes care of creating a clone
  12389. * of the bean to avoid the bean getting tainted (triggering saving when storing it).
  12390. *
  12391. * @param OODBBean $bean bean to be copied
  12392. * @param array $trail for internal usage, pass array()
  12393. * @param boolean $preserveIDs for internal usage
  12394. *
  12395. * @return OODBBean
  12396. */
  12397. public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
  12398. {
  12399. if ( !count( $this->tables ) ) {
  12400. $this->tables = $this->toolbox->getWriter()->getTables();
  12401. }
  12402.  
  12403. if ( !count( $this->columns ) ) {
  12404. foreach ( $this->tables as $table ) {
  12405. $this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
  12406. }
  12407. }
  12408.  
  12409. $rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
  12410.  
  12411. if ( !$this->cacheTables ) {
  12412. $this->tables = array();
  12413. $this->columns = array();
  12414. }
  12415.  
  12416. return $rs;
  12417. }
  12418.  
  12419. /**
  12420. * Exports a collection of beans recursively.
  12421. * This method will export an array of beans in the first argument to a
  12422. * set of arrays. This can be used to send JSON or XML representations
  12423. * of bean hierarchies to the client.
  12424. *
  12425. * For every bean in the array this method will export:
  12426. *
  12427. * - contents of the bean
  12428. * - all own bean lists (recursively)
  12429. * - all shared beans (but not THEIR own lists)
  12430. *
  12431. * If the second parameter is set to TRUE the parents of the beans in the
  12432. * array will be exported as well (but not THEIR parents).
  12433. *
  12434. * The third parameter can be used to provide a white-list array
  12435. * for filtering. This is an array of strings representing type names,
  12436. * only the type names in the filter list will be exported.
  12437. *
  12438. * The fourth parameter can be used to change the keys of the resulting
  12439. * export arrays. The default mode is 'snake case' but this leaves the
  12440. * keys as-is, because 'snake' is the default case style used by
  12441. * RedBeanPHP in the database. You can set this to 'camel' for
  12442. * camel cased keys or 'dolphin' (same as camelcase but id will be
  12443. * converted to ID instead of Id).
  12444. *
  12445. * @param array|OODBBean $beans beans to be exported
  12446. * @param boolean $parents also export parents
  12447. * @param array $filters only these types (whitelist)
  12448. * @param string $caseStyle case style identifier
  12449. *
  12450. * @return array
  12451. */
  12452. public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake')
  12453. {
  12454. $array = array();
  12455.  
  12456. if ( !is_array( $beans ) ) {
  12457. $beans = array( $beans );
  12458. }
  12459.  
  12460. foreach ( $beans as $bean ) {
  12461. $this->setFilters( $filters );
  12462.  
  12463. $duplicate = $this->dup( $bean, array(), TRUE );
  12464.  
  12465. $array[] = $duplicate->export( FALSE, $parents, FALSE, $filters );
  12466. }
  12467.  
  12468. if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
  12469. if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, true );
  12470.  
  12471. return $array;
  12472. }
  12473. }
  12474. }
  12475.  
  12476. namespace RedBeanPHP\Util {
  12477.  
  12478. use RedBeanPHP\OODB as OODB;
  12479. use RedBeanPHP\OODBBean as OODBBean;
  12480. use RedBeanPHP\RedException as RedException;
  12481.  
  12482. /**
  12483. * Array Tool Helper
  12484. *
  12485. * This code was originally part of the facade, however it has
  12486. * been decided to remove unique features to service classes like
  12487. * this to make them available to developers not using the facade class.
  12488. *
  12489. * This is a helper or service class containing frequently used
  12490. * array functions for dealing with SQL queries.
  12491. *
  12492. * @file RedBeanPHP/Util/ArrayTool.php
  12493. * @author Gabor de Mooij and the RedBeanPHP Community
  12494. * @license BSD/GPLv2
  12495. *
  12496. * @copyright
  12497. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12498. * This source file is subject to the BSD/GPLv2 License that is bundled
  12499. * with this source code in the file license.txt.
  12500. */
  12501. class ArrayTool
  12502. {
  12503. /**
  12504. * Generates question mark slots for an array of values.
  12505. *
  12506. * @param array $array array to generate question mark slots for
  12507. *
  12508. * @return string
  12509. */
  12510. public static function genSlots( $array, $template = NULL )
  12511. {
  12512. $str = count( $array ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : '';
  12513. return ( is_null( $template ) || $str === '' ) ? $str : sprintf( $template, $str );
  12514. }
  12515.  
  12516. /**
  12517. * Flattens a multi dimensional bindings array for use with genSlots().
  12518. *
  12519. * @param array $array array to flatten
  12520. * @param array $result result array parameter (for recursion)
  12521. *
  12522. * @return array
  12523. */
  12524. public static function flat( $array, $result = array() )
  12525. {
  12526. foreach( $array as $value ) {
  12527. if ( is_array( $value ) ) $result = self::flat( $value, $result );
  12528. else $result[] = $value;
  12529. }
  12530. return $result;
  12531. }
  12532. }
  12533. }
  12534.  
  12535. namespace RedBeanPHP\Util {
  12536.  
  12537. use RedBeanPHP\OODB as OODB;
  12538. use RedBeanPHP\RedException as RedException;
  12539.  
  12540. /**
  12541. * Dispense Helper
  12542. *
  12543. * A helper class containing a dispense utility.
  12544. *
  12545. * @file RedBeanPHP/Util/DispenseHelper.php
  12546. * @author Gabor de Mooij and the RedBeanPHP Community
  12547. * @license BSD/GPLv2
  12548. *
  12549. * @copyright
  12550. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12551. * This source file is subject to the BSD/GPLv2 License that is bundled
  12552. * with this source code in the file license.txt.
  12553. */
  12554. class DispenseHelper
  12555. {
  12556. /**
  12557. * Dispenses a new RedBean OODB Bean for use with
  12558. * the rest of the methods.
  12559. *
  12560. * @param OODB $oodb OODB
  12561. * @param string|array $typeOrBeanArray type or bean array to import
  12562. * @param integer $number number of beans to dispense
  12563. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  12564. *
  12565. * @return array|OODBBean
  12566. */
  12567. public static function dispense( OODB $oodb, $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE ) {
  12568.  
  12569. if ( is_array($typeOrBeanArray) ) {
  12570.  
  12571. if ( !isset( $typeOrBeanArray['_type'] ) ) {
  12572. $list = array();
  12573. foreach( $typeOrBeanArray as $beanArray ) {
  12574. if (
  12575. !( is_array( $beanArray )
  12576. && isset( $beanArray['_type'] ) ) ) {
  12577. throw new RedException( 'Invalid Array Bean' );
  12578. }
  12579. }
  12580. foreach( $typeOrBeanArray as $beanArray ) $list[] = self::dispense( $oodb, $beanArray );
  12581. return $list;
  12582. }
  12583.  
  12584. $import = $typeOrBeanArray;
  12585. $type = $import['_type'];
  12586. unset( $import['_type'] );
  12587. } else {
  12588. $type = $typeOrBeanArray;
  12589. }
  12590.  
  12591. if ( !preg_match( '/^[a-z0-9]+$/', $type ) ) {
  12592. throw new RedException( 'Invalid type: ' . $type );
  12593. }
  12594.  
  12595. $beanOrBeans = $oodb->dispense( $type, $num, $alwaysReturnArray );
  12596.  
  12597. if ( isset( $import ) ) {
  12598. $beanOrBeans->import( $import );
  12599. }
  12600.  
  12601. return $beanOrBeans;
  12602. }
  12603.  
  12604.  
  12605. /**
  12606. * Takes a comma separated list of bean types
  12607. * and dispenses these beans. For each type in the list
  12608. * you can specify the number of beans to be dispensed.
  12609. *
  12610. * Usage:
  12611. *
  12612. * <code>
  12613. * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
  12614. * </code>
  12615. *
  12616. * This will dispense a book, a page and a text. This way you can
  12617. * quickly dispense beans of various types in just one line of code.
  12618. *
  12619. * Usage:
  12620. *
  12621. * <code>
  12622. * list($book, $pages) = R::dispenseAll('book,page*100');
  12623. * </code>
  12624. *
  12625. * This returns an array with a book bean and then another array
  12626. * containing 100 page beans.
  12627. *
  12628. * @param OODB $oodb OODB
  12629. * @param string $order a description of the desired dispense order using the syntax above
  12630. * @param boolean $onlyArrays return only arrays even if amount < 2
  12631. *
  12632. * @return array
  12633. */
  12634. public static function dispenseAll( OODB $oodb, $order, $onlyArrays = FALSE )
  12635. {
  12636. $list = array();
  12637.  
  12638. foreach( explode( ',', $order ) as $order ) {
  12639. if ( strpos( $order, '*' ) !== FALSE ) {
  12640. list( $type, $amount ) = explode( '*', $order );
  12641. } else {
  12642. $type = $order;
  12643. $amount = 1;
  12644. }
  12645.  
  12646. $list[] = self::dispense( $oodb, $type, $amount, $onlyArrays );
  12647. }
  12648.  
  12649. return $list;
  12650. }
  12651. }
  12652. }
  12653.  
  12654. namespace RedBeanPHP\Util {
  12655.  
  12656. use RedBeanPHP\OODB as OODB;
  12657. use RedBeanPHP\OODBBean as OODBBean;
  12658.  
  12659. /**
  12660. * Dump helper
  12661. *
  12662. * This code was originally part of the facade, however it has
  12663. * been decided to remove unique features to service classes like
  12664. * this to make them available to developers not using the facade class.
  12665. *
  12666. * Dumps the contents of a bean in an array for
  12667. * debugging purposes.
  12668. *
  12669. * @file RedBeanPHP/Util/Dump.php
  12670. * @author Gabor de Mooij and the RedBeanPHP Community
  12671. * @license BSD/GPLv2
  12672. *
  12673. * @copyright
  12674. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12675. * This source file is subject to the BSD/GPLv2 License that is bundled
  12676. * with this source code in the file license.txt.
  12677. */
  12678. class Dump
  12679. {
  12680. /**
  12681. * Simple but effective debug function.
  12682. * Given a one or more beans this method will
  12683. * return an array containing first part of the string
  12684. * representation of each item in the array.
  12685. *
  12686. * @param OODBBean|array $data either a bean or an array of beans
  12687. *
  12688. * @return array
  12689. */
  12690. public static function dump( $data )
  12691. {
  12692. $array = array();
  12693.  
  12694. if ( $data instanceof OODBBean ) {
  12695. $str = strval( $data );
  12696. if (strlen($str) > 35) {
  12697. $beanStr = substr( $str, 0, 35 ).'... ';
  12698. } else {
  12699. $beanStr = $str;
  12700. }
  12701. return $beanStr;
  12702. }
  12703.  
  12704. if ( is_array( $data ) ) {
  12705. foreach( $data as $key => $item ) {
  12706. $array[$key] = self::dump( $item );
  12707. }
  12708. }
  12709.  
  12710. return $array;
  12711. }
  12712. }
  12713. }
  12714.  
  12715. namespace RedBeanPHP\Util {
  12716.  
  12717. use RedBeanPHP\OODB as OODB;
  12718.  
  12719. /**
  12720. * Multi Bean Loader Helper
  12721. *
  12722. * This code was originally part of the facade, however it has
  12723. * been decided to remove unique features to service classes like
  12724. * this to make them available to developers not using the facade class.
  12725. *
  12726. * This helper class offers limited support for one-to-one
  12727. * relations by providing a service to load a set of beans
  12728. * with differnt types and a common ID.
  12729. *
  12730. * @file RedBeanPHP/Util/MultiLoader.php
  12731. * @author Gabor de Mooij and the RedBeanPHP Community
  12732. * @license BSD/GPLv2
  12733. *
  12734. * @copyright
  12735. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12736. * This source file is subject to the BSD/GPLv2 License that is bundled
  12737. * with this source code in the file license.txt.
  12738. */
  12739. class MultiLoader
  12740. {
  12741. /**
  12742. * Loads multiple types of beans with the same ID.
  12743. * This might look like a strange method, however it can be useful
  12744. * for loading a one-to-one relation.
  12745. *
  12746. * @param OODB $oodb OODB object
  12747. * @param string|array $types the set of types to load at once
  12748. * @param mixed $id the common ID
  12749. *
  12750. * @return OODBBean
  12751. */
  12752. public static function load( OODB $oodb, $types, $id )
  12753. {
  12754. if ( is_string( $types ) ) {
  12755. $types = explode( ',', $types );
  12756. }
  12757.  
  12758. if ( !is_array( $types ) ) {
  12759. return array();
  12760. }
  12761.  
  12762. foreach ( $types as $k => $typeItem ) {
  12763. $types[$k] = $oodb->load( $typeItem, $id );
  12764. }
  12765.  
  12766. return $types;
  12767. }
  12768. }
  12769. }
  12770.  
  12771. namespace RedBeanPHP\Util {
  12772.  
  12773. use RedBeanPHP\OODB as OODB;
  12774. use RedBeanPHP\OODBBean as OODBBean;
  12775. use RedBeanPHP\RedException as RedException;
  12776. use RedBeanPHP\Adapter as Adapter;
  12777.  
  12778. /**
  12779. * Transaction Helper
  12780. *
  12781. * This code was originally part of the facade, however it has
  12782. * been decided to remove unique features to service classes like
  12783. * this to make them available to developers not using the facade class.
  12784. *
  12785. * Database transaction helper. This is a convenience class
  12786. * to perform a callback in a database transaction. This class
  12787. * contains a method to wrap your callback in a transaction.
  12788. *
  12789. * @file RedBeanPHP/Util/Transaction.php
  12790. * @author Gabor de Mooij and the RedBeanPHP Community
  12791. * @license BSD/GPLv2
  12792. *
  12793. * @copyright
  12794. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12795. * This source file is subject to the BSD/GPLv2 License that is bundled
  12796. * with this source code in the file license.txt.
  12797. */
  12798. class Transaction
  12799. {
  12800. /**
  12801. * Wraps a transaction around a closure or string callback.
  12802. * If an Exception is thrown inside, the operation is automatically rolled back.
  12803. * If no Exception happens, it commits automatically.
  12804. * It also supports (simulated) nested transactions (that is useful when
  12805. * you have many methods that needs transactions but are unaware of
  12806. * each other).
  12807. *
  12808. * Example:
  12809. *
  12810. * <code>
  12811. * $from = 1;
  12812. * $to = 2;
  12813. * $amount = 300;
  12814. *
  12815. * R::transaction(function() use($from, $to, $amount)
  12816. * {
  12817. * $accountFrom = R::load('account', $from);
  12818. * $accountTo = R::load('account', $to);
  12819. * $accountFrom->money -= $amount;
  12820. * $accountTo->money += $amount;
  12821. * R::store($accountFrom);
  12822. * R::store($accountTo);
  12823. * });
  12824. * </code>
  12825. *
  12826. * @param Adapter $adapter Database Adapter providing transaction mechanisms.
  12827. * @param callable $callback Closure (or other callable) with the transaction logic
  12828. *
  12829. * @return mixed
  12830. */
  12831. public static function transaction( Adapter $adapter, $callback )
  12832. {
  12833. if ( !is_callable( $callback ) ) {
  12834. throw new RedException( 'R::transaction needs a valid callback.' );
  12835. }
  12836.  
  12837. static $depth = 0;
  12838. $result = null;
  12839. try {
  12840. if ( $depth == 0 ) {
  12841. $adapter->startTransaction();
  12842. }
  12843. $depth++;
  12844. $result = call_user_func( $callback ); //maintain 5.2 compatibility
  12845. $depth--;
  12846. if ( $depth == 0 ) {
  12847. $adapter->commit();
  12848. }
  12849. } catch ( \Exception $exception ) {
  12850. $depth--;
  12851. if ( $depth == 0 ) {
  12852. $adapter->rollback();
  12853. }
  12854. throw $exception;
  12855. }
  12856. return $result;
  12857. }
  12858. }
  12859. }
  12860.  
  12861. namespace RedBeanPHP {
  12862.  
  12863. /**
  12864. * RedBean Plugin.
  12865. * Marker interface for plugins.
  12866. *
  12867. * @file RedBean/Plugin.php
  12868. * @author Gabor de Mooij and the RedBeanPHP Community
  12869. * @license BSD/GPLv2
  12870. *
  12871. * @copyright
  12872. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  12873. * This source file is subject to the BSD/GPLv2 License that is bundled
  12874. * with this source code in the file license.txt.
  12875. */
  12876. interface Plugin
  12877. {
  12878. }
  12879.  
  12880. ;
  12881. }
  12882. namespace {
  12883.  
  12884. //make some classes available for backward compatibility
  12885. class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
  12886.  
  12887. if (!class_exists('R')) {
  12888. class R extends \RedBeanPHP\Facade{};
  12889. }
  12890.  
  12891.  
  12892.  
  12893. /**
  12894. * Support functions for RedBeanPHP.
  12895. * Additional convenience shortcut functions for RedBeanPHP.
  12896. *
  12897. * @file RedBeanPHP/Functions.php
  12898. * @author Gabor de Mooij and the RedBeanPHP community
  12899. * @license BSD/GPLv2
  12900. *
  12901. * @copyright
  12902. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  12903. * This source file is subject to the BSD/GPLv2 License that is bundled
  12904. * with this source code in the file license.txt.
  12905. */
  12906.  
  12907. /**
  12908. * Convenience function for ENUM short syntax in queries.
  12909. *
  12910. * Usage:
  12911. *
  12912. * <code>
  12913. * R::find( 'paint', ' color_id = ? ', [ EID('color:yellow') ] );
  12914. * </code>
  12915. *
  12916. * If a function called EID() already exists you'll have to write this
  12917. * wrapper yourself ;)
  12918. *
  12919. * @param string $enumName enum code as you would pass to R::enum()
  12920. *
  12921. * @return mixed
  12922. */
  12923. if (!function_exists('EID')) {
  12924.  
  12925. function EID($enumName)
  12926. {
  12927. return \RedBeanPHP\Facade::enum( $enumName )->id;
  12928. }
  12929.  
  12930. }
  12931.  
  12932. /**
  12933. * Prints the result of R::dump() to the screen using
  12934. * print_r.
  12935. *
  12936. * @param mixed $data data to dump
  12937. *
  12938. * @return void
  12939. */
  12940. if ( !function_exists( 'dump' ) ) {
  12941.  
  12942. function dmp( $list )
  12943. {
  12944. print_r( \RedBeanPHP\Facade::dump( $list ) );
  12945. }
  12946. }
  12947.  
  12948. /**
  12949. * Function alias for R::genSlots().
  12950. */
  12951. if ( !function_exists( 'genslots' ) ) {
  12952.  
  12953. function genslots( $slots, $tpl = NULL )
  12954. {
  12955. return \RedBeanPHP\Facade::genSlots( $slots, $tpl );
  12956. }
  12957. }
  12958.  
  12959. /**
  12960. * Function alias for R::flat().
  12961. */
  12962. if ( !function_exists( 'array_flatten' ) ) {
  12963.  
  12964. function array_flatten( $array )
  12965. {
  12966. return \RedBeanPHP\Facade::flat( $array );
  12967. }
  12968. }
  12969.  
  12970.  
  12971. }
Add Comment
Please, Sign In to add comment