PageRenderTime 94ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/framework/rb.php

https://github.com/jburns20/webapp2php
PHP | 9819 lines | 4159 code | 1224 blank | 4436 comment | 500 complexity | c2e4bfe6aea2c1e23fbb2c829b736c7b MD5 | raw file
  1. <?php
  2. namespace RedBeanPHP {
  3. /**
  4. * RedBean Logging interface.
  5. * Provides a uniform and convenient logging
  6. * interface throughout RedBeanPHP.
  7. *
  8. * @file RedBean/Logging.php
  9. * @desc Logging interface for RedBeanPHP ORM
  10. * @author Gabor de Mooij and the RedBeanPHP Community
  11. * @license BSD/GPLv2
  12. *
  13. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  14. * This source file is subject to the BSD/GPLv2 License that is bundled
  15. * with this source code in the file license.txt.
  16. */
  17. interface Logger
  18. {
  19. /**
  20. * A logger (for\PDO or OCI driver) needs to implement the log method.
  21. * The log method will receive logging data. Note that the number of parameters is 0, this means
  22. * all parameters are optional and the number may vary. This way the logger can be used in a very
  23. * flexible way. Sometimes the logger is used to log a simple error message and in other
  24. * situations sql and bindings are passed.
  25. * The log method should be able to accept all kinds of parameters and data by using
  26. * functions like func_num_args/func_get_args.
  27. *
  28. * @return void
  29. */
  30. public function log();
  31. }
  32. }
  33. namespace RedBeanPHP\Logger {
  34. use RedBeanPHP\Logger as Logger;
  35. use RedBeanPHP\RedException as RedException;
  36. use RedBeanPHP\RedException\Security as Security;
  37. /**
  38. * Logger. Provides a basic logging function for RedBeanPHP.
  39. *
  40. * @file RedBean/Logger.php
  41. * @desc Logger
  42. * @author Gabor de Mooij and the RedBeanPHP Community
  43. * @license BSD/GPLv2
  44. *
  45. * Provides a basic logging function for RedBeanPHP.
  46. *
  47. * copyright (c) G.J.G.T. (Gabor) de Mooij
  48. * This source file is subject to the BSD/GPLv2 License that is bundled
  49. * with this source code in the file license.txt.
  50. */
  51. class RDefault implements Logger
  52. {
  53. /**
  54. * @var integer
  55. */
  56. protected $mode = 0;
  57. /**
  58. * @var array
  59. */
  60. protected $logs = array();
  61. /**
  62. * Default logger method logging to STDOUT.
  63. * This is the default/reference implementation of a logger.
  64. * This method will write the message value to STDOUT (screen).
  65. *
  66. * @param $message (optional)
  67. *
  68. * @return void
  69. */
  70. public function log()
  71. {
  72. if ( func_num_args() < 1 ) return;
  73. foreach ( func_get_args() as $argument ) {
  74. if ( is_array( $argument ) ) {
  75. $log = print_r( $argument, TRUE );
  76. if ( $this->mode === 0 ) {
  77. echo $log;
  78. } else {
  79. $this->logs[] = $log;
  80. }
  81. } else {
  82. if ( $this->mode === 0 ) {
  83. echo $argument;
  84. } else {
  85. $this->logs[] = $argument;
  86. }
  87. }
  88. if ($this->mode === 0) echo "<br>\n";
  89. }
  90. }
  91. /**
  92. * Returns the logs array.
  93. *
  94. * @return array
  95. */
  96. public function getLogs()
  97. {
  98. return $this->logs;
  99. }
  100. /**
  101. * Empties the logs array.
  102. *
  103. * @return self
  104. */
  105. public function clear()
  106. {
  107. $this->logs = array();
  108. return $this;
  109. }
  110. /**
  111. * Selects a logging mode.
  112. * Mode 0 means echoing all statements, while mode 1
  113. * means populating the logs array.
  114. *
  115. * @param integer $mode mode
  116. *
  117. * @return self
  118. */
  119. public function setMode( $mode )
  120. {
  121. if ($mode !== 0 && $mode !== 1) {
  122. throw new RedException( 'Invalid mode selected for logger, use 1 or 0.' );
  123. }
  124. $this->mode = $mode;
  125. return $this;
  126. }
  127. /**
  128. * Searches for all log entries in internal log array
  129. * for $needle and returns those entries.
  130. *
  131. * @param string $needle needle
  132. *
  133. * @return array
  134. */
  135. public function grep( $needle )
  136. {
  137. $found = array();
  138. foreach( $this->logs as $logEntry ) {
  139. if (strpos( $logEntry, $needle ) !== false) $found[] = $logEntry;
  140. }
  141. return $found;
  142. }
  143. }
  144. }
  145. namespace RedBeanPHP {
  146. /**
  147. * Interface for database drivers
  148. *
  149. * @file RedBean/Driver.php
  150. * @desc Describes the API for database classes
  151. * @author Gabor de Mooij and the RedBeanPHP Community
  152. * @license BSD/GPLv2
  153. *
  154. * The Driver API conforms to the ADODB pseudo standard
  155. * for database drivers.
  156. *
  157. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  158. * This source file is subject to the BSD/GPLv2 License that is bundled
  159. * with this source code in the file license.txt.
  160. */
  161. interface Driver
  162. {
  163. /**
  164. * Runs a query and fetches results as a multi dimensional array.
  165. *
  166. * @param string $sql SQL to be executed
  167. * @param array $bindings list of values to bind to SQL snippet
  168. *
  169. * @return array
  170. */
  171. public function GetAll( $sql, $bindings = array() );
  172. /**
  173. * Runs a query and fetches results as a column.
  174. *
  175. * @param string $sql SQL Code to execute
  176. * @param array $bindings list of values to bind to SQL snippet
  177. *
  178. * @return array
  179. */
  180. public function GetCol( $sql, $bindings = array() );
  181. /**
  182. * Runs a query and returns results as a single cell.
  183. *
  184. * @param string $sql SQL to execute
  185. * @param array $bindings list of values to bind to SQL snippet
  186. *
  187. * @return mixed
  188. */
  189. public function GetCell( $sql, $bindings = array() );
  190. /**
  191. * Runs a query and returns results as an associative array
  192. * indexed by the first column.
  193. *
  194. * @param string $sql SQL to execute
  195. * @param array $bindings list of values to bind to SQL snippet
  196. *
  197. * @return mixed
  198. */
  199. public function GetAssocRow( $sql, $bindings = array() );
  200. /**
  201. * Runs a query and returns a flat array containing the values of
  202. * one row.
  203. *
  204. * @param string $sql SQL to execute
  205. * @param array $bindings list of values to bind to SQL snippet
  206. *
  207. * @return array
  208. */
  209. public function GetRow( $sql, $bindings = array() );
  210. /**
  211. * Executes SQL code and allows key-value binding.
  212. * This function allows you to provide an array with values to bind
  213. * to query parameters. For instance you can bind values to question
  214. * marks in the query. Each value in the array corresponds to the
  215. * question mark in the query that matches the position of the value in the
  216. * array. You can also bind values using explicit keys, for instance
  217. * array(":key"=>123) will bind the integer 123 to the key :key in the
  218. * SQL. This method has no return value.
  219. *
  220. * @param string $sql SQL Code to execute
  221. * @param array $bindings list of values to bind to SQL snippet
  222. *
  223. * @return array Affected Rows
  224. */
  225. public function Execute( $sql, $bindings = array() );
  226. /**
  227. * Returns the latest insert ID if driver does support this
  228. * feature.
  229. *
  230. * @return integer
  231. */
  232. public function GetInsertID();
  233. /**
  234. * Returns the number of rows affected by the most recent query
  235. * if the currently selected driver driver supports this feature.
  236. *
  237. * @return integer
  238. */
  239. public function Affected_Rows();
  240. /**
  241. * Toggles debug mode. In debug mode the driver will print all
  242. * SQL to the screen together with some information about the
  243. * results. All SQL code that passes through the driver will be
  244. * passes on to the screen for inspection.
  245. * This method has no return value.
  246. *
  247. * @param boolean $trueFalse turn on/off
  248. *
  249. * @return void
  250. */
  251. public function setDebugMode( $tf );
  252. /**
  253. * Starts a transaction.
  254. *
  255. * @return void
  256. */
  257. public function CommitTrans();
  258. /**
  259. * Commits a transaction.
  260. *
  261. * @return void
  262. */
  263. public function StartTrans();
  264. /**
  265. * Rolls back a transaction.
  266. *
  267. * @return void
  268. */
  269. public function FailTrans();
  270. }
  271. }
  272. namespace RedBeanPHP\Driver {
  273. use RedBeanPHP\Driver as Driver;
  274. use RedBeanPHP\Logger as Logger;
  275. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  276. use RedBeanPHP\RedException\SQL as SQL;
  277. use RedBeanPHP\Logger\RDefault as RDefault;
  278. use RedBeanPHP\PDOCompatible as PDOCompatible;
  279. /**
  280. *\PDO Driver
  281. * This Driver implements the RedBean Driver API
  282. *
  283. * @file RedBean/PDO.php
  284. * @desc \PDO Driver
  285. * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes
  286. * @license BSD/GPLv2
  287. *
  288. * (c) copyright Desfrenes & Gabor de Mooij and the RedBeanPHP community
  289. * This source file is subject to the BSD/GPLv2 License that is bundled
  290. * with this source code in the file license.txt.
  291. */
  292. class RPDO implements Driver
  293. {
  294. /**
  295. * @var string
  296. */
  297. protected $dsn;
  298. /**
  299. * @var boolean
  300. */
  301. protected $debug = FALSE;
  302. /**
  303. * @var Logger
  304. */
  305. protected $logger = NULL;
  306. /**
  307. * @var\PDO
  308. */
  309. protected $pdo;
  310. /**
  311. * @var integer
  312. */
  313. protected $affectedRows;
  314. /**
  315. * @var integer
  316. */
  317. protected $resultArray;
  318. /**
  319. * @var array
  320. */
  321. protected $connectInfo = array();
  322. /**
  323. * @var boolean
  324. */
  325. protected $isConnected = FALSE;
  326. /**
  327. * @var bool
  328. */
  329. protected $flagUseStringOnlyBinding = FALSE;
  330. /**
  331. * @var string
  332. */
  333. protected $mysqlEncoding = '';
  334. /**
  335. * Binds parameters. This method binds parameters to a\PDOStatement for
  336. * Query Execution. This method binds parameters as NULL, INTEGER or STRING
  337. * and supports both named keys and question mark keys.
  338. *
  339. * @param \PDOStatement $statement \PDO Statement instance
  340. * @param array $bindings values that need to get bound to the statement
  341. *
  342. * @return void
  343. */
  344. protected function bindParams( $statement, $bindings )
  345. {
  346. foreach ( $bindings as $key => &$value ) {
  347. if ( is_integer( $key ) ) {
  348. if ( is_null( $value ) ) {
  349. $statement->bindValue( $key + 1, NULL,\PDO::PARAM_NULL );
  350. } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && $value < 2147483648 ) {
  351. $statement->bindParam( $key + 1, $value,\PDO::PARAM_INT );
  352. } else {
  353. $statement->bindParam( $key + 1, $value,\PDO::PARAM_STR );
  354. }
  355. } else {
  356. if ( is_null( $value ) ) {
  357. $statement->bindValue( $key, NULL,\PDO::PARAM_NULL );
  358. } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && $value < 2147483648 ) {
  359. $statement->bindParam( $key, $value,\PDO::PARAM_INT );
  360. } else {
  361. $statement->bindParam( $key, $value,\PDO::PARAM_STR );
  362. }
  363. }
  364. }
  365. }
  366. /**
  367. * This method runs the actual SQL query and binds a list of parameters to the query.
  368. * slots. The result of the query will be stored in the protected property
  369. * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
  370. * is stored in protected property $affectedRows. If the debug flag is set
  371. * this function will send debugging output to screen buffer.
  372. *
  373. * @param string $sql the SQL string to be send to database server
  374. * @param array $bindings the values that need to get bound to the query slots
  375. *
  376. * @return void
  377. *
  378. * @throws SQL
  379. */
  380. protected function runQuery( $sql, $bindings, $options = array() )
  381. {
  382. $this->connect();
  383. if ( $this->debug && $this->logger ) {
  384. $this->logger->log( $sql, $bindings );
  385. }
  386. try {
  387. if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
  388. $statement = $this->pdo->prepare( $sql, array(\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) );
  389. } else {
  390. $statement = $this->pdo->prepare( $sql );
  391. }
  392. $this->bindParams( $statement, $bindings );
  393. $statement->execute();
  394. $this->affectedRows = $statement->rowCount();
  395. if ( $statement->columnCount() ) {
  396. $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
  397. $this->resultArray = $statement->fetchAll( $fetchStyle );
  398. if ( $this->debug && $this->logger ) {
  399. $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
  400. }
  401. } else {
  402. $this->resultArray = array();
  403. }
  404. } catch (\PDOException $e ) {
  405. //Unfortunately the code field is supposed to be int by default (php)
  406. //So we need a property to convey the SQL State code.
  407. $err = $e->getMessage();
  408. if ( $this->debug && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
  409. $exception = new SQL( $err, 0 );
  410. $exception->setSQLState( $e->getCode() );
  411. throw $exception;
  412. }
  413. }
  414. /**
  415. * Try to fix MySQL character encoding problems.
  416. * MySQL < 5.5 does not support proper 4 byte unicode but they
  417. * seem to have added it with version 5.5 under a different label: utf8mb4.
  418. * We try to select the best possible charset based on your version data.
  419. */
  420. protected function setEncoding()
  421. {
  422. $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
  423. $version = floatval( $this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION ) );
  424. if ($driver === 'mysql') {
  425. $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8';
  426. $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect
  427. $this->pdo->exec(' SET NAMES '. $encoding); //also for current connection
  428. $this->mysqlEncoding = $encoding;
  429. }
  430. }
  431. /**
  432. * Returns the best possible encoding for MySQL based on version data.
  433. *
  434. * @return string
  435. */
  436. public function getMysqlEncoding()
  437. {
  438. return $this->mysqlEncoding;
  439. }
  440. /**
  441. * Constructor. You may either specify dsn, user and password or
  442. * just give an existing\PDO connection.
  443. * Examples:
  444. * $driver = new RPDO($dsn, $user, $password);
  445. * $driver = new RPDO($existingConnection);
  446. *
  447. * @param string|object $dsn database connection string
  448. * @param string $user optional, usename to sign in
  449. * @param string $pass optional, password for connection login
  450. *
  451. */
  452. public function __construct( $dsn, $user = NULL, $pass = NULL )
  453. {
  454. if ( is_object( $dsn ) ) {
  455. $this->pdo = $dsn;
  456. $this->isConnected = TRUE;
  457. $this->setEncoding();
  458. $this->pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
  459. $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
  460. // make sure that the dsn at least contains the type
  461. $this->dsn = $this->getDatabaseType();
  462. } else {
  463. $this->dsn = $dsn;
  464. $this->connectInfo = array( 'pass' => $pass, 'user' => $user );
  465. }
  466. }
  467. /**
  468. * Whether to bind all parameters as strings.
  469. *
  470. * @param boolean $yesNo pass TRUE to bind all parameters as strings.
  471. *
  472. * @return void
  473. */
  474. public function setUseStringOnlyBinding( $yesNo )
  475. {
  476. $this->flagUseStringOnlyBinding = (boolean) $yesNo;
  477. }
  478. /**
  479. * Establishes a connection to the database using PHP\PDO
  480. * functionality. If a connection has already been established this
  481. * method will simply return directly. This method also turns on
  482. * UTF8 for the database and\PDO-ERRMODE-EXCEPTION as well as
  483. *\PDO-FETCH-ASSOC.
  484. *
  485. * @throws\PDOException
  486. *
  487. * @return void
  488. */
  489. public function connect()
  490. {
  491. if ( $this->isConnected ) return;
  492. try {
  493. $user = $this->connectInfo['user'];
  494. $pass = $this->connectInfo['pass'];
  495. $this->pdo = new\PDO(
  496. $this->dsn,
  497. $user,
  498. $pass,
  499. array(\PDO::ATTR_ERRMODE =>\PDO::ERRMODE_EXCEPTION,
  500. \PDO::ATTR_DEFAULT_FETCH_MODE =>\PDO::FETCH_ASSOC,
  501. )
  502. );
  503. $this->setEncoding();
  504. $this->pdo->setAttribute(\PDO::ATTR_STRINGIFY_FETCHES, TRUE );
  505. $this->isConnected = TRUE;
  506. } catch (\PDOException $exception ) {
  507. $matches = array();
  508. $dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
  509. throw new\PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
  510. }
  511. }
  512. /**
  513. * @see Driver::GetAll
  514. */
  515. public function GetAll( $sql, $bindings = array() )
  516. {
  517. $this->runQuery( $sql, $bindings );
  518. return $this->resultArray;
  519. }
  520. /**
  521. * @see Driver::GetAssocRow
  522. */
  523. public function GetAssocRow( $sql, $bindings = array() )
  524. {
  525. $this->runQuery( $sql, $bindings, array(
  526. 'fetchStyle' => \PDO::FETCH_ASSOC
  527. )
  528. );
  529. return $this->resultArray;
  530. }
  531. /**
  532. * @see Driver::GetCol
  533. */
  534. public function GetCol( $sql, $bindings = array() )
  535. {
  536. $rows = $this->GetAll( $sql, $bindings );
  537. $cols = array();
  538. if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) {
  539. foreach ( $rows as $row ) {
  540. $cols[] = array_shift( $row );
  541. }
  542. }
  543. return $cols;
  544. }
  545. /**
  546. * @see Driver::GetCell
  547. */
  548. public function GetCell( $sql, $bindings = array() )
  549. {
  550. $arr = $this->GetAll( $sql, $bindings );
  551. $row1 = array_shift( $arr );
  552. $col1 = array_shift( $row1 );
  553. return $col1;
  554. }
  555. /**
  556. * @see Driver::GetRow
  557. */
  558. public function GetRow( $sql, $bindings = array() )
  559. {
  560. $arr = $this->GetAll( $sql, $bindings );
  561. return array_shift( $arr );
  562. }
  563. /**
  564. * @see Driver::Excecute
  565. */
  566. public function Execute( $sql, $bindings = array() )
  567. {
  568. $this->runQuery( $sql, $bindings );
  569. return $this->affectedRows;
  570. }
  571. /**
  572. * @see Driver::GetInsertID
  573. */
  574. public function GetInsertID()
  575. {
  576. $this->connect();
  577. return (int) $this->pdo->lastInsertId();
  578. }
  579. /**
  580. * @see Driver::Affected_Rows
  581. */
  582. public function Affected_Rows()
  583. {
  584. $this->connect();
  585. return (int) $this->affectedRows;
  586. }
  587. /**
  588. * Toggles debug mode. In debug mode the driver will print all
  589. * SQL to the screen together with some information about the
  590. * results.
  591. *
  592. * @param boolean $trueFalse turn on/off
  593. * @param Logger $logger logger instance
  594. *
  595. * @return void
  596. */
  597. public function setDebugMode( $tf, $logger = NULL )
  598. {
  599. $this->connect();
  600. $this->debug = (bool) $tf;
  601. if ( $this->debug and !$logger ) {
  602. $logger = new RDefault();
  603. }
  604. $this->setLogger( $logger );
  605. }
  606. /**
  607. * Injects Logger object.
  608. * Sets the logger instance you wish to use.
  609. *
  610. * @param Logger $logger the logger instance to be used for logging
  611. */
  612. public function setLogger( Logger $logger )
  613. {
  614. $this->logger = $logger;
  615. }
  616. /**
  617. * Gets Logger object.
  618. * Returns the currently active Logger instance.
  619. *
  620. * @return Logger
  621. */
  622. public function getLogger()
  623. {
  624. return $this->logger;
  625. }
  626. /**
  627. * @see Driver::StartTrans
  628. */
  629. public function StartTrans()
  630. {
  631. $this->connect();
  632. $this->pdo->beginTransaction();
  633. }
  634. /**
  635. * @see Driver::CommitTrans
  636. */
  637. public function CommitTrans()
  638. {
  639. $this->connect();
  640. $this->pdo->commit();
  641. }
  642. /**
  643. * @see Driver::FailTrans
  644. */
  645. public function FailTrans()
  646. {
  647. $this->connect();
  648. $this->pdo->rollback();
  649. }
  650. /**
  651. * Returns the name of database driver for\PDO.
  652. * Uses the\PDO attribute DRIVER NAME to obtain the name of the
  653. *\PDO driver.
  654. *
  655. * @return string
  656. */
  657. public function getDatabaseType()
  658. {
  659. $this->connect();
  660. return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
  661. }
  662. /**
  663. * Returns the version number of the database.
  664. *
  665. * @return mixed $version version number of the database
  666. */
  667. public function getDatabaseVersion()
  668. {
  669. $this->connect();
  670. return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
  671. }
  672. /**
  673. * Returns the underlying PHP\PDO instance.
  674. *
  675. * @return\PDO
  676. */
  677. public function getPDO()
  678. {
  679. $this->connect();
  680. return $this->pdo;
  681. }
  682. /**
  683. * Closes database connection by destructing\PDO.
  684. *
  685. * @return void
  686. */
  687. public function close()
  688. {
  689. $this->pdo = NULL;
  690. $this->isConnected = FALSE;
  691. }
  692. /**
  693. * Returns TRUE if the current\PDO instance is connected.
  694. *
  695. * @return boolean
  696. */
  697. public function isConnected()
  698. {
  699. return $this->isConnected && $this->pdo;
  700. }
  701. }
  702. }
  703. namespace RedBeanPHP {
  704. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  705. use RedBeanPHP\BeanHelper as BeanHelper;
  706. use RedBeanPHP\RedException\Security as Security;
  707. /**
  708. * OODBBean (Object Oriented DataBase Bean)
  709. *
  710. * @file RedBean/OODBBean.php
  711. * @desc The Bean class used for passing information
  712. * @author Gabor de Mooij and the RedBeanPHP community
  713. * @license BSD/GPLv2
  714. *
  715. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  716. * This source file is subject to the BSD/GPLv2 License that is bundled
  717. * with this source code in the file license.txt.
  718. */
  719. class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable
  720. {
  721. /**
  722. * Whether to skip beautification of columns or not.
  723. *
  724. * @var boolean
  725. */
  726. private $flagSkipBeau = FALSE;
  727. /**
  728. * This is where the real properties of the bean live. They are stored and retrieved
  729. * by the magic getter and setter (__get and __set).
  730. *
  731. * @var array $properties
  732. */
  733. private $properties = array();
  734. /**
  735. * Here we keep the meta data of a bean.
  736. *
  737. * @var array
  738. */
  739. private $__info = array();
  740. /**
  741. * The BeanHelper allows the bean to access the toolbox objects to implement
  742. * rich functionality, otherwise you would have to do everything with R or
  743. * external objects.
  744. *
  745. * @var BeanHelper
  746. */
  747. private $beanHelper = NULL;
  748. /**
  749. * @var null
  750. */
  751. private $fetchType = NULL;
  752. /**
  753. * @var string
  754. */
  755. private $withSql = '';
  756. /**
  757. * @var array
  758. */
  759. private $withParams = array();
  760. /**
  761. * @var string
  762. */
  763. private $aliasName = NULL;
  764. /**
  765. * @var string
  766. */
  767. private $via = NULL;
  768. /**
  769. * @var boolean
  770. */
  771. private $noLoad = FALSE;
  772. /**
  773. * @var boolean
  774. */
  775. private $all = FALSE;
  776. /** Returns the alias for a type
  777. *
  778. * @param string $type type
  779. *
  780. * @return string $type type
  781. */
  782. private function getAlias( $type )
  783. {
  784. if ( $this->fetchType ) {
  785. $type = $this->fetchType;
  786. $this->fetchType = NULL;
  787. }
  788. return $type;
  789. }
  790. /**
  791. * Internal method.
  792. * Obtains a shared list for a certain type.
  793. *
  794. * @param string $type the name of the list you want to retrieve.
  795. *
  796. * @return array
  797. */
  798. private function getSharedList( $type, $redbean, $toolbox )
  799. {
  800. $writer = $toolbox->getWriter();
  801. if ( $this->via ) {
  802. $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
  803. if ( $oldName !== $this->via ) {
  804. //set the new renaming rule
  805. $writer->renameAssocTable( $oldName, $this->via );
  806. }
  807. $this->via = NULL;
  808. }
  809. $beans = array();
  810. if ($this->getID()) {
  811. $type = $this->beau( $type );
  812. $assocManager = $redbean->getAssociationManager();
  813. $beans = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
  814. }
  815. $this->withSql = '';
  816. $this->withParams = array();
  817. return $beans;
  818. }
  819. /**
  820. * Internal method.
  821. * Obtains the own list of a certain type.
  822. *
  823. * @param string $type name of the list you want to retrieve
  824. *
  825. * @return array
  826. */
  827. private function getOwnList( $type, $redbean )
  828. {
  829. $type = $this->beau( $type );
  830. if ( $this->aliasName ) {
  831. $parentField = $this->aliasName;
  832. $myFieldLink = $parentField . '_id';
  833. $this->__info['sys.alias.' . $type] = $this->aliasName;
  834. $this->aliasName = NULL;
  835. } else {
  836. $parentField = $this->__info['type'];
  837. $myFieldLink = $parentField . '_id';
  838. }
  839. $beans = array();
  840. if ( $this->getID() ) {
  841. $firstKey = NULL;
  842. if ( count( $this->withParams ) > 0 ) {
  843. reset( $this->withParams );
  844. $firstKey = key( $this->withParams );
  845. }
  846. if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
  847. $bindings = $this->withParams;
  848. $bindings[':slot0'] = $this->getID();
  849. $beans = $redbean->find( $type, array(), " $myFieldLink = :slot0 " . $this->withSql, $bindings );
  850. } else {
  851. $bindings = array_merge( array( $this->getID() ), $this->withParams );
  852. $beans = $redbean->find( $type, array(), " $myFieldLink = ? " . $this->withSql, $bindings );
  853. }
  854. }
  855. $this->withSql = '';
  856. $this->withParams = array();
  857. foreach ( $beans as $beanFromList ) {
  858. $beanFromList->__info['sys.parentcache.' . $parentField] = $this;
  859. }
  860. return $beans;
  861. }
  862. /**
  863. * Sets a meta property for all beans. This is a quicker way to set
  864. * the meta properties for a collection of beans because this method
  865. * can directly access the property arrays of the beans.
  866. * This method returns the beans.
  867. *
  868. * @param array $beans beans to set the meta property of
  869. * @param string $property property to set
  870. * @param mixed $value value
  871. *
  872. * @return array
  873. */
  874. public static function setMetaAll( $beans, $property, $value )
  875. {
  876. foreach( $beans as $bean ) {
  877. $bean->__info[ $property ] = $value;
  878. }
  879. return $beans;
  880. }
  881. /**
  882. * Initializes a bean. Used by OODB for dispensing beans.
  883. * It is not recommended to use this method to initialize beans. Instead
  884. * use the OODB object to dispense new beans. You can use this method
  885. * if you build your own bean dispensing mechanism.
  886. *
  887. * @param string $type type of the new bean
  888. * @param BeanHelper $beanhelper bean helper to obtain a toolbox and a model
  889. *
  890. * @return void
  891. */
  892. public function initializeForDispense( $type, BeanHelper $beanhelper )
  893. {
  894. $this->beanHelper = $beanhelper;
  895. $this->__info['type'] = $type;
  896. $this->__info['sys.id'] = 'id';
  897. $this->__info['sys.orig'] = array( 'id' => 0 );
  898. $this->__info['tainted'] = TRUE;
  899. $this->properties['id'] = 0;
  900. }
  901. /**
  902. * Sets the Bean Helper. Normally the Bean Helper is set by OODB.
  903. * Here you can change the Bean Helper. The Bean Helper is an object
  904. * providing access to a toolbox for the bean necessary to retrieve
  905. * nested beans (bean lists: ownBean, sharedBean) without the need to
  906. * rely on static calls to the facade (or make this class dep. on OODB).
  907. *
  908. * @param BeanHelper $helper
  909. *
  910. * @return void
  911. */
  912. public function setBeanHelper( BeanHelper $helper )
  913. {
  914. $this->beanHelper = $helper;
  915. }
  916. /**
  917. * Returns an\ArrayIterator so you can treat the bean like
  918. * an array with the properties container as its contents.
  919. * This method is meant for PHP and allows you to access beans as if
  920. * they were arrays, i.e. using array notation:
  921. *
  922. * $bean[ $key ] = $value;
  923. *
  924. * Note that not all PHP functions work with the array interface.
  925. *
  926. * @return\ArrayIterator
  927. */
  928. public function getIterator()
  929. {
  930. return new\ArrayIterator( $this->properties );
  931. }
  932. /**
  933. * Imports all values from an associative array $array. Chainable.
  934. * This method imports the values in the first argument as bean
  935. * propery and value pairs. Use the second parameter to provide a
  936. * selection. If a selection array is passed, only the entries
  937. * having keys mentioned in the selection array will be imported.
  938. * Set the third parameter to TRUE to preserve spaces in selection keys.
  939. *
  940. * @param array $array what you want to import
  941. * @param string|array $selection selection of values
  942. * @param boolean $notrim if TRUE selection keys will NOT be trimmed
  943. *
  944. * @return OODBBean
  945. */
  946. public function import( $array, $selection = FALSE, $notrim = FALSE )
  947. {
  948. if ( is_string( $selection ) ) {
  949. $selection = explode( ',', $selection );
  950. }
  951. if ( !$notrim && is_array( $selection ) ) {
  952. foreach ( $selection as $key => $selected ) {
  953. $selection[$key] = trim( $selected );
  954. }
  955. }
  956. foreach ( $array as $key => $value ) {
  957. if ( $key != '__info' ) {
  958. if ( !$selection || ( $selection && in_array( $key, $selection ) ) ) {
  959. if ( is_array($value ) ) {
  960. if ( isset( $value['_type'] ) ) {
  961. $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
  962. unset( $value['_type'] );
  963. $bean->import($value);
  964. $this->$key = $bean;
  965. } else {
  966. $listBeans = array();
  967. foreach( $value as $listKey => $listItem ) {
  968. $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
  969. unset( $listItem['_type'] );
  970. $bean->import($listItem);
  971. $list = &$this->$key;
  972. $list[ $listKey ] = $bean;
  973. }
  974. }
  975. } else {
  976. $this->$key = $value;
  977. }
  978. }
  979. }
  980. }
  981. return $this;
  982. }
  983. /**
  984. * Fast way to import a row.
  985. * Does not perform any checks.
  986. *
  987. * @param array $row a database row
  988. *
  989. * @return self
  990. */
  991. public function importRow( $row )
  992. {
  993. $this->properties = $row;
  994. $this->__info['sys.orig'] = $row;
  995. return $this;
  996. }
  997. /**
  998. * Imports data from another bean. Chainable.
  999. * Copies the properties from the source bean to the internal
  1000. * property list.
  1001. *
  1002. * @param OODBBean $sourceBean the source bean to take properties from
  1003. *
  1004. * @return OODBBean
  1005. */
  1006. public function importFrom( OODBBean $sourceBean )
  1007. {
  1008. $this->__info['tainted'] = TRUE;
  1009. $this->properties = $sourceBean->properties;
  1010. return $this;
  1011. }
  1012. /**
  1013. * Injects the properties of another bean but keeps the original ID.
  1014. * Just like import() but keeps the original ID.
  1015. * Chainable.
  1016. *
  1017. * @param OODBBean $otherBean the bean whose properties you would like to copy
  1018. *
  1019. * @return OODBBean
  1020. */
  1021. public function inject( OODBBean $otherBean )
  1022. {
  1023. $myID = $this->properties['id'];
  1024. $this->import( $otherBean->export() );
  1025. $this->id = $myID;
  1026. return $this;
  1027. }
  1028. /**
  1029. * Exports the bean as an array.
  1030. * This function exports the contents of a bean to an array and returns
  1031. * the resulting array.
  1032. *
  1033. * @param boolean $meta set to TRUE if you want to export meta data as well
  1034. * @param boolean $parents set to TRUE if you want to export parents as well
  1035. * @param boolean $onlyMe set to TRUE if you want to export only this bean
  1036. * @param array $filters optional whitelist for export
  1037. *
  1038. * @return array
  1039. */
  1040. public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
  1041. {
  1042. $arr = array();
  1043. if ( $parents ) {
  1044. foreach ( $this as $key => $value ) {
  1045. if ( substr( $key, -3 ) != '_id' ) continue;
  1046. $prop = substr( $key, 0, strlen( $key ) - 3 );
  1047. $this->$prop;
  1048. }
  1049. }
  1050. $hasFilters = is_array( $filters ) && count( $filters );
  1051. foreach ( $this as $key => $value ) {
  1052. if ( !$onlyMe && is_array( $value ) ) {
  1053. $vn = array();
  1054. foreach ( $value as $i => $b ) {
  1055. $vn[] = $b->export( $meta, FALSE, FALSE, $filters );
  1056. $value = $vn;
  1057. }
  1058. } elseif ( $value instanceof OODBBean ) {
  1059. if ( $hasFilters ) {
  1060. if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
  1061. }
  1062. $value = $value->export( $meta, $parents, FALSE, $filters );
  1063. }
  1064. $arr[$key] = $value;
  1065. }
  1066. if ( $meta ) {
  1067. $arr['__info'] = $this->__info;
  1068. }
  1069. return $arr;
  1070. }
  1071. /**
  1072. * Implements isset() function for use as an array.
  1073. *
  1074. * @param string $property name of the property you want to check
  1075. *
  1076. * @return boolean
  1077. */
  1078. public function __isset( $property )
  1079. {
  1080. $property = $this->beau( $property );
  1081. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  1082. $property = substr($property, 1);
  1083. }
  1084. return isset( $this->properties[$property] );
  1085. }
  1086. /**
  1087. * Returns the ID of the bean no matter what the ID field is.
  1088. *
  1089. * @return string|null
  1090. */
  1091. public function getID()
  1092. {
  1093. return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
  1094. }
  1095. /**
  1096. * Unsets a property. This method will load the property first using
  1097. * __get.
  1098. *
  1099. * @param string $property property
  1100. *
  1101. * @return void
  1102. */
  1103. public function __unset( $property )
  1104. {
  1105. $property = $this->beau( $property );
  1106. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  1107. $property = substr($property, 1);
  1108. }
  1109. unset( $this->properties[$property] );
  1110. $shadowKey = 'sys.shadow.'.$property;
  1111. if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
  1112. //also clear modifiers
  1113. $this->withSql = '';
  1114. $this->withParams = array();
  1115. $this->aliasName = NULL;
  1116. $this->fetchType = NULL;
  1117. $this->noLoad = FALSE;
  1118. $this->all = FALSE;
  1119. $this->via = NULL;
  1120. return;
  1121. }
  1122. /**
  1123. * Adds WHERE clause conditions to ownList retrieval.
  1124. * For instance to get the pages that belong to a book you would
  1125. * issue the following command: $book->ownPage
  1126. * However, to order these pages by number use:
  1127. *
  1128. * $book->with(' ORDER BY `number` ASC ')->ownPage
  1129. *
  1130. * the additional SQL snippet will be merged into the final
  1131. * query.
  1132. *
  1133. * @param string $sql SQL to be added to retrieval query.
  1134. * @param array $bindings array with parameters to bind to SQL snippet
  1135. *
  1136. * @return OODBBean
  1137. */
  1138. public function with( $sql, $bindings = array() )
  1139. {
  1140. $this->withSql = $sql;
  1141. $this->withParams = $bindings;
  1142. return $this;
  1143. }
  1144. /**
  1145. * Just like with(). Except that this method prepends the SQL query snippet
  1146. * with AND which makes it slightly more comfortable to use a conditional
  1147. * SQL snippet. For instance to filter an own-list with pages (belonging to
  1148. * a book) on specific chapters you can use:
  1149. *
  1150. * $book->withCondition(' chapter = 3 ')->ownPage
  1151. *
  1152. * This will return in the own list only the pages having 'chapter == 3'.
  1153. *
  1154. * @param string $sql SQL to be added to retrieval query (prefixed by AND)
  1155. * @param array $bindings array with parameters to bind to SQL snippet
  1156. *
  1157. * @return OODBBean
  1158. */
  1159. public function withCondition( $sql, $bindings = array() )
  1160. {
  1161. $this->withSql = ' AND ' . $sql;
  1162. $this->withParams = $bindings;
  1163. return $this;
  1164. }
  1165. /**
  1166. * When prefix for a list, this causes the list to reload.
  1167. *
  1168. * @return self
  1169. */
  1170. public function all()
  1171. {
  1172. $this->all = TRUE;
  1173. return $this;
  1174. }
  1175. /**
  1176. * Tells the bean to only access the list but not load
  1177. * its contents. Use this if you only want to add something to a list
  1178. * and you have no interest in retrieving its contents from the database.
  1179. *
  1180. * @return self
  1181. */
  1182. public function noLoad()
  1183. {
  1184. $this->noLoad = TRUE;
  1185. return $this;
  1186. }
  1187. /**
  1188. * Prepares an own-list to use an alias. This is best explained using
  1189. * an example. Imagine a project and a person. The project always involves
  1190. * two persons: a teacher and a student. The person beans have been aliased in this
  1191. * case, so to the project has a teacher_id pointing to a person, and a student_id
  1192. * also pointing to a person. Given a project, we obtain the teacher like this:
  1193. *
  1194. * $project->fetchAs('person')->teacher;
  1195. *
  1196. * Now, if we want all projects of a teacher we cant say:
  1197. *
  1198. * $teacher->ownProject
  1199. *
  1200. * because the $teacher is a bean of type 'person' and no project has been
  1201. * assigned to a person. Instead we use the alias() method like this:
  1202. *
  1203. * $teacher->alias('teacher')->ownProject
  1204. *
  1205. * now we get the projects associated with the person bean aliased as
  1206. * a teacher.
  1207. *
  1208. * @param string $aliasName the alias name to use
  1209. *
  1210. * @return OODBBean
  1211. */
  1212. public function alias( $aliasName )
  1213. {
  1214. $this->aliasName = $this->beau( $aliasName );
  1215. return $this;
  1216. }
  1217. /**
  1218. * Returns properties of bean as an array.
  1219. * This method returns the raw internal property list of the
  1220. * bean. Only use this method for optimization purposes. Otherwise
  1221. * use the export() method to export bean data to arrays.
  1222. *
  1223. * @return array
  1224. */
  1225. public function getProperties()
  1226. {
  1227. return $this->properties;
  1228. }
  1229. /**
  1230. * Returns properties of bean as an array.
  1231. * This method returns the raw internal property list of the
  1232. * bean. Only use this method for optimization purposes. Otherwise
  1233. * use the export() method to export bean data to arrays.
  1234. * This method returns an array with the properties array and
  1235. * the type (string).
  1236. *
  1237. * @return array
  1238. */
  1239. public function getPropertiesAndType()
  1240. {
  1241. return array( $this->properties, $this->__info['type'] );
  1242. }
  1243. /**
  1244. * Turns a camelcase property name into an underscored property name.
  1245. * Examples:
  1246. * oneACLRoute -> one_acl_route
  1247. * camelCase -> camel_case
  1248. *
  1249. * Also caches the result to improve performance.
  1250. *
  1251. * @param string $property
  1252. *
  1253. * @return string
  1254. */
  1255. public function beau( $property )
  1256. {
  1257. static $beautifulColumns = array();
  1258. if ( ctype_lower( $property ) ) return $property;
  1259. if (
  1260. strpos( $property, 'own' ) === 0
  1261. || strpos( $property, 'xown' ) === 0
  1262. || strpos( $property, 'shared' ) === 0
  1263. ) {
  1264. $property = preg_replace( '/List$/', '', $property );
  1265. return $property;
  1266. }
  1267. if ( !isset( $beautifulColumns[$property] ) ) {
  1268. $beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
  1269. }
  1270. return $beautifulColumns[$property];
  1271. }
  1272. /**
  1273. * Returns current status of modification flags.
  1274. *
  1275. * @return string
  1276. */
  1277. public function getModFlags()
  1278. {
  1279. $modFlags = '';
  1280. if ($this->aliasName !== NULL) $modFlags .= 'a';
  1281. if ($this->fetchType !== NULL) $modFlags .= 'f';
  1282. if ($this->noLoad === TRUE) $modFlags .= 'n';
  1283. if ($this->all === TRUE) $modFlags .= 'r';
  1284. if ($this->withSql !== '') $modFlags .= 'w';
  1285. return $modFlags;
  1286. }
  1287. /**
  1288. * Clears all modifiers.
  1289. *
  1290. * @return self
  1291. */
  1292. public function clearModifiers()
  1293. {
  1294. $this->withSql = '';
  1295. $this->withParams = array();
  1296. $this->aliasName = NULL;
  1297. $this->fetchType = NULL;
  1298. $this->noLoad = FALSE;
  1299. $this->all = FALSE;
  1300. $this->via = NULL;
  1301. return $this;
  1302. }
  1303. /**
  1304. * Determines whether a list is opened in exclusive mode or not.
  1305. * If a list has been opened in exclusive mode this method will return TRUE,
  1306. * othwerwise it will return FALSE.
  1307. *
  1308. * @param string $listName name of the list to check
  1309. *
  1310. * @return boolean
  1311. */
  1312. public function isListInExclusiveMode( $listName )
  1313. {
  1314. $listName = $this->beau( $listName );
  1315. if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
  1316. $listName = substr($listName, 1);
  1317. }
  1318. $listName = lcfirst( substr( $listName, 3 ) );
  1319. return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
  1320. }
  1321. /**
  1322. * Magic Getter. Gets the value for a specific property in the bean.
  1323. * If the property does not exist this getter will make sure no error
  1324. * occurs. This is because RedBean allows you to query (probe) for
  1325. * properties. If the property can not be found this method will
  1326. * return NULL instead.
  1327. *
  1328. * @param string $property name of the property you wish to obtain the value of
  1329. *
  1330. * @return mixed
  1331. */
  1332. public function &__get( $property )
  1333. {
  1334. $isEx = FALSE;
  1335. $isOwn = FALSE;
  1336. $isShared = FALSE;
  1337. if ( !ctype_lower( $property ) ) {
  1338. $property = $this->beau( $property );
  1339. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  1340. $property = substr($property, 1);
  1341. $listName = lcfirst( substr( $property, 3 ) );
  1342. $isEx = TRUE;
  1343. $isOwn = TRUE;
  1344. $this->__info['sys.exclusive-'.$listName] = TRUE;
  1345. } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
  1346. $isOwn = TRUE;
  1347. $listName = lcfirst( substr( $property, 3 ) );
  1348. } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
  1349. $isShared = TRUE;
  1350. }
  1351. }
  1352. $fieldLink = $property . '_id';
  1353. $exists = isset( $this->properties[$property] );
  1354. //If not exists and no field link and no list, bail out.
  1355. if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
  1356. $this->withSql = '';
  1357. $this->withParams = array();
  1358. $this->aliasName = NULL;
  1359. $this->fetchType = NULL;
  1360. $this->noLoad = FALSE;
  1361. $this->all = FALSE;
  1362. $this->via = NULL;
  1363. $NULL = NULL;
  1364. return $NULL;
  1365. }
  1366. $hasAlias = (!is_null($this->aliasName));
  1367. $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
  1368. ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
  1369. $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
  1370. $hasAll = (boolean) ($this->all);
  1371. //If exists and no list or exits and list not changed, bail out.
  1372. if ( $exists && ((!$isOwn && !$isShared ) || (!$hasSQL && !$differentAlias && !$hasAll)) ) {
  1373. $this->withSql = '';
  1374. $this->withParams = array();
  1375. $this->aliasName = NULL;
  1376. $this->fetchType = NULL;
  1377. $this->noLoad = FALSE;
  1378. $this->all = FALSE;
  1379. $this->via = NULL;
  1380. return $this->properties[$property];
  1381. }
  1382. list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
  1383. if ( isset( $this->$fieldLink ) ) {
  1384. $this->__info['tainted'] = TRUE;
  1385. if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
  1386. $bean = $this->__info["sys.parentcache.$property"];
  1387. } else {
  1388. $type = $this->getAlias( $property );
  1389. $bean = $redbean->load( $type, $this->properties[$fieldLink] );
  1390. }
  1391. $this->properties[$property] = $bean;
  1392. $this->withSql = '';
  1393. $this->withParams = array();
  1394. $this->aliasName = NULL;
  1395. $this->fetchType = NULL;
  1396. $this->noLoad = FALSE;
  1397. $this->all = FALSE;
  1398. $this->via = NULL;
  1399. return $this->properties[$property];
  1400. }
  1401. //Implicit: elseif ( $isOwn || $isShared ) {
  1402. if ( $this->noLoad ) {
  1403. $beans = array();
  1404. } elseif ( $isOwn ) {
  1405. $beans = $this->getOwnList( $listName, $redbean );
  1406. } else {
  1407. $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
  1408. }
  1409. $this->properties[$property] = $beans;
  1410. $this->__info["sys.shadow.$property"] = $beans;
  1411. $this->__info['tainted'] = TRUE;
  1412. $this->withSql = '';
  1413. $this->withParams = array();
  1414. $this->aliasName = NULL;
  1415. $this->fetchType = NULL;
  1416. $this->noLoad = FALSE;
  1417. $this->all = FALSE;
  1418. $this->via = NULL;
  1419. return $this->properties[$property];
  1420. }
  1421. /**
  1422. * Magic Setter. Sets the value for a specific property.
  1423. * This setter acts as a hook for OODB to mark beans as tainted.
  1424. * The tainted meta property can be retrieved using getMeta("tainted").
  1425. * The tainted meta property indicates whether a bean has been modified and
  1426. * can be used in various caching mechanisms.
  1427. *
  1428. * @param string $property name of the property you wish to assign a value to
  1429. * @param mixed $value the value you want to assign
  1430. *
  1431. * @return void
  1432. *
  1433. * @throws Security
  1434. */
  1435. public function __set( $property, $value )
  1436. {
  1437. $isEx = FALSE;
  1438. $isOwn = FALSE;
  1439. $isShared = FALSE;
  1440. if ( !ctype_lower( $property ) ) {
  1441. $property = $this->beau( $property );
  1442. if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
  1443. $property = substr($property, 1);
  1444. $listName = lcfirst( substr( $property, 3 ) );
  1445. $isEx = TRUE;
  1446. $isOwn = TRUE;
  1447. $this->__info['sys.exclusive-'.$listName] = TRUE;
  1448. } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
  1449. $isOwn = TRUE;
  1450. $listName = lcfirst( substr( $property, 3 ) );
  1451. } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
  1452. $isShared = TRUE;
  1453. }
  1454. }
  1455. $hasAlias = (!is_null($this->aliasName));
  1456. $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
  1457. ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
  1458. $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
  1459. $exists = isset( $this->properties[$property] );
  1460. $fieldLink = $property . '_id';
  1461. if ( ($isOwn || $isShared) && (!$exists || $hasSQL || $differentAlias) ) {
  1462. if ( !$this->noLoad ) {
  1463. list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
  1464. if ( $isOwn ) {
  1465. $beans = $this->getOwnList( $listName, $redbean );
  1466. } else {
  1467. $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
  1468. }
  1469. $this->__info["sys.shadow.$property"] = $beans;
  1470. }
  1471. }
  1472. $this->withSql = '';
  1473. $this->withParams = array();
  1474. $this->aliasName = NULL;
  1475. $this->fetchType = NULL;
  1476. $this->noLoad = FALSE;
  1477. $this->all = FALSE;
  1478. $this->via = NULL;
  1479. $this->__info['tainted'] = TRUE;
  1480. if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
  1481. if ( is_null( $value ) || $value === FALSE ) {
  1482. unset( $this->properties[ $property ]);
  1483. $this->properties[ $fieldLink ] = NULL;
  1484. return;
  1485. } else {
  1486. throw new RedException( 'Cannot cast to bean.' );
  1487. }
  1488. }
  1489. if ( $value === FALSE ) {
  1490. $value = '0';
  1491. } elseif ( $value === TRUE ) {
  1492. $value = '1';
  1493. } elseif ( $value instanceof \DateTime ) {
  1494. $value = $value->format( 'Y-m-d H:i:s' );
  1495. }
  1496. $this->properties[$property] = $value;
  1497. }
  1498. /**
  1499. * Sets a property directly, for internal use only.
  1500. *
  1501. * @param string $property property
  1502. * @param mixed $value value
  1503. * @param boolean $updateShadow whether you want to update the shadow
  1504. * @param boolean $taint whether you want to mark the bean as tainted
  1505. *
  1506. * @return void
  1507. */
  1508. public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
  1509. {
  1510. $this->properties[$property] = $value;
  1511. if ( $updateShadow ) {
  1512. $this->__info['sys.shadow.' . $property] = $value;
  1513. }
  1514. if ( $taint ) {
  1515. $this->__info['tainted'] = TRUE;
  1516. }
  1517. }
  1518. /**
  1519. * Returns the value of a meta property. A meta property
  1520. * contains extra information about the bean object that will not
  1521. * get stored in the database. Meta information is used to instruct
  1522. * RedBean as well as other systems how to deal with the bean.
  1523. * For instance: $bean->setMeta("buildcommand.unique", array(
  1524. * array("column1", "column2", "column3") ) );
  1525. * Will add a UNIQUE constraint for the bean on columns: column1, column2 and
  1526. * column 3.
  1527. * To access a Meta property we use a dot separated notation.
  1528. * If the property cannot be found this getter will return NULL instead.
  1529. *
  1530. * @param string $path path
  1531. * @param mixed $default default value
  1532. *
  1533. * @return mixed
  1534. */
  1535. public function getMeta( $path, $default = NULL )
  1536. {
  1537. return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
  1538. }
  1539. /**
  1540. * Stores a value in the specified Meta information property. $value contains
  1541. * the value you want to store in the Meta section of the bean and $path
  1542. * specifies the dot separated path to the property. For instance "my.meta.property".
  1543. * If "my" and "meta" do not exist they will be created automatically.
  1544. *
  1545. * @param string $path path
  1546. * @param mixed $value value
  1547. *
  1548. * @return OODBBean
  1549. */
  1550. public function setMeta( $path, $value )
  1551. {
  1552. $this->__info[$path] = $value;
  1553. return $this;
  1554. }
  1555. /**
  1556. * Copies the meta information of the specified bean
  1557. * This is a convenience method to enable you to
  1558. * exchange meta information easily.
  1559. *
  1560. * @param OODBBean $bean
  1561. *
  1562. * @return OODBBean
  1563. */
  1564. public function copyMetaFrom( OODBBean $bean )
  1565. {
  1566. $this->__info = $bean->__info;
  1567. return $this;
  1568. }
  1569. /**
  1570. * Sends the call to the registered model.
  1571. *
  1572. * @param string $method name of the method
  1573. * @param array $args argument list
  1574. *
  1575. * @return mixed
  1576. */
  1577. public function __call( $method, $args )
  1578. {
  1579. if ( !isset( $this->__info['model'] ) ) {
  1580. $model = $this->beanHelper->getModelForBean( $this );
  1581. if ( !$model ) {
  1582. return NULL;
  1583. }
  1584. $this->__info['model'] = $model;
  1585. }
  1586. if ( !method_exists( $this->__info['model'], $method ) ) {
  1587. return NULL;
  1588. }
  1589. return call_user_func_array( array( $this->__info['model'], $method ), $args );
  1590. }
  1591. /**
  1592. * Implementation of __toString Method
  1593. * Routes call to Model. If the model implements a __toString() method this
  1594. * method will be called and the result will be returned. In case of an
  1595. * echo-statement this result will be printed. If the model does not
  1596. * implement a __toString method, this method will return a JSON
  1597. * representation of the current bean.
  1598. *
  1599. * @return string
  1600. */
  1601. public function __toString()
  1602. {
  1603. $string = $this->__call( '__toString', array() );
  1604. if ( $string === NULL ) {
  1605. return json_encode( $this->properties );
  1606. } else {
  1607. return $string;
  1608. }
  1609. }
  1610. /**
  1611. * Implementation of Array Access Interface, you can access bean objects
  1612. * like an array.
  1613. * Call gets routed to __set.
  1614. *
  1615. * @param mixed $offset offset string
  1616. * @param mixed $value value
  1617. *
  1618. * @return void
  1619. */
  1620. public function offsetSet( $offset, $value )
  1621. {
  1622. $this->__set( $offset, $value );
  1623. }
  1624. /**
  1625. * Implementation of Array Access Interface, you can access bean objects
  1626. * like an array.
  1627. *
  1628. * Array functions do not reveal x-own-lists and list-alias because
  1629. * you dont want duplicate entries in foreach-loops.
  1630. * Also offers a slight performance improvement for array access.
  1631. *
  1632. * @param mixed $offset property
  1633. *
  1634. * @return boolean
  1635. */
  1636. public function offsetExists( $offset )
  1637. {
  1638. return $this->__isset( $offset );
  1639. }
  1640. /**
  1641. * Implementation of Array Access Interface, you can access bean objects
  1642. * like an array.
  1643. * Unsets a value from the array/bean.
  1644. *
  1645. * Array functions do not reveal x-own-lists and list-alias because
  1646. * you dont want duplicate entries in foreach-loops.
  1647. * Also offers a slight performance improvement for array access.
  1648. *
  1649. * @param mixed $offset property
  1650. *
  1651. * @return void
  1652. */
  1653. public function offsetUnset( $offset )
  1654. {
  1655. $this->__unset( $offset );
  1656. }
  1657. /**
  1658. * Implementation of Array Access Interface, you can access bean objects
  1659. * like an array.
  1660. * Returns value of a property.
  1661. *
  1662. * Array functions do not reveal x-own-lists and list-alias because
  1663. * you dont want duplicate entries in foreach-loops.
  1664. * Also offers a slight performance improvement for array access.
  1665. *
  1666. * @param mixed $offset property
  1667. *
  1668. * @return mixed
  1669. */
  1670. public function &offsetGet( $offset )
  1671. {
  1672. return $this->__get( $offset );
  1673. }
  1674. /**
  1675. * Chainable method to cast a certain ID to a bean; for instance:
  1676. * $person = $club->fetchAs('person')->member;
  1677. * This will load a bean of type person using member_id as ID.
  1678. *
  1679. * @param string $type preferred fetch type
  1680. *
  1681. * @return OODBBean
  1682. */
  1683. public function fetchAs( $type )
  1684. {
  1685. $this->fetchType = $type;
  1686. return $this;
  1687. }
  1688. /**
  1689. * For polymorphic bean relations.
  1690. * Same as fetchAs but uses a column instead of a direct value.
  1691. *
  1692. * @param string $column
  1693. *
  1694. * @return OODBBean
  1695. */
  1696. public function poly( $field )
  1697. {
  1698. return $this->fetchAs( $this->$field );
  1699. }
  1700. /**
  1701. * Traverses a bean property with the specified function.
  1702. * Recursively iterates through the property invoking the
  1703. * function for each bean along the way passing the bean to it.
  1704. *
  1705. * Can be used together with with, withCondition, alias and fetchAs.
  1706. *
  1707. * @param string $property property
  1708. * @param closure $function function
  1709. *
  1710. * @return OODBBean
  1711. */
  1712. public function traverse( $property, $function, $maxDepth = NULL )
  1713. {
  1714. $this->via = NULL;
  1715. if ( strpos( $property, 'shared' ) !== FALSE ) {
  1716. throw new RedException( 'Traverse only works with (x)own-lists.' );
  1717. }
  1718. if ( !is_null( $maxDepth ) ) {
  1719. if ( !$maxDepth-- ) return $this;
  1720. }
  1721. $oldFetchType = $this->fetchType;
  1722. $oldAliasName = $this->aliasName;
  1723. $oldWith = $this->withSql;
  1724. $oldBindings = $this->withParams;
  1725. $beans = $this->$property;
  1726. if ( $beans === NULL ) return $this;
  1727. if ( !is_array( $beans ) ) $beans = array( $beans );
  1728. foreach( $beans as $bean ) {
  1729. $function( $bean );
  1730. $bean->fetchType = $oldFetchType;
  1731. $bean->aliasName = $oldAliasName;
  1732. $bean->withSql = $oldWith;
  1733. $bean->withParams = $oldBindings;
  1734. $bean->traverse( $property, $function, $maxDepth );
  1735. }
  1736. return $this;
  1737. }
  1738. /**
  1739. * Implementation of\Countable interface. Makes it possible to use
  1740. * count() function on a bean.
  1741. *
  1742. * @return integer
  1743. */
  1744. public function count()
  1745. {
  1746. return count( $this->properties );
  1747. }
  1748. /**
  1749. * Checks whether a bean is empty or not.
  1750. * A bean is empty if it has no other properties than the id field OR
  1751. * if all the other property are empty().
  1752. *
  1753. * @return boolean
  1754. */
  1755. public function isEmpty()
  1756. {
  1757. $empty = TRUE;
  1758. foreach ( $this->properties as $key => $value ) {
  1759. if ( $key == 'id' ) {
  1760. continue;
  1761. }
  1762. if ( !empty( $value ) ) {
  1763. $empty = FALSE;
  1764. }
  1765. }
  1766. return $empty;
  1767. }
  1768. /**
  1769. * Chainable setter.
  1770. *
  1771. * @param string $property the property of the bean
  1772. * @param mixed $value the value you want to set
  1773. *
  1774. * @return OODBBean
  1775. */
  1776. public function setAttr( $property, $value )
  1777. {
  1778. $this->$property = $value;
  1779. return $this;
  1780. }
  1781. /**
  1782. * Comfort method.
  1783. * Unsets all properties in array.
  1784. *
  1785. * @param array $properties properties you want to unset.
  1786. *
  1787. * @return OODBBean
  1788. */
  1789. public function unsetAll( $properties )
  1790. {
  1791. foreach ( $properties as $prop ) {
  1792. if ( isset( $this->properties[$prop] ) ) {
  1793. unset( $this->properties[$prop] );
  1794. }
  1795. }
  1796. return $this;
  1797. }
  1798. /**
  1799. * Returns original (old) value of a property.
  1800. * You can use this method to see what has changed in a
  1801. * bean.
  1802. *
  1803. * @param string $property name of the property you want the old value of
  1804. *
  1805. * @return mixed
  1806. */
  1807. public function old( $property )
  1808. {
  1809. $old = $this->getMeta( 'sys.orig', array() );
  1810. if ( array_key_exists( $property, $old ) ) {
  1811. return $old[$property];
  1812. }
  1813. return NULL;
  1814. }
  1815. /**
  1816. * Convenience method.
  1817. * Returns TRUE if the bean has been changed, or FALSE otherwise.
  1818. * Same as $bean->getMeta('tainted');
  1819. * Note that a bean becomes tainted as soon as you retrieve a list from
  1820. * the bean. This is because the bean lists are arrays and the bean cannot
  1821. * determine whether you have made modifications to a list so RedBeanPHP
  1822. * will mark the whole bean as tainted.
  1823. *
  1824. * @return boolean
  1825. */
  1826. public function isTainted()
  1827. {
  1828. return $this->getMeta( 'tainted' );
  1829. }
  1830. /**
  1831. * Returns TRUE if the value of a certain property of the bean has been changed and
  1832. * FALSE otherwise.
  1833. *
  1834. * @param string $property name of the property you want the change-status of
  1835. *
  1836. * @return boolean
  1837. */
  1838. public function hasChanged( $property )
  1839. {
  1840. return ( array_key_exists( $property, $this->properties ) ) ?
  1841. $this->old( $property ) != $this->properties[$property] : FALSE;
  1842. }
  1843. /**
  1844. * Creates a N-M relation by linking an intermediate bean.
  1845. * This method can be used to quickly connect beans using indirect
  1846. * relations. For instance, given an album and a song you can connect the two
  1847. * using a track with a number like this:
  1848. *
  1849. * Usage:
  1850. *
  1851. * $album->link('track', array('number'=>1))->song = $song;
  1852. *
  1853. * or:
  1854. *
  1855. * $album->link($trackBean)->song = $song;
  1856. *
  1857. * What this method does is adding the link bean to the own-list, in this case
  1858. * ownTrack. If the first argument is a string and the second is an array or
  1859. * a JSON string then the linking bean gets dispensed on-the-fly as seen in
  1860. * example #1. After preparing the linking bean, the bean is returned thus
  1861. * allowing the chained setter: ->song = $song.
  1862. *
  1863. * @param string|OODBBean $type type of bean to dispense or the full bean
  1864. * @param string|array $qualification JSON string or array (optional)
  1865. *
  1866. * @return OODBBean
  1867. */
  1868. public function link( $typeOrBean, $qualification = array() )
  1869. {
  1870. if ( is_string( $typeOrBean ) ) {
  1871. $typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
  1872. $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
  1873. if ( is_string( $qualification ) ) {
  1874. $data = json_decode( $qualification, TRUE );
  1875. } else {
  1876. $data = $qualification;
  1877. }
  1878. foreach ( $data as $key => $value ) {
  1879. $bean->$key = $value;
  1880. }
  1881. } else {
  1882. $bean = $typeOrBean;
  1883. }
  1884. $list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
  1885. array_push( $this->$list, $bean );
  1886. return $bean;
  1887. }
  1888. /**
  1889. * Returns the same bean freshly loaded from the database.
  1890. *
  1891. * @return OODBBean
  1892. */
  1893. public function fresh()
  1894. {
  1895. return $this->beanHelper->getToolbox()->getRedBean()->load( $this->getMeta( 'type' ), $this->properties['id'] );
  1896. }
  1897. /**
  1898. * Registers a association renaming globally.
  1899. *
  1900. * @param string $via type you wish to use for shared lists
  1901. *
  1902. * @return OODBBean
  1903. */
  1904. public function via( $via )
  1905. {
  1906. $this->via = $via;
  1907. return $this;
  1908. }
  1909. /**
  1910. * Counts all own beans of type $type.
  1911. * Also works with alias(), with() and withCondition().
  1912. *
  1913. * @param string $type the type of bean you want to count
  1914. *
  1915. * @return integer
  1916. */
  1917. public function countOwn( $type )
  1918. {
  1919. $type = $this->beau( $type );
  1920. if ( $this->aliasName ) {
  1921. $myFieldLink = $this->aliasName . '_id';
  1922. $this->aliasName = NULL;
  1923. } else {
  1924. $myFieldLink = $this->__info['type'] . '_id';
  1925. }
  1926. $count = 0;
  1927. if ( $this->getID() ) {
  1928. $firstKey = NULL;
  1929. if ( count( $this->withParams ) > 0 ) {
  1930. reset( $this->withParams );
  1931. $firstKey = key( $this->withParams );
  1932. }
  1933. if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
  1934. $bindings = $this->withParams;
  1935. $bindings[':slot0'] = $this->getID();
  1936. $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " $myFieldLink = :slot0 " . $this->withSql, $bindings );
  1937. } else {
  1938. $bindings = array_merge( array( $this->getID() ), $this->withParams );
  1939. $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " $myFieldLink = ? " . $this->withSql, $bindings );
  1940. }
  1941. }
  1942. $this->clearModifiers();
  1943. return (int) $count;
  1944. }
  1945. /**
  1946. * Counts all shared beans of type $type.
  1947. * Also works with via(), with() and withCondition().
  1948. *
  1949. * @param string $type type of bean you wish to count
  1950. *
  1951. * @return integer
  1952. */
  1953. public function countShared( $type )
  1954. {
  1955. $toolbox = $this->beanHelper->getToolbox();
  1956. $redbean = $toolbox->getRedBean();
  1957. $writer = $toolbox->getWriter();
  1958. if ( $this->via ) {
  1959. $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
  1960. if ( $oldName !== $this->via ) {
  1961. //set the new renaming rule
  1962. $writer->renameAssocTable( $oldName, $this->via );
  1963. $this->via = NULL;
  1964. }
  1965. }
  1966. $type = $this->beau( $type );
  1967. $count = 0;
  1968. if ( $this->getID() ) {
  1969. $count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams, TRUE );
  1970. }
  1971. $this->clearModifiers();
  1972. return (integer) $count;
  1973. }
  1974. /**
  1975. * Tests whether the database identities of two beans are equal.
  1976. *
  1977. * @param OODBBean $bean other bean
  1978. *
  1979. * @return boolean
  1980. */
  1981. public function equals(OODBBean $bean)
  1982. {
  1983. return (bool) (
  1984. ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
  1985. && ( (string) $this->__info['type'] === (string) $bean->__info['type'] )
  1986. );
  1987. }
  1988. }
  1989. }
  1990. namespace RedBeanPHP {
  1991. use RedBeanPHP\Observer as Observer;
  1992. /**
  1993. * Observable
  1994. * Base class for Observables
  1995. *
  1996. * @file RedBean/Observable.php
  1997. * @description Part of the observer pattern in RedBean
  1998. * @author Gabor de Mooij and the RedBeanPHP community
  1999. * @license BSD/GPLv2
  2000. *
  2001. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  2002. * This source file is subject to the BSD/GPLv2 License that is bundled
  2003. * with this source code in the file license.txt.
  2004. */
  2005. abstract class Observable { //bracket must be here - otherwise coverage software does not understand.
  2006. /**
  2007. * @var array
  2008. */
  2009. private $observers = array();
  2010. /**
  2011. * Implementation of the Observer Pattern.
  2012. * Adds an event listener to the observable object.
  2013. * First argument should be the name of the event you wish to listen for.
  2014. * Second argument should be the object that wants to be notified in case
  2015. * the event occurs.
  2016. *
  2017. * @param string $eventname event identifier
  2018. * @param Observer $observer observer instance
  2019. *
  2020. * @return void
  2021. */
  2022. public function addEventListener( $eventname, Observer $observer )
  2023. {
  2024. if ( !isset( $this->observers[$eventname] ) ) {
  2025. $this->observers[$eventname] = array();
  2026. }
  2027. foreach ( $this->observers[$eventname] as $o ) {
  2028. if ( $o == $observer ) {
  2029. return;
  2030. }
  2031. }
  2032. $this->observers[$eventname][] = $observer;
  2033. }
  2034. /**
  2035. * Notifies listeners.
  2036. * Sends the signal $eventname, the event identifier and a message object
  2037. * to all observers that have been registered to receive notification for
  2038. * this event. Part of the observer pattern implementation in RedBeanPHP.
  2039. *
  2040. * @param string $eventname event you want signal
  2041. * @param mixed $info message object to send along
  2042. *
  2043. * @return void
  2044. */
  2045. public function signal( $eventname, $info )
  2046. {
  2047. if ( !isset( $this->observers[$eventname] ) ) {
  2048. $this->observers[$eventname] = array();
  2049. }
  2050. foreach ( $this->observers[$eventname] as $observer ) {
  2051. $observer->onEvent( $eventname, $info );
  2052. }
  2053. }
  2054. }
  2055. }
  2056. namespace RedBeanPHP {
  2057. /**
  2058. * Observer
  2059. * Interface for Observer object. Implementation of the
  2060. * observer pattern.
  2061. *
  2062. * @file RedBean/Observer.php
  2063. * @desc Part of the observer pattern in RedBean
  2064. * @author Gabor de Mooijand the RedBeanPHP community
  2065. * @license BSD/GPLv2
  2066. *
  2067. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  2068. * This source file is subject to the BSD/GPLv2 License that is bundled
  2069. * with this source code in the file license.txt.
  2070. */
  2071. interface Observer
  2072. {
  2073. /**
  2074. * An observer object needs to be capable of receiving
  2075. * notifications. Therefore the observer needs to implement the
  2076. * onEvent method with two parameters, the event identifier specifying the
  2077. * current event and a message object (in RedBeanPHP this can also be a bean).
  2078. *
  2079. * @param string $eventname event identifier
  2080. * @param mixed $bean a message sent along with the notification
  2081. *
  2082. * @return void
  2083. */
  2084. public function onEvent( $eventname, $bean );
  2085. }
  2086. }
  2087. namespace RedBeanPHP {
  2088. /**
  2089. * Adapter Interface
  2090. *
  2091. * @file RedBean/Adapter.php
  2092. * @desc Describes the API for a RedBean Database Adapter.
  2093. * @author Gabor de Mooij and the RedBeanPHP Community
  2094. * @license BSD/GPLv2
  2095. *
  2096. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  2097. * This source file is subject to the BSD/GPLv2 License that is bundled
  2098. * with this source code in the file license.txt.
  2099. */
  2100. interface Adapter
  2101. {
  2102. /**
  2103. * Returns the latest SQL statement
  2104. *
  2105. * @return string
  2106. */
  2107. public function getSQL();
  2108. /**
  2109. * Executes an SQL Statement using an array of values to bind
  2110. * If $noevent is TRUE then this function will not signal its
  2111. * observers to notify about the SQL execution; this to prevent
  2112. * infinite recursion when using observers.
  2113. *
  2114. * @param string $sql SQL
  2115. * @param array $bindings values
  2116. * @param boolean $noevent no event firing
  2117. */
  2118. public function exec( $sql, $bindings = array(), $noevent = FALSE );
  2119. /**
  2120. * Executes an SQL Query and returns a resultset.
  2121. * This method returns a multi dimensional resultset similar to getAll
  2122. * The values array can be used to bind values to the place holders in the
  2123. * SQL query.
  2124. *
  2125. * @param string $sql SQL
  2126. * @param array $bindings values
  2127. *
  2128. * @return array
  2129. */
  2130. public function get( $sql, $bindings = array() );
  2131. /**
  2132. * Executes an SQL Query and returns a resultset.
  2133. * This method returns a single row (one array) resultset.
  2134. * The values array can be used to bind values to the place holders in the
  2135. * SQL query.
  2136. *
  2137. * @param string $sql SQL
  2138. * @param array $bindings values to bind
  2139. *
  2140. * @return array
  2141. */
  2142. public function getRow( $sql, $bindings = array() );
  2143. /**
  2144. * Executes an SQL Query and returns a resultset.
  2145. * This method returns a single column (one array) resultset.
  2146. * The values array can be used to bind values to the place holders in the
  2147. * SQL query.
  2148. *
  2149. * @param string $sql SQL
  2150. * @param array $bindings values to bind
  2151. *
  2152. * @return array
  2153. */
  2154. public function getCol( $sql, $bindings = array() );
  2155. /**
  2156. * Executes an SQL Query and returns a resultset.
  2157. * This method returns a single cell, a scalar value as the resultset.
  2158. * The values array can be used to bind values to the place holders in the
  2159. * SQL query.
  2160. *
  2161. * @param string $sql SQL
  2162. * @param array $bindings values to bind
  2163. *
  2164. * @return string
  2165. */
  2166. public function getCell( $sql, $bindings = array() );
  2167. /**
  2168. * Executes the SQL query specified in $sql and takes
  2169. * the first two columns of the resultset. This function transforms the
  2170. * resultset into an associative array. Values from the the first column will
  2171. * serve as keys while the values of the second column will be used as values.
  2172. * The values array can be used to bind values to the place holders in the
  2173. * SQL query.
  2174. *
  2175. * @param string $sql SQL
  2176. * @param array $bindings values to bind
  2177. *
  2178. * @return array
  2179. */
  2180. public function getAssoc( $sql, $bindings = array() );
  2181. /**
  2182. * Executes the SQL query specified in $sql and indexes
  2183. * the row by the first column.
  2184. *
  2185. * @param string $sql SQL
  2186. * @param array $bindings values to bind
  2187. *
  2188. * @return array
  2189. */
  2190. public function getAssocRow( $sql, $bindings = array() );
  2191. /**
  2192. * Returns the latest insert ID.
  2193. *
  2194. * @return integer
  2195. */
  2196. public function getInsertID();
  2197. /**
  2198. * Returns the number of rows that have been
  2199. * affected by the last update statement.
  2200. *
  2201. * @return integer
  2202. */
  2203. public function getAffectedRows();
  2204. /**
  2205. * Returns the original database resource. This is useful if you want to
  2206. * perform operations on the driver directly instead of working with the
  2207. * adapter. RedBean will only access the adapter and never to talk
  2208. * directly to the driver though.
  2209. *
  2210. * @return object
  2211. */
  2212. public function getDatabase();
  2213. /**
  2214. * This method is part of the RedBean Transaction Management
  2215. * mechanisms.
  2216. * Starts a transaction.
  2217. *
  2218. * @return void
  2219. */
  2220. public function startTransaction();
  2221. /**
  2222. * This method is part of the RedBean Transaction Management
  2223. * mechanisms.
  2224. * Commits the transaction.
  2225. *
  2226. * @return void
  2227. */
  2228. public function commit();
  2229. /**
  2230. * This method is part of the RedBean Transaction Management
  2231. * mechanisms.
  2232. * Rolls back the transaction.
  2233. *
  2234. * @return void
  2235. */
  2236. public function rollback();
  2237. /**
  2238. * Closes database connection.
  2239. *
  2240. * @return void
  2241. */
  2242. public function close();
  2243. }
  2244. }
  2245. namespace RedBeanPHP\Adapter {
  2246. use RedBeanPHP\Observable as Observable;
  2247. use RedBeanPHP\Adapter as Adapter;
  2248. use RedBeanPHP\Driver as Driver;
  2249. /**
  2250. * DBAdapter (Database Adapter)
  2251. *
  2252. * @file RedBean/Adapter/DBAdapter.php
  2253. * @desc An adapter class to connect various database systems to RedBean
  2254. * @author Gabor de Mooij and the RedBeanPHP Community.
  2255. * @license BSD/GPLv2
  2256. *
  2257. * Database Adapter Class.
  2258. *
  2259. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community.
  2260. * This source file is subject to the BSD/GPLv2 License that is bundled
  2261. * with this source code in the file license.txt.
  2262. */
  2263. class DBAdapter extends Observable implements Adapter
  2264. {
  2265. /**
  2266. * @var Driver
  2267. */
  2268. private $db = NULL;
  2269. /**
  2270. * @var string
  2271. */
  2272. private $sql = '';
  2273. /**
  2274. * Constructor.
  2275. *
  2276. * Creates an instance of the RedBean Adapter Class.
  2277. * This class provides an interface for RedBean to work
  2278. * with ADO compatible DB instances.
  2279. *
  2280. * @param Driver $database ADO Compatible DB Instance
  2281. */
  2282. public function __construct( $database )
  2283. {
  2284. $this->db = $database;
  2285. }
  2286. /**
  2287. * @see Adapter::getSQL
  2288. */
  2289. public function getSQL()
  2290. {
  2291. return $this->sql;
  2292. }
  2293. /**
  2294. * @see Adapter::exec
  2295. */
  2296. public function exec( $sql, $bindings = array(), $noevent = FALSE )
  2297. {
  2298. if ( !$noevent ) {
  2299. $this->sql = $sql;
  2300. $this->signal( 'sql_exec', $this );
  2301. }
  2302. return $this->db->Execute( $sql, $bindings );
  2303. }
  2304. /**
  2305. * @see Adapter::get
  2306. */
  2307. public function get( $sql, $bindings = array() )
  2308. {
  2309. $this->sql = $sql;
  2310. $this->signal( 'sql_exec', $this );
  2311. return $this->db->GetAll( $sql, $bindings );
  2312. }
  2313. /**
  2314. * @see Adapter::getRow
  2315. */
  2316. public function getRow( $sql, $bindings = array() )
  2317. {
  2318. $this->sql = $sql;
  2319. $this->signal( 'sql_exec', $this );
  2320. return $this->db->GetRow( $sql, $bindings );
  2321. }
  2322. /**
  2323. * @see Adapter::getCol
  2324. */
  2325. public function getCol( $sql, $bindings = array() )
  2326. {
  2327. $this->sql = $sql;
  2328. $this->signal( 'sql_exec', $this );
  2329. return $this->db->GetCol( $sql, $bindings );
  2330. }
  2331. /**
  2332. * @see Adapter::getAssoc
  2333. */
  2334. public function getAssoc( $sql, $bindings = array() )
  2335. {
  2336. $this->sql = $sql;
  2337. $this->signal( 'sql_exec', $this );
  2338. $rows = $this->db->GetAll( $sql, $bindings );
  2339. $assoc = array();
  2340. if ( !$rows ) {
  2341. return $assoc;
  2342. }
  2343. foreach ( $rows as $row ) {
  2344. if ( empty( $row ) ) continue;
  2345. if ( count( $row ) > 1 ) {
  2346. $key = array_shift( $row );
  2347. $value = array_shift( $row );
  2348. } else {
  2349. $key = array_shift( $row );
  2350. $value = $key;
  2351. }
  2352. $assoc[$key] = $value;
  2353. }
  2354. return $assoc;
  2355. }
  2356. /**
  2357. * @see Adapter::getAssocRow
  2358. */
  2359. public function getAssocRow($sql, $bindings = array())
  2360. {
  2361. $this->sql = $sql;
  2362. $this->signal( 'sql_exec', $this );
  2363. return $this->db->GetAssocRow( $sql, $bindings );
  2364. }
  2365. /**
  2366. * @see Adapter::getCell
  2367. */
  2368. public function getCell( $sql, $bindings = array(), $noSignal = NULL )
  2369. {
  2370. $this->sql = $sql;
  2371. if ( !$noSignal ) $this->signal( 'sql_exec', $this );
  2372. $arr = $this->db->getCol( $sql, $bindings );
  2373. if ( $arr && is_array( $arr ) && isset( $arr[0] ) ) {
  2374. return ( $arr[0] );
  2375. }
  2376. return NULL;
  2377. }
  2378. /**
  2379. * @see Adapter::getInsertID
  2380. */
  2381. public function getInsertID()
  2382. {
  2383. return $this->db->getInsertID();
  2384. }
  2385. /**
  2386. * @see Adapter::getAffectedRows
  2387. */
  2388. public function getAffectedRows()
  2389. {
  2390. return $this->db->Affected_Rows();
  2391. }
  2392. /**
  2393. * @see Adapter::getDatabase
  2394. */
  2395. public function getDatabase()
  2396. {
  2397. return $this->db;
  2398. }
  2399. /**
  2400. * @see Adapter::startTransaction
  2401. */
  2402. public function startTransaction()
  2403. {
  2404. $this->db->StartTrans();
  2405. }
  2406. /**
  2407. * @see Adapter::commit
  2408. */
  2409. public function commit()
  2410. {
  2411. $this->db->CommitTrans();
  2412. }
  2413. /**
  2414. * @see Adapter::rollback
  2415. */
  2416. public function rollback()
  2417. {
  2418. $this->db->FailTrans();
  2419. }
  2420. /**
  2421. * @see Adapter::close.
  2422. */
  2423. public function close()
  2424. {
  2425. $this->db->close();
  2426. }
  2427. }
  2428. }
  2429. namespace RedBeanPHP {
  2430. /**
  2431. * QueryWriter
  2432. * Interface for QueryWriters
  2433. *
  2434. * @file RedBean/QueryWriter.php
  2435. * @desc Describes the API for a QueryWriter
  2436. * @author Gabor de Mooij and the RedBeanPHP community
  2437. * @license BSD/GPLv2
  2438. *
  2439. * Notes:
  2440. * - Whenever you see a parameter called $table or $type you should always
  2441. * be aware of the fact that this argument contains a Bean Type string, not the
  2442. * actual table name. These raw type names are passed to safeTable() to obtain the
  2443. * actual name of the database table. Don't let the names confuse you $type/$table
  2444. * refers to Bean Type, not physical database table names!
  2445. * - This is the interface for FLUID database drivers. Drivers intended to support
  2446. * just FROZEN mode should implement the IceWriter instead.
  2447. *
  2448. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  2449. * This source file is subject to the BSD/GPLv2 License that is bundled
  2450. * with this source code in the file license.txt.
  2451. */
  2452. interface QueryWriter
  2453. {
  2454. /**
  2455. * Query Writer constants.
  2456. */
  2457. const C_SQLSTATE_NO_SUCH_TABLE = 1;
  2458. const C_SQLSTATE_NO_SUCH_COLUMN = 2;
  2459. const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;
  2460. /**
  2461. * Define data type regions
  2462. *
  2463. * 00 - 80: normal data types
  2464. * 80 - 99: special data types, only scan/code if requested
  2465. * 99 : specified by user, don't change
  2466. */
  2467. const C_DATATYPE_RANGE_SPECIAL = 80;
  2468. const C_DATATYPE_RANGE_SPECIFIED = 99;
  2469. /**
  2470. * Define GLUE types for use with glueSQLCondition methods.
  2471. * Determines how to prefix a snippet of SQL before appending it
  2472. * to other SQL (or integrating it, mixing it otherwise).
  2473. *
  2474. * WHERE - glue as WHERE condition
  2475. * AND - glue as AND condition
  2476. */
  2477. const C_GLUE_WHERE = 1;
  2478. const C_GLUE_AND = 2;
  2479. /**
  2480. * Glues an SQL snippet to the beginning of a WHERE clause.
  2481. * This ensures users don't have to add WHERE to their query snippets.
  2482. *
  2483. * The snippet gets prefixed with WHERE or AND
  2484. * if it starts with a condition.
  2485. *
  2486. * If the snippet does NOT start with a condition (or this function thinks so)
  2487. * the snippet is returned as-is.
  2488. *
  2489. * The GLUE type determines the prefix:
  2490. *
  2491. * - NONE prefixes with WHERE
  2492. * - WHERE prefixes with WHERE and replaces AND if snippets starts with AND
  2493. * - AND prefixes with AND
  2494. *
  2495. * This method will never replace WHERE with AND since a snippet should never
  2496. * begin with WHERE in the first place. OR is not supported.
  2497. *
  2498. * Only a limited set of clauses will be recognized as non-conditions.
  2499. * For instance beginning a snippet with complex statements like JOIN or UNION
  2500. * will not work. This is too complex for use in a snippet.
  2501. *
  2502. * @param string $sql SQL Snippet
  2503. * @param integer $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND)
  2504. *
  2505. * @return string
  2506. */
  2507. public function glueSQLCondition( $sql, $glue = NULL );
  2508. /**
  2509. * Returns the tables that are in the database.
  2510. *
  2511. * @return array
  2512. */
  2513. public function getTables();
  2514. /**
  2515. * This method will create a table for the bean.
  2516. * This methods accepts a type and infers the corresponding table name.
  2517. *
  2518. * @param string $type type of bean you want to create a table for
  2519. *
  2520. * @return void
  2521. */
  2522. public function createTable( $type );
  2523. /**
  2524. * Returns an array containing all the columns of the specified type.
  2525. * The format of the return array looks like this:
  2526. * $field => $type where $field is the name of the column and $type
  2527. * is a database specific description of the datatype.
  2528. *
  2529. * This methods accepts a type and infers the corresponding table name.
  2530. *
  2531. * @param string $type type of bean you want to obtain a column list of
  2532. *
  2533. * @return array
  2534. */
  2535. public function getColumns( $type );
  2536. /**
  2537. * Returns the Column Type Code (integer) that corresponds
  2538. * to the given value type. This method is used to determine the minimum
  2539. * column type required to represent the given value.
  2540. *
  2541. * @param string $value value
  2542. *
  2543. * @return integer
  2544. */
  2545. public function scanType( $value, $alsoScanSpecialForTypes = FALSE );
  2546. /**
  2547. * This method will add a column to a table.
  2548. * This methods accepts a type and infers the corresponding table name.
  2549. *
  2550. * @param string $type name of the table
  2551. * @param string $column name of the column
  2552. * @param integer $field data type for field
  2553. *
  2554. * @return void
  2555. */
  2556. public function addColumn( $type, $column, $field );
  2557. /**
  2558. * Returns the Type Code for a Column Description.
  2559. * Given an SQL column description this method will return the corresponding
  2560. * code for the writer. If the include specials flag is set it will also
  2561. * return codes for special columns. Otherwise special columns will be identified
  2562. * as specified columns.
  2563. *
  2564. * @param string $typedescription description
  2565. * @param boolean $includeSpecials whether you want to get codes for special columns as well
  2566. *
  2567. * @return integer
  2568. */
  2569. public function code( $typedescription, $includeSpecials = FALSE );
  2570. /**
  2571. * This method will widen the column to the specified data type.
  2572. * This methods accepts a type and infers the corresponding table name.
  2573. *
  2574. * @param string $type type / table that needs to be adjusted
  2575. * @param string $column column that needs to be altered
  2576. * @param integer $datatype target data type
  2577. *
  2578. * @return void
  2579. */
  2580. public function widenColumn( $type, $column, $datatype );
  2581. /**
  2582. * Selects records from the database.
  2583. * This methods selects the records from the database that match the specified
  2584. * type, conditions (optional) and additional SQL snippet (optional).
  2585. *
  2586. * @param string $type name of the table you want to query
  2587. * @param array $conditions criteria ( $column => array( $values ) )
  2588. * @param string $addSQL additional SQL snippet
  2589. * @param array $bindings bindings for SQL snippet
  2590. *
  2591. * @return array
  2592. */
  2593. public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
  2594. /**
  2595. * Returns records through an intermediate type. This method is used to obtain records using a link table and
  2596. * allows the SQL snippets to reference columns in the link table for additional filtering or ordering.
  2597. *
  2598. * @param string $sourceType source type, the reference type you want to use to fetch related items on the other side
  2599. * @param string $destType destination type, the target type you want to get beans of
  2600. * @param mixed $linkID ID to use for the link table
  2601. * @param string $addSql Additional SQL snippet
  2602. * @param array $bindings Bindings for SQL snippet
  2603. *
  2604. * @return array
  2605. */
  2606. public function queryRecordRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() );
  2607. /**
  2608. * Returns the row that links $sourceType $sourcID to $destType $destID in an N-M relation.
  2609. *
  2610. * @param string $sourceType source type, the first part of the link you're looking for
  2611. * @param string $destType destination type, the second part of the link you're looking for
  2612. * @param string $sourceID ID for the source
  2613. * @param string $destID ID for the destination
  2614. *
  2615. * @return array|null
  2616. */
  2617. public function queryRecordLink( $sourceType, $destType, $sourceID, $destID );
  2618. /**
  2619. * Counts the number of records in the database that match the
  2620. * conditions and additional SQL.
  2621. *
  2622. * @param string $type name of the table you want to query
  2623. * @param array $conditions criteria ( $column => array( $values ) )
  2624. * @param string $addSQL additional SQL snippet
  2625. * @param array $bindings bindings for SQL snippet
  2626. *
  2627. * @return integer
  2628. */
  2629. public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
  2630. /**
  2631. * Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$bindings.
  2632. *
  2633. * @param string $sourceType source type
  2634. * @param string $targetType the thing you want to count
  2635. * @param mixed $linkID the of the source type
  2636. * @param string $addSQL additional SQL snippet
  2637. * @param array $bindings bindings for SQL snippet
  2638. *
  2639. * @return integer
  2640. */
  2641. public function queryRecordCountRelated( $sourceType, $targetType, $linkID, $addSQL = '', $bindings = array() );
  2642. /**
  2643. * This method should update (or insert a record), it takes
  2644. * a table name, a list of update values ( $field => $value ) and an
  2645. * primary key ID (optional). If no primary key ID is provided, an
  2646. * INSERT will take place.
  2647. * Returns the new ID.
  2648. * This methods accepts a type and infers the corresponding table name.
  2649. *
  2650. * @param string $type name of the table to update
  2651. * @param array $updatevalues list of update values
  2652. * @param integer $id optional primary key ID value
  2653. *
  2654. * @return integer
  2655. */
  2656. public function updateRecord( $type, $updatevalues, $id = NULL );
  2657. /**
  2658. * Deletes records from the database.
  2659. * @note $addSql is always prefixed with ' WHERE ' or ' AND .'
  2660. *
  2661. * @param string $type name of the table you want to query
  2662. * @param array $conditions criteria ( $column => array( $values ) )
  2663. * @param string $sql additional SQL
  2664. * @param array $bindings bindings
  2665. *
  2666. * @return void
  2667. */
  2668. public function deleteRecord( $type, $conditions = array(), $addSql = '', $bindings = array() );
  2669. /**
  2670. * Deletes all links between $sourceType and $destType in an N-M relation.
  2671. *
  2672. * @param string $sourceType source type
  2673. * @param string $destType destination type
  2674. * @param string $sourceID source ID
  2675. *
  2676. * @return void
  2677. */
  2678. public function deleteRelations( $sourceType, $destType, $sourceID );
  2679. /**
  2680. * This method will add a UNIQUE constraint index to a table on columns $columns.
  2681. * This methods accepts a type and infers the corresponding table name.
  2682. *
  2683. * @param string $type type
  2684. * @param array $columnsPartOfIndex columns to include in index
  2685. *
  2686. * @return void
  2687. */
  2688. public function addUniqueIndex( $type, $columns );
  2689. /**
  2690. * This method will check whether the SQL state is in the list of specified states
  2691. * and returns TRUE if it does appear in this list or FALSE if it
  2692. * does not. The purpose of this method is to translate the database specific state to
  2693. * a one of the constants defined in this class and then check whether it is in the list
  2694. * of standard states provided.
  2695. *
  2696. * @param string $state sql state
  2697. * @param array $list list
  2698. *
  2699. * @return boolean
  2700. */
  2701. public function sqlStateIn( $state, $list );
  2702. /**
  2703. * This method will remove all beans of a certain type.
  2704. * This methods accepts a type and infers the corresponding table name.
  2705. *
  2706. * @param string $type bean type
  2707. *
  2708. * @return void
  2709. */
  2710. public function wipe( $type );
  2711. /**
  2712. * Given two types this method will add a foreign key constraint.
  2713. *
  2714. * @param string $sourceType source type
  2715. * @param string $destType destination type
  2716. *
  2717. * @return void
  2718. */
  2719. public function addConstraintForTypes( $sourceType, $destType );
  2720. /**
  2721. * This method will add a foreign key from type and field to
  2722. * target type and target field.
  2723. * The foreign key is created without an action. On delete/update
  2724. * no action will be triggered. The FK is only used to allow database
  2725. * tools to generate pretty diagrams and to make it easy to add actions
  2726. * later on.
  2727. * This methods accepts a type and infers the corresponding table name.
  2728. *
  2729. *
  2730. * @param string $type type that will have a foreign key field
  2731. * @param string $targetType points to this type
  2732. * @param string $field field that contains the foreign key value
  2733. * @param string $targetField field where the fk points to
  2734. * @param string $isDep whether target is dependent and should cascade on update/delete
  2735. *
  2736. * @return void
  2737. */
  2738. public function addFK( $type, $targetType, $field, $targetField, $isDep = false );
  2739. /**
  2740. * This method will add an index to a type and field with name
  2741. * $name.
  2742. * This methods accepts a type and infers the corresponding table name.
  2743. *
  2744. * @param string $type type to add index to
  2745. * @param string $name name of the new index
  2746. * @param string $column field to index
  2747. *
  2748. * @return void
  2749. */
  2750. public function addIndex( $type, $name, $column );
  2751. /**
  2752. * Checks and filters a database structure element like a table of column
  2753. * for safe use in a query. A database structure has to conform to the
  2754. * RedBeanPHP DB security policy which basically means only alphanumeric
  2755. * symbols are allowed. This security policy is more strict than conventional
  2756. * SQL policies and does therefore not require database specific escaping rules.
  2757. *
  2758. * @param string $databaseStructure name of the column/table to check
  2759. * @param boolean $noQuotes TRUE to NOT put backticks or quotes around the string
  2760. *
  2761. * @return string
  2762. */
  2763. public function esc( $databaseStructure, $dontQuote = FALSE );
  2764. /**
  2765. * Removes all tables and views from the database.
  2766. *
  2767. * @return void
  2768. */
  2769. public function wipeAll();
  2770. /**
  2771. * Renames an association. For instance if you would like to refer to
  2772. * album_song as: track you can specify this by calling this method like:
  2773. *
  2774. * renameAssociation('album_song','track')
  2775. *
  2776. * This allows:
  2777. *
  2778. * $album->sharedSong
  2779. *
  2780. * to add/retrieve beans from track instead of album_song.
  2781. * Also works for exportAll().
  2782. *
  2783. * This method also accepts a single associative array as
  2784. * its first argument.
  2785. *
  2786. * @param string|array $from
  2787. * @param string $to (optional)
  2788. *
  2789. * @return void
  2790. */
  2791. public function renameAssocTable( $from, $to = NULL );
  2792. /**
  2793. * Returns the format for link tables.
  2794. * Given an array containing two type names this method returns the
  2795. * name of the link table to be used to store and retrieve
  2796. * association records.
  2797. *
  2798. * @param array $types two types array($type1, $type2)
  2799. *
  2800. * @return string
  2801. */
  2802. public function getAssocTable( $types );
  2803. }
  2804. }
  2805. namespace RedBeanPHP\QueryWriter {
  2806. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  2807. use RedBeanPHP\RedException as RedException;
  2808. use RedBeanPHP\QueryWriter as QueryWriter;
  2809. use RedBeanPHP\OODBBean as OODBBean;
  2810. /**
  2811. * RedBean Abstract Query Writer
  2812. *
  2813. * @file RedBean/QueryWriter/AQueryWriter.php
  2814. * @desc Query Writer (abstract class)
  2815. * @author Gabor de Mooij and the RedBeanPHP Community
  2816. * @license BSD/GPLv2
  2817. *
  2818. * Represents an abstract Database to RedBean
  2819. * To write a driver for a different database for RedBean
  2820. * Contains a number of functions all implementors can
  2821. * inherit or override.
  2822. *
  2823. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  2824. * This source file is subject to the BSD/GPLv2 License that is bundled
  2825. * with this source code in the file license.txt.
  2826. */
  2827. abstract class AQueryWriter { //bracket must be here - otherwise coverage software does not understand.
  2828. /**
  2829. * @var DBAdapter
  2830. */
  2831. protected $adapter;
  2832. /**
  2833. * @var string
  2834. */
  2835. protected $defaultValue = 'NULL';
  2836. /**
  2837. * @var string
  2838. */
  2839. protected $quoteCharacter = '';
  2840. /**
  2841. * @var boolean
  2842. */
  2843. protected $flagUseCache = TRUE;
  2844. /**
  2845. * @var array
  2846. */
  2847. protected $cache = array();
  2848. /**
  2849. * @var array
  2850. */
  2851. public static $renames = array();
  2852. /**
  2853. * @var array
  2854. */
  2855. public $typeno_sqltype = array();
  2856. /**
  2857. * Clears renames.
  2858. *
  2859. * @return void
  2860. */
  2861. public static function clearRenames()
  2862. {
  2863. self::$renames = array();
  2864. }
  2865. /**
  2866. * Generates a list of parameters (slots) for an SQL snippet.
  2867. * This method calculates the correct number of slots to insert in the
  2868. * SQL snippet and determines the correct type of slot. If the bindings
  2869. * array contains named parameters this method will return named ones and
  2870. * update the keys in the value list accordingly (that's why we use the &).
  2871. *
  2872. * If you pass an offset the bindings will be re-added to the value list.
  2873. * Some databases cant handle duplicate parameter names in queries.
  2874. *
  2875. * @param array &$valueList list of values to generate slots for (gets modified if needed)
  2876. * @param array $otherBindings list of additional bindings
  2877. * @param integer $offset start counter at...
  2878. *
  2879. * @return string
  2880. */
  2881. protected function getParametersForInClause( &$valueList, $otherBindings, $offset = 0 )
  2882. {
  2883. if ( is_array( $otherBindings ) && count( $otherBindings ) > 0 ) {
  2884. reset( $otherBindings );
  2885. $key = key( $otherBindings );
  2886. if ( !is_numeric($key) ) {
  2887. $filler = array();
  2888. $newList = (!$offset) ? array() : $valueList;
  2889. $counter = $offset;
  2890. foreach( $valueList as $value ) {
  2891. $slot = ':slot' . ( $counter++ );
  2892. $filler[] = $slot;
  2893. $newList[$slot] = $value;
  2894. }
  2895. // Change the keys!
  2896. $valueList = $newList;
  2897. return implode( ',', $filler );
  2898. }
  2899. }
  2900. return implode( ',', array_fill( 0, count( $valueList ), '?' ) );
  2901. }
  2902. /**
  2903. * Returns a cache key for the cache values passed.
  2904. * This method returns a fingerprint string to be used as a key to store
  2905. * data in the writer cache.
  2906. *
  2907. * @param array $keyValues key-value to generate key for
  2908. *
  2909. * @return string
  2910. */
  2911. private function getCacheKey( $keyValues )
  2912. {
  2913. return json_encode( $keyValues );
  2914. }
  2915. /**
  2916. * Returns the values associated with the provided cache tag and key.
  2917. *
  2918. * @param string $cacheTag cache tag to use for lookup
  2919. * @param string $key key to use for lookup
  2920. *
  2921. * @return mixed
  2922. */
  2923. private function getCached( $cacheTag, $key )
  2924. {
  2925. $sql = $this->adapter->getSQL();
  2926. if ($this->updateCache()) {
  2927. if ( isset( $this->cache[$cacheTag][$key] ) ) {
  2928. return $this->cache[$cacheTag][$key];
  2929. }
  2930. }
  2931. return NULL;
  2932. }
  2933. /**
  2934. * Checks if the previous query had a keep-cache tag.
  2935. * If so, the cache will persist, otherwise the cache will be flushed.
  2936. *
  2937. * Returns TRUE if the cache will remain and FALSE if a flush has
  2938. * been performed.
  2939. *
  2940. * @return boolean
  2941. */
  2942. private function updateCache()
  2943. {
  2944. $sql = $this->adapter->getSQL();
  2945. if ( strpos( $sql, '-- keep-cache' ) !== strlen( $sql ) - 13 ) {
  2946. // If SQL has been taken place outside of this method then something else then
  2947. // a select query might have happened! (or instruct to keep cache)
  2948. $this->cache = array();
  2949. return FALSE;
  2950. }
  2951. return TRUE;
  2952. }
  2953. /**
  2954. * Stores data from the writer in the cache under a specific key and cache tag.
  2955. * A cache tag is used to make sure the cache remains consistent. In most cases the cache tag
  2956. * will be the bean type, this makes sure queries associated with a certain reference type will
  2957. * never contain conflicting data.
  2958. * You can only store one item under a cache tag. Why not use the cache tag as a key? Well
  2959. * we need to make sure the cache contents fits the key (and key is based on the cache values).
  2960. * Otherwise it would be possible to store two different result sets under the same key (the cache tag).
  2961. *
  2962. * @param string $cacheTag cache tag (secondary key)
  2963. * @param string $key key
  2964. * @param array $values content to be stored
  2965. *
  2966. * @return void
  2967. */
  2968. private function putResultInCache( $cacheTag, $key, $values )
  2969. {
  2970. $this->cache[$cacheTag] = array(
  2971. $key => $values
  2972. );
  2973. }
  2974. /**
  2975. * Creates an SQL snippet from a list of conditions of format:
  2976. *
  2977. * array(
  2978. * key => array(
  2979. * value1, value2, value3 ....
  2980. * )
  2981. * )
  2982. *
  2983. * @param array $conditions list of conditions
  2984. * @param array $bindings parameter bindings for SQL snippet
  2985. * @param string $addSql SQL snippet
  2986. *
  2987. * @return string
  2988. */
  2989. private function makeSQLFromConditions( $conditions, &$bindings, $addSql = '' )
  2990. {
  2991. reset( $bindings );
  2992. $firstKey = key( $bindings );
  2993. $paramTypeIsNum = ( is_numeric( $firstKey ) );
  2994. $counter = 0;
  2995. $sqlConditions = array();
  2996. foreach ( $conditions as $column => $values ) {
  2997. if ( !count( $values ) ) continue;
  2998. $sql = $this->esc( $column );
  2999. $sql .= ' IN ( ';
  3000. if ( !is_array( $values ) ) $values = array( $values );
  3001. // If it's safe to skip bindings, do so...
  3002. if ( ctype_digit( implode( '', $values ) ) ) {
  3003. $sql .= implode( ',', $values ) . ' ) ';
  3004. // only numeric, cant do much harm
  3005. $sqlConditions[] = $sql;
  3006. } else {
  3007. if ( $paramTypeIsNum ) {
  3008. $sql .= implode( ',', array_fill( 0, count( $values ), '?' ) ) . ' ) ';
  3009. array_unshift($sqlConditions, $sql);
  3010. foreach ( $values as $k => $v ) {
  3011. $values[$k] = strval( $v );
  3012. array_unshift( $bindings, $v );
  3013. }
  3014. } else {
  3015. $slots = array();
  3016. foreach( $values as $k => $v ) {
  3017. $slot = ':slot'.$counter++;
  3018. $slots[] = $slot;
  3019. $bindings[$slot] = strval( $v );
  3020. }
  3021. $sql .= implode( ',', $slots ).' ) ';
  3022. $sqlConditions[] = $sql;
  3023. }
  3024. }
  3025. }
  3026. $sql = '';
  3027. if ( is_array( $sqlConditions ) && count( $sqlConditions ) > 0 ) {
  3028. $sql = implode( ' AND ', $sqlConditions );
  3029. $sql = " WHERE ( $sql ) ";
  3030. if ( $addSql ) $sql .= $addSql;
  3031. } elseif ( $addSql ) {
  3032. $sql = $addSql;
  3033. }
  3034. return $sql;
  3035. }
  3036. /**
  3037. * Returns the table names and column names for a relational query.
  3038. *
  3039. * @param string $sourceType type of the source bean
  3040. * @param string $destType type of the bean you want to obtain using the relation
  3041. * @param boolean $noQuote TRUE if you want to omit quotes
  3042. *
  3043. * @return array
  3044. */
  3045. private function getRelationalTablesAndColumns( $sourceType, $destType, $noQuote = FALSE )
  3046. {
  3047. $linkTable = $this->esc( $this->getAssocTable( array( $sourceType, $destType ) ), $noQuote );
  3048. $sourceCol = $this->esc( $sourceType . '_id', $noQuote );
  3049. if ( $sourceType === $destType ) {
  3050. $destCol = $this->esc( $destType . '2_id', $noQuote );
  3051. } else {
  3052. $destCol = $this->esc( $destType . '_id', $noQuote );
  3053. }
  3054. $sourceTable = $this->esc( $sourceType, $noQuote );
  3055. $destTable = $this->esc( $destType, $noQuote );
  3056. return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol );
  3057. }
  3058. /**
  3059. * Adds a data type to the list of data types.
  3060. * Use this method to add a new column type definition to the writer.
  3061. * Used for UUID support.
  3062. *
  3063. * @param integer $dataTypeID magic number constant assigned to this data type
  3064. * @param string $SQLDefinition SQL column definition (i.e. INT(11))
  3065. *
  3066. * @return self
  3067. */
  3068. protected function addDataType( $dataTypeID, $SQLDefinition )
  3069. {
  3070. $this->typeno_sqltype[ $dataTypeID ] = $SQLDefinition;
  3071. $this->sqltype_typeno[ $SQLDefinition ] = $dataTypeID;
  3072. return $this;
  3073. }
  3074. /**
  3075. * Returns the sql that should follow an insert statement.
  3076. *
  3077. * @param string $table name
  3078. *
  3079. * @return string
  3080. */
  3081. protected function getInsertSuffix( $table )
  3082. {
  3083. return '';
  3084. }
  3085. /**
  3086. * Checks whether a value starts with zeros. In this case
  3087. * the value should probably be stored using a text datatype instead of a
  3088. * numerical type in order to preserve the zeros.
  3089. *
  3090. * @param string $value value to be checked.
  3091. *
  3092. * @return boolean
  3093. */
  3094. protected function startsWithZeros( $value )
  3095. {
  3096. $value = strval( $value );
  3097. if ( strlen( $value ) > 1 && strpos( $value, '0' ) === 0 && strpos( $value, '0.' ) !== 0 ) {
  3098. return TRUE;
  3099. } else {
  3100. return FALSE;
  3101. }
  3102. }
  3103. /**
  3104. * Inserts a record into the database using a series of insert columns
  3105. * and corresponding insertvalues. Returns the insert id.
  3106. *
  3107. * @param string $table table to perform query on
  3108. * @param array $insertcolumns columns to be inserted
  3109. * @param array $insertvalues values to be inserted
  3110. *
  3111. * @return integer
  3112. */
  3113. protected function insertRecord( $type, $insertcolumns, $insertvalues )
  3114. {
  3115. $default = $this->defaultValue;
  3116. $suffix = $this->getInsertSuffix( $type );
  3117. $table = $this->esc( $type );
  3118. if ( count( $insertvalues ) > 0 && is_array( $insertvalues[0] ) && count( $insertvalues[0] ) > 0 ) {
  3119. foreach ( $insertcolumns as $k => $v ) {
  3120. $insertcolumns[$k] = $this->esc( $v );
  3121. }
  3122. $insertSQL = "INSERT INTO $table ( id, " . implode( ',', $insertcolumns ) . " ) VALUES
  3123. ( $default, " . implode( ',', array_fill( 0, count( $insertcolumns ), ' ? ' ) ) . " ) $suffix";
  3124. $ids = array();
  3125. foreach ( $insertvalues as $i => $insertvalue ) {
  3126. $ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i );
  3127. }
  3128. $result = count( $ids ) === 1 ? array_pop( $ids ) : $ids;
  3129. } else {
  3130. $result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix" );
  3131. }
  3132. if ( $suffix ) return $result;
  3133. $last_id = $this->adapter->getInsertID();
  3134. return $last_id;
  3135. }
  3136. /**
  3137. * Checks table name or column name.
  3138. *
  3139. * @param string $table table string
  3140. *
  3141. * @return string
  3142. *
  3143. * @throws Security
  3144. */
  3145. protected function check( $struct )
  3146. {
  3147. if ( !preg_match( '/^[a-zA-Z0-9_]+$/', $struct ) ) {
  3148. throw new RedException( 'Identifier does not conform to RedBeanPHP security policies.' );
  3149. }
  3150. return $struct;
  3151. }
  3152. /**
  3153. * Checks whether a number can be treated like an int.
  3154. *
  3155. * @param string $value string representation of a certain value
  3156. *
  3157. * @return boolean
  3158. */
  3159. public static function canBeTreatedAsInt( $value )
  3160. {
  3161. return (bool) ( ctype_digit( strval( $value ) ) && strval( $value ) === strval( intval( $value ) ) );
  3162. }
  3163. /**
  3164. * @see QueryWriter::getAssocTableFormat
  3165. */
  3166. public static function getAssocTableFormat( $types )
  3167. {
  3168. sort( $types );
  3169. $assoc = implode( '_', $types );
  3170. return ( isset( self::$renames[$assoc] ) ) ? self::$renames[$assoc] : $assoc;
  3171. }
  3172. /**
  3173. * @see QueryWriter::renameAssociation
  3174. */
  3175. public static function renameAssociation( $from, $to = NULL )
  3176. {
  3177. if ( is_array( $from ) ) {
  3178. foreach ( $from as $key => $value ) self::$renames[$key] = $value;
  3179. return;
  3180. }
  3181. self::$renames[$from] = $to;
  3182. }
  3183. /**
  3184. * Globally available service method for RedBeanPHP.
  3185. * Converts a camel cased string to a snake cased string.
  3186. *
  3187. * @param string $camel a camelCased string
  3188. *
  3189. * @return string
  3190. */
  3191. public static function camelsSnake( $camel )
  3192. {
  3193. return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) );
  3194. }
  3195. /**
  3196. * Checks whether the specified type (i.e. table) already exists in the database.
  3197. * Not part of the Object Database interface!
  3198. *
  3199. * @param string $table table name
  3200. *
  3201. * @return boolean
  3202. */
  3203. public function tableExists( $table )
  3204. {
  3205. $tables = $this->getTables();
  3206. return in_array( $table, $tables );
  3207. }
  3208. /**
  3209. * @see QueryWriter::glueSQLCondition
  3210. */
  3211. public function glueSQLCondition( $sql, $glue = NULL )
  3212. {
  3213. static $snippetCache = array();
  3214. if ( trim( $sql ) === '' ) {
  3215. return $sql;
  3216. }
  3217. $key = $glue . '|' . $sql;
  3218. if ( isset( $snippetCache[$key] ) ) {
  3219. return $snippetCache[$key];
  3220. }
  3221. $lsql = ltrim( $sql );
  3222. if ( preg_match( '/^(INNER|LEFT|RIGHT|JOIN|AND|OR|WHERE|ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', $lsql ) ) {
  3223. if ( $glue === QueryWriter::C_GLUE_WHERE && stripos( $lsql, 'AND' ) === 0 ) {
  3224. $snippetCache[$key] = ' WHERE ' . substr( $lsql, 3 );
  3225. } else {
  3226. $snippetCache[$key] = $sql;
  3227. }
  3228. } else {
  3229. $snippetCache[$key] = ( ( $glue === QueryWriter::C_GLUE_AND ) ? ' AND ' : ' WHERE ') . $sql;
  3230. }
  3231. return $snippetCache[$key];
  3232. }
  3233. /**
  3234. * @see QueryWriter::esc
  3235. */
  3236. public function esc( $dbStructure, $dontQuote = FALSE )
  3237. {
  3238. $this->check( $dbStructure );
  3239. return ( $dontQuote ) ? $dbStructure : $this->quoteCharacter . $dbStructure . $this->quoteCharacter;
  3240. }
  3241. /**
  3242. * @see QueryWriter::addColumn
  3243. */
  3244. public function addColumn( $type, $column, $field )
  3245. {
  3246. $table = $type;
  3247. $type = $field;
  3248. $table = $this->esc( $table );
  3249. $column = $this->esc( $column );
  3250. $type = ( isset( $this->typeno_sqltype[$type] ) ) ? $this->typeno_sqltype[$type] : '';
  3251. $this->adapter->exec( "ALTER TABLE $table ADD $column $type " );
  3252. }
  3253. /**
  3254. * @see QueryWriter::updateRecord
  3255. */
  3256. public function updateRecord( $type, $updatevalues, $id = NULL )
  3257. {
  3258. $table = $type;
  3259. if ( !$id ) {
  3260. $insertcolumns = $insertvalues = array();
  3261. foreach ( $updatevalues as $pair ) {
  3262. $insertcolumns[] = $pair['property'];
  3263. $insertvalues[] = $pair['value'];
  3264. }
  3265. //Otherwise psql returns string while MySQL/SQLite return numeric causing problems with additions (array_diff)
  3266. return (string) $this->insertRecord( $table, $insertcolumns, array( $insertvalues ) );
  3267. }
  3268. if ( $id && !count( $updatevalues ) ) {
  3269. return $id;
  3270. }
  3271. $table = $this->esc( $table );
  3272. $sql = "UPDATE $table SET ";
  3273. $p = $v = array();
  3274. foreach ( $updatevalues as $uv ) {
  3275. $p[] = " {$this->esc( $uv["property"] )} = ? ";
  3276. $v[] = $uv['value'];
  3277. }
  3278. $sql .= implode( ',', $p ) . ' WHERE id = ? ';
  3279. $v[] = $id;
  3280. $this->adapter->exec( $sql, $v );
  3281. return $id;
  3282. }
  3283. /**
  3284. * @see QueryWriter::queryRecord
  3285. */
  3286. public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
  3287. {
  3288. $addSql = $this->glueSQLCondition( $addSql, ( count($conditions) > 0) ? QueryWriter::C_GLUE_AND : NULL );
  3289. $key = NULL;
  3290. if ( $this->flagUseCache ) {
  3291. $key = $this->getCacheKey( array( $conditions, $addSql, $bindings, 'select' ) );
  3292. if ( $cached = $this->getCached( $type, $key ) ) {
  3293. return $cached;
  3294. }
  3295. }
  3296. $table = $this->esc( $type );
  3297. $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
  3298. $sql = "SELECT * FROM {$table} {$sql} -- keep-cache";
  3299. $rows = $this->adapter->get( $sql, $bindings );
  3300. if ( $this->flagUseCache && $key ) {
  3301. $this->putResultInCache( $type, $key, $rows );
  3302. }
  3303. return $rows;
  3304. }
  3305. /**
  3306. * @see QueryWriter::queryRecordRelated
  3307. */
  3308. public function queryRecordRelated( $sourceType, $destType, $linkIDs, $addSql = '', $bindings = array() )
  3309. {
  3310. $addSql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
  3311. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  3312. $key = $this->getCacheKey( array( $sourceType, $destType, implode( ',', $linkIDs ), $addSql, $bindings ) );
  3313. if ( $this->flagUseCache && $cached = $this->getCached( $destType, $key ) ) {
  3314. return $cached;
  3315. }
  3316. $inClause = $this->getParametersForInClause( $linkIDs, $bindings );
  3317. if ( $sourceType === $destType ) {
  3318. $inClause2 = $this->getParametersForInClause( $linkIDs, $bindings, count( $bindings ) ); //for some databases
  3319. $sql = "
  3320. SELECT
  3321. {$destTable}.*,
  3322. COALESCE(
  3323. NULLIF({$linkTable}.{$sourceCol}, {$destTable}.id),
  3324. NULLIF({$linkTable}.{$destCol}, {$destTable}.id)) AS linked_by
  3325. FROM {$linkTable}
  3326. INNER JOIN {$destTable} ON
  3327. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) ) OR
  3328. ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} IN ($inClause2) )
  3329. {$addSql}
  3330. -- keep-cache";
  3331. $linkIDs = array_merge( $linkIDs, $linkIDs );
  3332. } else {
  3333. $sql = "
  3334. SELECT
  3335. {$destTable}.*,
  3336. {$linkTable}.{$sourceCol} AS linked_by
  3337. FROM {$linkTable}
  3338. INNER JOIN {$destTable} ON
  3339. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) )
  3340. {$addSql}
  3341. -- keep-cache";
  3342. }
  3343. $bindings = array_merge( $linkIDs, $bindings );
  3344. $rows = $this->adapter->get( $sql, $bindings );
  3345. $this->putResultInCache( $destType, $key, $rows );
  3346. return $rows;
  3347. }
  3348. /**
  3349. * @see QueryWriter::queryRecordLink
  3350. */
  3351. public function queryRecordLink( $sourceType, $destType, $sourceID, $destID )
  3352. {
  3353. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  3354. $key = $this->getCacheKey( array( $sourceType, $destType, $sourceID, $destID ) );
  3355. if ( $this->flagUseCache && $cached = $this->getCached( $linkTable, $key ) ) {
  3356. return $cached;
  3357. }
  3358. if ( $sourceTable === $destTable ) {
  3359. $sql = "SELECT {$linkTable}.* FROM {$linkTable}
  3360. WHERE ( {$sourceCol} = ? AND {$destCol} = ? ) OR
  3361. ( {$destCol} = ? AND {$sourceCol} = ? ) -- keep-cache";
  3362. $row = $this->adapter->getRow( $sql, array( $sourceID, $destID, $sourceID, $destID ) );
  3363. } else {
  3364. $sql = "SELECT {$linkTable}.* FROM {$linkTable}
  3365. WHERE {$sourceCol} = ? AND {$destCol} = ? -- keep-cache";
  3366. $row = $this->adapter->getRow( $sql, array( $sourceID, $destID ) );
  3367. }
  3368. $this->putResultInCache( $linkTable, $key, $row );
  3369. return $row;
  3370. }
  3371. /**
  3372. * @see QueryWriter::queryRecordCount
  3373. */
  3374. public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
  3375. {
  3376. $addSql = $this->glueSQLCondition( $addSql );
  3377. $table = $this->esc( $type );
  3378. $this->updateCache(); //check if cache chain has been broken
  3379. $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
  3380. $sql = "SELECT COUNT(*) FROM {$table} {$sql} -- keep-cache";
  3381. return (int) $this->adapter->getCell( $sql, $bindings );
  3382. }
  3383. /**
  3384. * @see QueryWriter::queryRecordCountRelated
  3385. */
  3386. public function queryRecordCountRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() )
  3387. {
  3388. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  3389. $this->updateCache(); //check if cache chain has been broken
  3390. if ( $sourceType === $destType ) {
  3391. $sql = "
  3392. SELECT COUNT(*) FROM {$linkTable}
  3393. INNER JOIN {$destTable} ON
  3394. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? ) OR
  3395. ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} = ? )
  3396. {$addSql}
  3397. -- keep-cache";
  3398. $bindings = array_merge( array( $linkID, $linkID ), $bindings );
  3399. } else {
  3400. $sql = "
  3401. SELECT COUNT(*) FROM {$linkTable}
  3402. INNER JOIN {$destTable} ON
  3403. ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? )
  3404. {$addSql}
  3405. -- keep-cache";
  3406. $bindings = array_merge( array( $linkID ), $bindings );
  3407. }
  3408. return (int) $this->adapter->getCell( $sql, $bindings );
  3409. }
  3410. /**
  3411. * @see QueryWriter::deleteRecord
  3412. */
  3413. public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
  3414. {
  3415. $addSql = $this->glueSQLCondition( $addSql );
  3416. $table = $this->esc( $type );
  3417. $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
  3418. $sql = "DELETE FROM {$table} {$sql}";
  3419. $this->adapter->exec( $sql, $bindings );
  3420. }
  3421. /**
  3422. * @see QueryWriter::deleteRelations
  3423. */
  3424. public function deleteRelations( $sourceType, $destType, $sourceID )
  3425. {
  3426. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
  3427. if ( $sourceTable === $destTable ) {
  3428. $sql = "DELETE FROM {$linkTable}
  3429. WHERE ( {$sourceCol} = ? ) OR
  3430. ( {$destCol} = ? )
  3431. ";
  3432. $this->adapter->exec( $sql, array( $sourceID, $sourceID ) );
  3433. } else {
  3434. $sql = "DELETE FROM {$linkTable}
  3435. WHERE {$sourceCol} = ? ";
  3436. $this->adapter->exec( $sql, array( $sourceID ) );
  3437. }
  3438. }
  3439. /**
  3440. * @see QueryWriter::widenColumn
  3441. */
  3442. public function widenColumn( $type, $column, $datatype )
  3443. {
  3444. if ( !isset($this->typeno_sqltype[$datatype]) ) return;
  3445. $table = $type;
  3446. $type = $datatype;
  3447. $table = $this->esc( $table );
  3448. $column = $this->esc( $column );
  3449. $newtype = $this->typeno_sqltype[$type];
  3450. $this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newtype " );
  3451. }
  3452. /**
  3453. * @see QueryWriter::wipe
  3454. */
  3455. public function wipe( $type )
  3456. {
  3457. $table = $this->esc( $type );
  3458. $this->adapter->exec( "TRUNCATE $table " );
  3459. }
  3460. /**
  3461. * @see QueryWriter::renameAssocTable
  3462. */
  3463. public function renameAssocTable( $from, $to = NULL )
  3464. {
  3465. self::renameAssociation( $from, $to );
  3466. }
  3467. /**
  3468. * @see QueryWriter::getAssocTable
  3469. */
  3470. public function getAssocTable( $types )
  3471. {
  3472. return self::getAssocTableFormat( $types );
  3473. }
  3474. /**
  3475. * @see QueryWriter::addConstraintForTypes
  3476. */
  3477. public function addConstraintForTypes( $sourceType, $destType )
  3478. {
  3479. list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType, TRUE );
  3480. return $this->constrain( $linkTable, $sourceTable, $destTable, $sourceCol, $destCol );
  3481. }
  3482. /**
  3483. * Turns caching on or off. Default: off.
  3484. * If caching is turned on retrieval queries fired after eachother will
  3485. * use a result row cache.
  3486. *
  3487. * @param boolean
  3488. */
  3489. public function setUseCache( $yesNo )
  3490. {
  3491. $this->flushCache();
  3492. $this->flagUseCache = (bool) $yesNo;
  3493. }
  3494. /**
  3495. * Flushes the Query Writer Cache.
  3496. *
  3497. * @return void
  3498. */
  3499. public function flushCache()
  3500. {
  3501. $this->cache = array();
  3502. }
  3503. /**
  3504. * @deprecated Use esc() instead.
  3505. *
  3506. * @param string $column column to be escaped
  3507. * @param boolean $noQuotes omit quotes
  3508. *
  3509. * @return string
  3510. */
  3511. public function safeColumn( $column, $noQuotes = FALSE )
  3512. {
  3513. return $this->esc( $column, $noQuotes );
  3514. }
  3515. /**
  3516. * @deprecated Use esc() instead.
  3517. *
  3518. * @param string $table table to be escaped
  3519. * @param boolean $noQuotes omit quotes
  3520. *
  3521. * @return string
  3522. */
  3523. public function safeTable( $table, $noQuotes = FALSE )
  3524. {
  3525. return $this->esc( $table, $noQuotes );
  3526. }
  3527. }
  3528. }
  3529. namespace RedBeanPHP\QueryWriter {
  3530. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  3531. use RedBeanPHP\QueryWriter as QueryWriter;
  3532. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  3533. use RedBeanPHP\Adapter as Adapter;
  3534. /**
  3535. * RedBean MySQLWriter
  3536. *
  3537. * @file RedBean/QueryWriter/MySQL.php
  3538. * @desc Represents a MySQL Database to RedBean
  3539. * @author Gabor de Mooij and the RedBeanPHP Community
  3540. * @license BSD/GPLv2
  3541. *
  3542. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3543. * This source file is subject to the BSD/GPLv2 License that is bundled
  3544. * with this source code in the file license.txt.
  3545. */
  3546. class MySQL extends AQueryWriter implements QueryWriter
  3547. {
  3548. /**
  3549. * Data types
  3550. */
  3551. const C_DATATYPE_BOOL = 0;
  3552. const C_DATATYPE_UINT32 = 2;
  3553. const C_DATATYPE_DOUBLE = 3;
  3554. const C_DATATYPE_TEXT8 = 4;
  3555. const C_DATATYPE_TEXT16 = 5;
  3556. const C_DATATYPE_TEXT32 = 6;
  3557. const C_DATATYPE_SPECIAL_DATE = 80;
  3558. const C_DATATYPE_SPECIAL_DATETIME = 81;
  3559. const C_DATATYPE_SPECIAL_POINT = 90;
  3560. const C_DATATYPE_SPECIFIED = 99;
  3561. /**
  3562. * @var DBAdapter
  3563. */
  3564. protected $adapter;
  3565. /**
  3566. * @var string
  3567. */
  3568. protected $quoteCharacter = '`';
  3569. /**
  3570. * Add the constraints for a specific database driver: MySQL.
  3571. *
  3572. * @param string $table table table to add constrains to
  3573. * @param string $table1 table1 first reference table
  3574. * @param string $table2 table2 second reference table
  3575. * @param string $property1 property1 first column
  3576. * @param string $property2 property2 second column
  3577. *
  3578. * @return boolean $succes whether the constraint has been applied
  3579. */
  3580. protected function constrain( $table, $table1, $table2, $property1, $property2 )
  3581. {
  3582. try {
  3583. $db = $this->adapter->getCell( 'SELECT database()' );
  3584. $fks = $this->adapter->getCell(
  3585. "SELECT count(*)
  3586. FROM information_schema.KEY_COLUMN_USAGE
  3587. WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND
  3588. CONSTRAINT_NAME <>'PRIMARY' AND REFERENCED_TABLE_NAME IS NOT NULL",
  3589. array( $db, $table )
  3590. );
  3591. // already foreign keys added in this association table
  3592. if ( $fks > 0 ) {
  3593. return FALSE;
  3594. }
  3595. $columns = $this->getColumns( $table );
  3596. $idType = $this->getTypeForID();
  3597. if ( $this->code( $columns[$property1] ) !== $idType ) {
  3598. $this->widenColumn( $table, $property1, $idType );
  3599. }
  3600. if ( $this->code( $columns[$property2] ) !== $idType ) {
  3601. $this->widenColumn( $table, $property2, $idType );
  3602. }
  3603. $sql = "
  3604. ALTER TABLE " . $this->esc( $table ) . "
  3605. ADD FOREIGN KEY($property1) references `$table1`(id) ON DELETE CASCADE ON UPDATE CASCADE;
  3606. ";
  3607. $this->adapter->exec( $sql );
  3608. $sql = "
  3609. ALTER TABLE " . $this->esc( $table ) . "
  3610. ADD FOREIGN KEY($property2) references `$table2`(id) ON DELETE CASCADE ON UPDATE CASCADE
  3611. ";
  3612. $this->adapter->exec( $sql );
  3613. return TRUE;
  3614. } catch ( \Exception $e ) {
  3615. return FALSE;
  3616. }
  3617. }
  3618. /**
  3619. * Constructor
  3620. *
  3621. * @param Adapter $adapter Database Adapter
  3622. */
  3623. public function __construct( Adapter $adapter )
  3624. {
  3625. $this->typeno_sqltype = array(
  3626. MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ',
  3627. MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ',
  3628. MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ',
  3629. MySQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ',
  3630. MySQL::C_DATATYPE_TEXT16 => ' TEXT ',
  3631. MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ',
  3632. MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ',
  3633. MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
  3634. MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ',
  3635. );
  3636. $this->sqltype_typeno = array();
  3637. foreach ( $this->typeno_sqltype as $k => $v ) {
  3638. $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
  3639. }
  3640. $this->adapter = $adapter;
  3641. $this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
  3642. }
  3643. /**
  3644. * This method returns the datatype to be used for primary key IDS and
  3645. * foreign keys. Returns one if the data type constants.
  3646. *
  3647. * @return integer $const data type to be used for IDS.
  3648. */
  3649. public function getTypeForID()
  3650. {
  3651. return self::C_DATATYPE_UINT32;
  3652. }
  3653. /**
  3654. * @see QueryWriter::getTables
  3655. */
  3656. public function getTables()
  3657. {
  3658. return $this->adapter->getCol( 'show tables' );
  3659. }
  3660. /**
  3661. * @see QueryWriter::createTable
  3662. */
  3663. public function createTable( $table )
  3664. {
  3665. $table = $this->esc( $table );
  3666. $encoding = $this->adapter->getDatabase()->getMysqlEncoding();
  3667. $sql = "CREATE TABLE $table (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET={$encoding} COLLATE={$encoding}_unicode_ci ";
  3668. $this->adapter->exec( $sql );
  3669. }
  3670. /**
  3671. * @see QueryWriter::getColumns
  3672. */
  3673. public function getColumns( $table )
  3674. {
  3675. $columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
  3676. $columns = array();
  3677. foreach ( $columnsRaw as $r ) {
  3678. $columns[$r['Field']] = $r['Type'];
  3679. }
  3680. return $columns;
  3681. }
  3682. /**
  3683. * @see QueryWriter::scanType
  3684. */
  3685. public function scanType( $value, $flagSpecial = FALSE )
  3686. {
  3687. $this->svalue = $value;
  3688. if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
  3689. if ( $flagSpecial ) {
  3690. if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
  3691. return MySQL::C_DATATYPE_SPECIAL_DATE;
  3692. }
  3693. if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
  3694. return MySQL::C_DATATYPE_SPECIAL_DATETIME;
  3695. }
  3696. }
  3697. $value = strval( $value );
  3698. if ( !$this->startsWithZeros( $value ) ) {
  3699. if ( $value === TRUE || $value === FALSE || $value === '1' || $value === '' || $value === '0') {
  3700. return MySQL::C_DATATYPE_BOOL;
  3701. }
  3702. if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
  3703. return MySQL::C_DATATYPE_UINT32;
  3704. }
  3705. if ( is_numeric( $value ) ) {
  3706. return MySQL::C_DATATYPE_DOUBLE;
  3707. }
  3708. }
  3709. if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
  3710. return MySQL::C_DATATYPE_TEXT8;
  3711. }
  3712. if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
  3713. return MySQL::C_DATATYPE_TEXT16;
  3714. }
  3715. return MySQL::C_DATATYPE_TEXT32;
  3716. }
  3717. /**
  3718. * @see QueryWriter::code
  3719. */
  3720. public function code( $typedescription, $includeSpecials = FALSE )
  3721. {
  3722. if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
  3723. $r = $this->sqltype_typeno[$typedescription];
  3724. } else {
  3725. $r = self::C_DATATYPE_SPECIFIED;
  3726. }
  3727. if ( $includeSpecials ) {
  3728. return $r;
  3729. }
  3730. if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
  3731. return self::C_DATATYPE_SPECIFIED;
  3732. }
  3733. return $r;
  3734. }
  3735. /**
  3736. * @see QueryWriter::addUniqueIndex
  3737. */
  3738. public function addUniqueIndex( $table, $columns )
  3739. {
  3740. $table = $this->esc( $table );
  3741. sort( $columns ); // Else we get multiple indexes due to order-effects
  3742. foreach ( $columns as $k => $v ) {
  3743. $columns[$k] = $this->esc( $v );
  3744. }
  3745. $r = $this->adapter->get( "SHOW INDEX FROM $table" );
  3746. $name = 'UQ_' . sha1( implode( ',', $columns ) );
  3747. if ( $r ) {
  3748. foreach ( $r as $i ) {
  3749. if ( $i['Key_name'] == $name ) {
  3750. return;
  3751. }
  3752. }
  3753. }
  3754. try {
  3755. $sql = "ALTER TABLE $table
  3756. ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
  3757. } catch ( \Exception $e ) {
  3758. //do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
  3759. }
  3760. $this->adapter->exec( $sql );
  3761. }
  3762. /**
  3763. * @see QueryWriter::addIndex
  3764. */
  3765. public function addIndex( $type, $name, $column )
  3766. {
  3767. $table = $type;
  3768. $table = $this->esc( $table );
  3769. $name = preg_replace( '/\W/', '', $name );
  3770. $column = $this->esc( $column );
  3771. try {
  3772. foreach ( $this->adapter->get( "SHOW INDEX FROM $table " ) as $ind ) if ( $ind['Key_name'] === $name ) return;
  3773. $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
  3774. } catch (\Exception $e ) {
  3775. }
  3776. }
  3777. /**
  3778. * @see QueryWriter::addFK
  3779. */
  3780. public function addFK( $type, $targetType, $field, $targetField, $isDependent = FALSE )
  3781. {
  3782. $db = $this->adapter->getCell( 'SELECT DATABASE()' );
  3783. $cfks = $this->adapter->getCell('
  3784. SELECT CONSTRAINT_NAME
  3785. FROM information_schema.KEY_COLUMN_USAGE
  3786. WHERE
  3787. TABLE_SCHEMA = ?
  3788. AND TABLE_NAME = ?
  3789. AND COLUMN_NAME = ? AND
  3790. CONSTRAINT_NAME != \'PRIMARY\'
  3791. AND REFERENCED_TABLE_NAME IS NOT NULL
  3792. ', array($db, $type, $field));
  3793. if ($cfks) return;
  3794. try {
  3795. $fkName = 'fk_'.($type.'_'.$field);
  3796. $cName = 'c_'.$fkName;
  3797. $this->adapter->exec( "
  3798. ALTER TABLE {$this->esc($type)}
  3799. ADD CONSTRAINT $cName
  3800. FOREIGN KEY $fkName ( {$this->esc($field)} ) REFERENCES {$this->esc($targetType)} (
  3801. {$this->esc($targetField)}) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
  3802. } catch (\Exception $e ) {
  3803. // Failure of fk-constraints is not a problem
  3804. }
  3805. }
  3806. /**
  3807. * @see QueryWriter::sqlStateIn
  3808. */
  3809. public function sqlStateIn( $state, $list )
  3810. {
  3811. $stateMap = array(
  3812. '42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  3813. '42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  3814. '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
  3815. );
  3816. return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
  3817. }
  3818. /**
  3819. * @see QueryWriter::wipeAll
  3820. */
  3821. public function wipeAll()
  3822. {
  3823. $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
  3824. foreach ( $this->getTables() as $t ) {
  3825. try {
  3826. $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
  3827. } catch (\Exception $e ) {
  3828. }
  3829. try {
  3830. $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" );
  3831. } catch (\Exception $e ) {
  3832. }
  3833. }
  3834. $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
  3835. }
  3836. }
  3837. }
  3838. namespace RedBeanPHP\QueryWriter {
  3839. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  3840. use RedBeanPHP\QueryWriter as QueryWriter;
  3841. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  3842. use RedBeanPHP\Adapter as Adapter;
  3843. /**
  3844. * RedBean SQLiteWriter with support for SQLite types
  3845. *
  3846. * @file RedBean/QueryWriter/SQLiteT.php
  3847. * @desc Represents a SQLite Database to RedBean
  3848. * @author Gabor de Mooij and the RedBeanPHP Community
  3849. * @license BSD/GPLv2
  3850. *
  3851. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  3852. * This source file is subject to the BSD/GPLv2 License that is bundled
  3853. * with this source code in the file license.txt.
  3854. */
  3855. class SQLiteT extends AQueryWriter implements QueryWriter
  3856. {
  3857. /**
  3858. * @var DBAdapter
  3859. */
  3860. protected $adapter;
  3861. /**
  3862. * @var string
  3863. */
  3864. protected $quoteCharacter = '`';
  3865. /**
  3866. * Data types
  3867. */
  3868. const C_DATATYPE_INTEGER = 0;
  3869. const C_DATATYPE_NUMERIC = 1;
  3870. const C_DATATYPE_TEXT = 2;
  3871. const C_DATATYPE_SPECIFIED = 99;
  3872. /**
  3873. * Gets all information about a table (from a type).
  3874. *
  3875. * Format:
  3876. * array(
  3877. * name => name of the table
  3878. * columns => array( name => datatype )
  3879. * indexes => array() raw index information rows from PRAGMA query
  3880. * keys => array() raw key information rows from PRAGMA query
  3881. * )
  3882. *
  3883. * @param string $type type you want to get info of
  3884. *
  3885. * @return array $info
  3886. */
  3887. protected function getTable( $type )
  3888. {
  3889. $tableName = $this->esc( $type, TRUE );
  3890. $columns = $this->getColumns( $type );
  3891. $indexes = $this->getIndexes( $type );
  3892. $keys = $this->getKeys( $type );
  3893. $table = array(
  3894. 'columns' => $columns,
  3895. 'indexes' => $indexes,
  3896. 'keys' => $keys,
  3897. 'name' => $tableName
  3898. );
  3899. $this->tableArchive[$tableName] = $table;
  3900. return $table;
  3901. }
  3902. /**
  3903. * Puts a table. Updates the table structure.
  3904. * In SQLite we can't change columns, drop columns, change or add foreign keys so we
  3905. * have a table-rebuild function. You simply load your table with getTable(), modify it and
  3906. * then store it with putTable()...
  3907. *
  3908. * @param array $tableMap information array
  3909. */
  3910. protected function putTable( $tableMap )
  3911. {
  3912. $table = $tableMap['name'];
  3913. $q = array();
  3914. $q[] = "DROP TABLE IF EXISTS tmp_backup;";
  3915. $oldColumnNames = array_keys( $this->getColumns( $table ) );
  3916. foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
  3917. $q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
  3918. $q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
  3919. $q[] = "PRAGMA foreign_keys = 0 ";
  3920. $q[] = "DROP TABLE `$table`;";
  3921. $newTableDefStr = '';
  3922. foreach ( $tableMap['columns'] as $column => $type ) {
  3923. if ( $column != 'id' ) {
  3924. $newTableDefStr .= ",`$column` $type";
  3925. }
  3926. }
  3927. $fkDef = '';
  3928. foreach ( $tableMap['keys'] as $key ) {
  3929. $fkDef .= ", FOREIGN KEY(`{$key['from']}`)
  3930. REFERENCES `{$key['table']}`(`{$key['to']}`)
  3931. ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
  3932. }
  3933. $q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
  3934. foreach ( $tableMap['indexes'] as $name => $index ) {
  3935. if ( strpos( $name, 'UQ_' ) === 0 ) {
  3936. $cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
  3937. foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
  3938. $q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
  3939. } else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
  3940. }
  3941. $q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
  3942. $q[] = "DROP TABLE tmp_backup;";
  3943. $q[] = "PRAGMA foreign_keys = 1 ";
  3944. foreach ( $q as $sq ) $this->adapter->exec( $sq );
  3945. }
  3946. /**
  3947. * Returns the indexes for type $type.
  3948. *
  3949. * @param string $type
  3950. *
  3951. * @return array $indexInfo index information
  3952. */
  3953. protected function getIndexes( $type )
  3954. {
  3955. $table = $this->esc( $type, TRUE );
  3956. $indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
  3957. $indexInfoList = array();
  3958. foreach ( $indexes as $i ) {
  3959. $indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
  3960. $indexInfoList[$i['name']]['unique'] = $i['unique'];
  3961. }
  3962. return $indexInfoList;
  3963. }
  3964. /**
  3965. * Returns the keys for type $type.
  3966. *
  3967. * @param string $type
  3968. *
  3969. * @return array $keysInfo keys information
  3970. */
  3971. protected function getKeys( $type )
  3972. {
  3973. $table = $this->esc( $type, TRUE );
  3974. $keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
  3975. $keyInfoList = array();
  3976. foreach ( $keys as $k ) {
  3977. $keyInfoList['from_' . $k['from'] . '_to_table_' . $k['table'] . '_col_' . $k['to']] = $k;
  3978. }
  3979. return $keyInfoList;
  3980. }
  3981. /**
  3982. * Adds a foreign key to a type
  3983. *
  3984. * @param string $type type you want to modify table of
  3985. * @param string $targetType target type
  3986. * @param string $field field of the type that needs to get the fk
  3987. * @param string $targetField field where the fk needs to point to
  3988. * @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE
  3989. *
  3990. * @return boolean $didIt
  3991. *
  3992. * @note: cant put this in try-catch because that can hide the fact
  3993. * that database has been damaged.
  3994. */
  3995. protected function buildFK( $type, $targetType, $field, $targetField, $constraint = FALSE )
  3996. {
  3997. $consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
  3998. $t = $this->getTable( $type );
  3999. $label = 'from_' . $field . '_to_table_' . $targetType . '_col_' . $targetField;
  4000. foreach($t['keys'] as $key) {
  4001. if ($key['from'] === $field) return FALSE;
  4002. }
  4003. $t['keys'][$label] = array(
  4004. 'table' => $targetType,
  4005. 'from' => $field,
  4006. 'to' => $targetField,
  4007. 'on_update' => $consSQL,
  4008. 'on_delete' => $consSQL
  4009. );
  4010. $this->putTable( $t );
  4011. return TRUE;
  4012. }
  4013. /**
  4014. * Add the constraints for a specific database driver: SQLite.
  4015. *
  4016. * @param string $table table to add fk constrains to
  4017. * @param string $table1 first reference table
  4018. * @param string $table2 second reference table
  4019. * @param string $property1 first reference column
  4020. * @param string $property2 second reference column
  4021. *
  4022. * @return boolean $success whether the constraint has been applied
  4023. */
  4024. protected function constrain( $table, $table1, $table2, $property1, $property2 )
  4025. {
  4026. $firstState = $this->buildFK( $table, $table1, $property1, 'id', TRUE );
  4027. $secondState = $this->buildFK( $table, $table2, $property2, 'id', TRUE );
  4028. return ( $firstState && $secondState );
  4029. }
  4030. /**
  4031. * Constructor
  4032. *
  4033. * @param Adapter $adapter Database Adapter
  4034. */
  4035. public function __construct( Adapter $adapter )
  4036. {
  4037. $this->typeno_sqltype = array(
  4038. SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
  4039. SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
  4040. SQLiteT::C_DATATYPE_TEXT => 'TEXT',
  4041. );
  4042. $this->sqltype_typeno = array();
  4043. foreach ( $this->typeno_sqltype as $k => $v ) {
  4044. $this->sqltype_typeno[$v] = $k;
  4045. }
  4046. $this->adapter = $adapter;
  4047. }
  4048. /**
  4049. * This method returns the datatype to be used for primary key IDS and
  4050. * foreign keys. Returns one if the data type constants.
  4051. *
  4052. * @return integer $const data type to be used for IDS.
  4053. */
  4054. public function getTypeForID()
  4055. {
  4056. return self::C_DATATYPE_INTEGER;
  4057. }
  4058. /**
  4059. * @see QueryWriter::scanType
  4060. */
  4061. public function scanType( $value, $flagSpecial = FALSE )
  4062. {
  4063. $this->svalue = $value;
  4064. if ( $value === FALSE ) return self::C_DATATYPE_INTEGER;
  4065. if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
  4066. if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
  4067. if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 ) return self::C_DATATYPE_INTEGER;
  4068. if ( ( is_numeric( $value ) && $value < 2147483648 )
  4069. || preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
  4070. || preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
  4071. ) {
  4072. return self::C_DATATYPE_NUMERIC;
  4073. }
  4074. return self::C_DATATYPE_TEXT;
  4075. }
  4076. /**
  4077. * @see QueryWriter::addColumn
  4078. */
  4079. public function addColumn( $table, $column, $type )
  4080. {
  4081. $column = $this->check( $column );
  4082. $table = $this->check( $table );
  4083. $type = $this->typeno_sqltype[$type];
  4084. $this->adapter->exec( "ALTER TABLE `$table` ADD `$column` $type " );
  4085. }
  4086. /**
  4087. * @see QueryWriter::code
  4088. */
  4089. public function code( $typedescription, $includeSpecials = FALSE )
  4090. {
  4091. $r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
  4092. return $r;
  4093. }
  4094. /**
  4095. * @see QueryWriter::widenColumn
  4096. */
  4097. public function widenColumn( $type, $column, $datatype )
  4098. {
  4099. $t = $this->getTable( $type );
  4100. $t['columns'][$column] = $this->typeno_sqltype[$datatype];
  4101. $this->putTable( $t );
  4102. }
  4103. /**
  4104. * @see QueryWriter::getTables();
  4105. */
  4106. public function getTables()
  4107. {
  4108. return $this->adapter->getCol( "SELECT name FROM sqlite_master
  4109. WHERE type='table' AND name!='sqlite_sequence';" );
  4110. }
  4111. /**
  4112. * @see QueryWriter::createTable
  4113. */
  4114. public function createTable( $table )
  4115. {
  4116. $table = $this->esc( $table );
  4117. $sql = "CREATE TABLE $table ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ";
  4118. $this->adapter->exec( $sql );
  4119. }
  4120. /**
  4121. * @see QueryWriter::getColumns
  4122. */
  4123. public function getColumns( $table )
  4124. {
  4125. $table = $this->esc( $table, TRUE );
  4126. $columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
  4127. $columns = array();
  4128. foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
  4129. return $columns;
  4130. }
  4131. /**
  4132. * @see QueryWriter::addUniqueIndex
  4133. */
  4134. public function addUniqueIndex( $type, $columns )
  4135. {
  4136. $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $columns );
  4137. $t = $this->getTable( $type );
  4138. if ( isset( $t['indexes'][$name] ) ) return;
  4139. $t['indexes'][$name] = array( 'name' => $name );
  4140. $this->putTable( $t );
  4141. }
  4142. /**
  4143. * @see QueryWriter::sqlStateIn
  4144. */
  4145. public function sqlStateIn( $state, $list )
  4146. {
  4147. $stateMap = array(
  4148. 'HY000' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  4149. '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
  4150. );
  4151. return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
  4152. }
  4153. /**
  4154. * @see QueryWriter::addIndex
  4155. */
  4156. public function addIndex( $type, $name, $column )
  4157. {
  4158. $table = $type;
  4159. $table = $this->esc( $table );
  4160. $name = preg_replace( '/\W/', '', $name );
  4161. $column = $this->esc( $column, TRUE );
  4162. try {
  4163. foreach ( $this->adapter->get( "PRAGMA INDEX_LIST($table) " ) as $ind ) {
  4164. if ( $ind['name'] === $name ) return;
  4165. }
  4166. $t = $this->getTable( $type );
  4167. $t['indexes'][$name] = array( 'name' => $column );
  4168. $this->putTable( $t );
  4169. } catch( \Exception $exception ) {
  4170. //do nothing
  4171. }
  4172. }
  4173. /**
  4174. * @see QueryWriter::wipe
  4175. */
  4176. public function wipe( $type )
  4177. {
  4178. $table = $this->esc( $type );
  4179. $this->adapter->exec( "DELETE FROM $table " );
  4180. }
  4181. /**
  4182. * @see QueryWriter::addFK
  4183. */
  4184. public function addFK( $type, $targetType, $field, $targetField, $isDep = FALSE )
  4185. {
  4186. return $this->buildFK( $type, $targetType, $field, $targetField, $isDep );
  4187. }
  4188. /**
  4189. * @see QueryWriter::wipeAll
  4190. */
  4191. public function wipeAll()
  4192. {
  4193. $this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
  4194. foreach ( $this->getTables() as $t ) {
  4195. try {
  4196. $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
  4197. } catch (\Exception $e ) {
  4198. }
  4199. try {
  4200. $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
  4201. } catch (\Exception $e ) {
  4202. }
  4203. }
  4204. $this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
  4205. }
  4206. }
  4207. }
  4208. namespace RedBeanPHP\QueryWriter {
  4209. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  4210. use RedBeanPHP\QueryWriter as QueryWriter;
  4211. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  4212. use RedBeanPHP\Adapter as Adapter;
  4213. /**
  4214. * RedBean PostgreSQL Query Writer
  4215. *
  4216. * @file RedBean/QueryWriter/PostgreSQL.php
  4217. * @desc QueryWriter for the PostgreSQL database system.
  4218. * @author Gabor de Mooij and the RedBeanPHP Community
  4219. * @license BSD/GPLv2
  4220. *
  4221. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  4222. * This source file is subject to the BSD/GPLv2 License that is bundled
  4223. * with this source code in the file license.txt.
  4224. */
  4225. class PostgreSQL extends AQueryWriter implements QueryWriter
  4226. {
  4227. /**
  4228. * Data types
  4229. */
  4230. const C_DATATYPE_INTEGER = 0;
  4231. const C_DATATYPE_DOUBLE = 1;
  4232. const C_DATATYPE_TEXT = 3;
  4233. const C_DATATYPE_SPECIAL_DATE = 80;
  4234. const C_DATATYPE_SPECIAL_DATETIME = 81;
  4235. const C_DATATYPE_SPECIAL_POINT = 90;
  4236. const C_DATATYPE_SPECIAL_LSEG = 91;
  4237. const C_DATATYPE_SPECIAL_CIRCLE = 92;
  4238. const C_DATATYPE_SPECIAL_MONEY = 93;
  4239. const C_DATATYPE_SPECIFIED = 99;
  4240. /**
  4241. * @var DBAdapter
  4242. */
  4243. protected $adapter;
  4244. /**
  4245. * @var string
  4246. */
  4247. protected $quoteCharacter = '"';
  4248. /**
  4249. * @var string
  4250. */
  4251. protected $defaultValue = 'DEFAULT';
  4252. /**
  4253. * Returns the insert suffix SQL Snippet
  4254. *
  4255. * @param string $table table
  4256. *
  4257. * @return string $sql SQL Snippet
  4258. */
  4259. protected function getInsertSuffix( $table )
  4260. {
  4261. return 'RETURNING id ';
  4262. }
  4263. /**
  4264. * Add the constraints for a specific database driver: PostgreSQL.
  4265. *
  4266. * @param string $table table to add fk constraints to
  4267. * @param string $table1 first reference table
  4268. * @param string $table2 second reference table
  4269. * @param string $property1 first reference column
  4270. * @param string $property2 second reference column
  4271. *
  4272. * @return boolean
  4273. */
  4274. protected function constrain( $table, $table1, $table2, $property1, $property2 )
  4275. {
  4276. try {
  4277. $adapter = $this->adapter;
  4278. $fkCode = 'fk' . md5( $table . $property1 . $property2 );
  4279. $sql = "SELECT c.oid, n.nspname, c.relname,
  4280. n2.nspname, c2.relname, cons.conname
  4281. FROM pg_class c
  4282. JOIN pg_namespace n ON n.oid = c.relnamespace
  4283. LEFT OUTER JOIN pg_constraint cons ON cons.conrelid = c.oid
  4284. LEFT OUTER JOIN pg_class c2 ON cons.confrelid = c2.oid
  4285. LEFT OUTER JOIN pg_namespace n2 ON n2.oid = c2.relnamespace
  4286. WHERE c.relkind = 'r'
  4287. AND n.nspname IN ('public')
  4288. AND (cons.contype = 'f' OR cons.contype IS NULL)
  4289. AND ( cons.conname = '{$fkCode}a' OR cons.conname = '{$fkCode}b' )
  4290. ";
  4291. $rows = $adapter->get( $sql );
  4292. if ( !count( $rows ) ) {
  4293. $sql1 = "ALTER TABLE \"$table\" ADD CONSTRAINT
  4294. {$fkCode}a FOREIGN KEY ($property1)
  4295. REFERENCES \"$table1\" (id) ON DELETE CASCADE ON UPDATE CASCADE ";
  4296. $sql2 = "ALTER TABLE \"$table\" ADD CONSTRAINT
  4297. {$fkCode}b FOREIGN KEY ($property2)
  4298. REFERENCES \"$table2\" (id) ON DELETE CASCADE ON UPDATE CASCADE ";
  4299. $adapter->exec( $sql1 );
  4300. $adapter->exec( $sql2 );
  4301. return TRUE;
  4302. }
  4303. return FALSE;
  4304. } catch (\Exception $e ) {
  4305. return FALSE;
  4306. }
  4307. }
  4308. /**
  4309. * Constructor
  4310. *
  4311. * @param Adapter $adapter Database Adapter
  4312. */
  4313. public function __construct( Adapter $adapter )
  4314. {
  4315. $this->typeno_sqltype = array(
  4316. self::C_DATATYPE_INTEGER => ' integer ',
  4317. self::C_DATATYPE_DOUBLE => ' double precision ',
  4318. self::C_DATATYPE_TEXT => ' text ',
  4319. self::C_DATATYPE_SPECIAL_DATE => ' date ',
  4320. self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
  4321. self::C_DATATYPE_SPECIAL_POINT => ' point ',
  4322. self::C_DATATYPE_SPECIAL_LSEG => ' lseg ',
  4323. self::C_DATATYPE_SPECIAL_CIRCLE => ' circle ',
  4324. self::C_DATATYPE_SPECIAL_MONEY => ' money ',
  4325. );
  4326. $this->sqltype_typeno = array();
  4327. foreach ( $this->typeno_sqltype as $k => $v ) {
  4328. $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
  4329. }
  4330. $this->adapter = $adapter;
  4331. }
  4332. /**
  4333. * This method returns the datatype to be used for primary key IDS and
  4334. * foreign keys. Returns one if the data type constants.
  4335. *
  4336. * @return integer $const data type to be used for IDS.
  4337. */
  4338. public function getTypeForID()
  4339. {
  4340. return self::C_DATATYPE_INTEGER;
  4341. }
  4342. /**
  4343. * @see QueryWriter::getTables
  4344. */
  4345. public function getTables()
  4346. {
  4347. return $this->adapter->getCol( "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'" );
  4348. }
  4349. /**
  4350. * @see QueryWriter::createTable
  4351. */
  4352. public function createTable( $table )
  4353. {
  4354. $table = $this->esc( $table );
  4355. $this->adapter->exec( " CREATE TABLE $table (id SERIAL PRIMARY KEY); " );
  4356. }
  4357. /**
  4358. * @see QueryWriter::getColumns
  4359. */
  4360. public function getColumns( $table )
  4361. {
  4362. $table = $this->esc( $table, TRUE );
  4363. $columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table'" );
  4364. $columns = array();
  4365. foreach ( $columnsRaw as $r ) {
  4366. $columns[$r['column_name']] = $r['data_type'];
  4367. }
  4368. return $columns;
  4369. }
  4370. /**
  4371. * @see QueryWriter::scanType
  4372. */
  4373. public function scanType( $value, $flagSpecial = FALSE )
  4374. {
  4375. $this->svalue = $value;
  4376. if ( $flagSpecial && $value ) {
  4377. if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
  4378. return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
  4379. }
  4380. if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
  4381. return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
  4382. }
  4383. if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
  4384. return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
  4385. }
  4386. if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
  4387. return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
  4388. }
  4389. if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
  4390. return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
  4391. }
  4392. if ( preg_match( '/^\-?\$[\d,\.]+$/', $value ) ) {
  4393. return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
  4394. }
  4395. }
  4396. $sz = ( $this->startsWithZeros( $value ) );
  4397. if ( $sz ) {
  4398. return self::C_DATATYPE_TEXT;
  4399. }
  4400. if ( $value === NULL || ( $value instanceof NULL ) || ( is_numeric( $value )
  4401. && floor( $value ) == $value
  4402. && $value < 2147483648
  4403. && $value > -2147483648 )
  4404. ) {
  4405. return self::C_DATATYPE_INTEGER;
  4406. } elseif ( is_numeric( $value ) ) {
  4407. return self::C_DATATYPE_DOUBLE;
  4408. } else {
  4409. return self::C_DATATYPE_TEXT;
  4410. }
  4411. }
  4412. /**
  4413. * @see QueryWriter::code
  4414. */
  4415. public function code( $typedescription, $includeSpecials = FALSE )
  4416. {
  4417. $r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
  4418. if ( $includeSpecials ) return $r;
  4419. if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
  4420. return self::C_DATATYPE_SPECIFIED;
  4421. }
  4422. return $r;
  4423. }
  4424. /**
  4425. * @see QueryWriter::widenColumn
  4426. */
  4427. public function widenColumn( $type, $column, $datatype )
  4428. {
  4429. $table = $type;
  4430. $type = $datatype;
  4431. $table = $this->esc( $table );
  4432. $column = $this->esc( $column );
  4433. $newtype = $this->typeno_sqltype[$type];
  4434. $this->adapter->exec( "ALTER TABLE $table \n\t ALTER COLUMN $column TYPE $newtype " );
  4435. }
  4436. /**
  4437. * @see QueryWriter::addUniqueIndex
  4438. */
  4439. public function addUniqueIndex( $table, $columns )
  4440. {
  4441. $table = $this->esc( $table, TRUE );
  4442. sort( $columns ); //else we get multiple indexes due to order-effects
  4443. foreach ( $columns as $k => $v ) {
  4444. $columns[$k] = $this->esc( $v );
  4445. }
  4446. $r = $this->adapter->get( "SELECT i.relname AS index_name
  4447. FROM pg_class t,pg_class i,pg_index ix,pg_attribute a
  4448. WHERE t.oid = ix.indrelid
  4449. AND i.oid = ix.indexrelid
  4450. AND a.attrelid = t.oid
  4451. AND a.attnum = ANY(ix.indkey)
  4452. AND t.relkind = 'r'
  4453. AND t.relname = '$table'
  4454. ORDER BY t.relname, i.relname;" );
  4455. $name = "UQ_" . sha1( $table . implode( ',', $columns ) );
  4456. if ( $r ) {
  4457. foreach ( $r as $i ) {
  4458. if ( strtolower( $i['index_name'] ) == strtolower( $name ) ) {
  4459. return;
  4460. }
  4461. }
  4462. }
  4463. $sql = "ALTER TABLE \"$table\"
  4464. ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
  4465. $this->adapter->exec( $sql );
  4466. }
  4467. /**
  4468. * @see QueryWriter::sqlStateIn
  4469. */
  4470. public function sqlStateIn( $state, $list )
  4471. {
  4472. $stateMap = array(
  4473. '42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  4474. '42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  4475. '23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
  4476. );
  4477. return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
  4478. }
  4479. /**
  4480. * @see QueryWriter::addIndex
  4481. */
  4482. public function addIndex( $type, $name, $column )
  4483. {
  4484. $table = $type;
  4485. $table = $this->esc( $table );
  4486. $name = preg_replace( '/\W/', '', $name );
  4487. $column = $this->esc( $column );
  4488. if ( $this->adapter->getCell( "SELECT COUNT(*) FROM pg_class WHERE relname = '$name'" ) ) {
  4489. return;
  4490. }
  4491. try {
  4492. $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
  4493. } catch (\Exception $e ) {
  4494. //do nothing
  4495. }
  4496. }
  4497. /**
  4498. * @see QueryWriter::addFK
  4499. */
  4500. public function addFK( $type, $targetType, $field, $targetField, $isDep = FALSE )
  4501. {
  4502. $db = $this->adapter->getCell( 'SELECT current_database()' );
  4503. $cfks = $this->adapter->getCell('
  4504. SELECT constraint_name
  4505. FROM information_schema.KEY_COLUMN_USAGE
  4506. WHERE
  4507. table_catalog = ?
  4508. AND table_schema = \'public\'
  4509. AND table_name = ?
  4510. AND column_name = ?
  4511. ', array($db, $type, $field));
  4512. try{
  4513. if (!$cfks) {
  4514. $delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
  4515. $this->adapter->exec( "ALTER TABLE {$this->esc($type)}
  4516. ADD FOREIGN KEY ( {$this->esc($field)} ) REFERENCES {$this->esc($targetType)} (
  4517. {$this->esc($targetField)}) ON DELETE $delRule ON UPDATE $delRule DEFERRABLE ;" );
  4518. }
  4519. } catch (\Exception $e ) {
  4520. return FALSE;
  4521. }
  4522. }
  4523. /**
  4524. * @see QueryWriter::wipeAll
  4525. */
  4526. public function wipeAll()
  4527. {
  4528. $this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
  4529. foreach ( $this->getTables() as $t ) {
  4530. $t = $this->esc( $t );
  4531. $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " );
  4532. }
  4533. $this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
  4534. }
  4535. }
  4536. }
  4537. namespace RedBeanPHP {
  4538. /**
  4539. * RedBean\Exception Base
  4540. *
  4541. * @file RedBean/Exception.php
  4542. * @desc Represents the base class for RedBean\Exceptions
  4543. * @author Gabor de Mooij and the RedBeanPHP Community
  4544. * @license BSD/GPLv2
  4545. *
  4546. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  4547. * This source file is subject to the BSD/GPLv2 License that is bundled
  4548. * with this source code in the file license.txt.
  4549. */
  4550. class RedException extends \Exception
  4551. {
  4552. }
  4553. }
  4554. namespace RedBeanPHP\RedException {
  4555. use RedBeanPHP\RedException as RedException;
  4556. /**
  4557. * RedBean\Exception SQL
  4558. *
  4559. * @file RedBean/Exception/SQL.php
  4560. * @desc Represents a generic database exception independent of the underlying driver.
  4561. * @author Gabor de Mooij and the RedBeanPHP Community
  4562. * @license BSD/GPLv2
  4563. *
  4564. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  4565. * This source file is subject to the BSD/GPLv2 License that is bundled
  4566. * with this source code in the file license.txt.
  4567. */
  4568. class SQL extends RedException
  4569. {
  4570. /**
  4571. * @var string
  4572. */
  4573. private $sqlState;
  4574. /**
  4575. * Returns an ANSI-92 compliant SQL state.
  4576. *
  4577. * @return string $state ANSI state code
  4578. */
  4579. public function getSQLState()
  4580. {
  4581. return $this->sqlState;
  4582. }
  4583. /**
  4584. * @todo parse state to verify valid ANSI92!
  4585. * Stores ANSI-92 compliant SQL state.
  4586. *
  4587. * @param string $sqlState code
  4588. *
  4589. * @return void
  4590. */
  4591. public function setSQLState( $sqlState )
  4592. {
  4593. $this->sqlState = $sqlState;
  4594. }
  4595. /**
  4596. * To String prints both code and SQL state.
  4597. *
  4598. * @return string $message prints this exception instance as a string
  4599. */
  4600. public function __toString()
  4601. {
  4602. return '[' . $this->getSQLState() . '] - ' . $this->getMessage()."\n".
  4603. 'trace: ' . $this->getTraceAsString();
  4604. }
  4605. }
  4606. }
  4607. namespace RedBeanPHP {
  4608. use RedBeanPHP\OODBBean as OODBBean;
  4609. use RedBeanPHP\Observable as Observable;
  4610. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  4611. use RedBeanPHP\BeanHelper\FacadeBeanHelper as FacadeBeanHelper;
  4612. use RedBeanPHP\AssociationManager as AssociationManager;
  4613. use RedBeanPHP\QueryWriter as QueryWriter;
  4614. use RedBeanPHP\RedException\Security as Security;
  4615. use RedBeanPHP\SimpleModel as SimpleModel;
  4616. use RedBeanPHP\BeanHelper as BeanHelper;
  4617. use RedBeanPHP\RedException\SQL as SQL;
  4618. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  4619. /**
  4620. * RedBean Object Oriented DataBase
  4621. *
  4622. * @file RedBean/OODB.php
  4623. * @desc RedBean Object Database
  4624. * @author Gabor de Mooij and the RedBeanPHP community
  4625. * @license BSD/GPLv2
  4626. *
  4627. * The RedBean OODB Class is the main class of RedBeanPHP.
  4628. * It takes OODBBean objects and stores them to and loads them from the
  4629. * database as well as providing other CRUD functions. This class acts as a
  4630. * object database.
  4631. *
  4632. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  4633. * This source file is subject to the BSD/GPLv2 License that is bundled
  4634. * with this source code in the file license.txt.
  4635. */
  4636. class OODB extends Observable
  4637. {
  4638. /**
  4639. * @var array
  4640. */
  4641. protected $chillList = array();
  4642. /**
  4643. * @var array
  4644. */
  4645. protected $stash = NULL;
  4646. /*
  4647. * @var integer
  4648. */
  4649. protected $nesting = 0;
  4650. /**
  4651. * @var DBAdapter
  4652. */
  4653. protected $writer;
  4654. /**
  4655. * @var boolean
  4656. */
  4657. protected $isFrozen = FALSE;
  4658. /**
  4659. * @var FacadeBeanHelper
  4660. */
  4661. protected $beanhelper = NULL;
  4662. /**
  4663. * @var AssociationManager
  4664. */
  4665. protected $assocManager = NULL;
  4666. /**
  4667. * Handles\Exceptions. Suppresses exceptions caused by missing structures.
  4668. *
  4669. * @param\Exception $exception exception
  4670. *
  4671. * @return void
  4672. *
  4673. * @throws\Exception
  4674. */
  4675. private function handleException(\Exception $exception )
  4676. {
  4677. if ( $this->isFrozen || !$this->writer->sqlStateIn( $exception->getSQLState(),
  4678. array(
  4679. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  4680. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) )
  4681. ) {
  4682. throw $exception;
  4683. }
  4684. }
  4685. /**
  4686. * Unboxes a bean from a FUSE model if needed and checks whether the bean is
  4687. * an instance of OODBBean.
  4688. *
  4689. * @param OODBBean $bean bean you wish to unbox
  4690. *
  4691. * @return OODBBean
  4692. *
  4693. * @throws Security
  4694. */
  4695. private function unboxIfNeeded( $bean )
  4696. {
  4697. if ( $bean instanceof SimpleModel ) {
  4698. $bean = $bean->unbox();
  4699. }
  4700. if ( !( $bean instanceof OODBBean ) ) {
  4701. throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
  4702. }
  4703. return $bean;
  4704. }
  4705. /**
  4706. * Process groups. Internal function. Processes different kind of groups for
  4707. * storage function. Given a list of original beans and a list of current beans,
  4708. * this function calculates which beans remain in the list (residue), which
  4709. * have been deleted (are in the trashcan) and which beans have been added
  4710. * (additions).
  4711. *
  4712. * @param array $originals originals
  4713. * @param array $current the current beans
  4714. * @param array $additions beans that have been added
  4715. * @param array $trashcan beans that have been deleted
  4716. * @param array $residue beans that have been left untouched
  4717. *
  4718. * @return array
  4719. */
  4720. private function processGroups( $originals, $current, $additions, $trashcan, $residue )
  4721. {
  4722. return array(
  4723. array_merge( $additions, array_diff( $current, $originals ) ),
  4724. array_merge( $trashcan, array_diff( $originals, $current ) ),
  4725. array_merge( $residue, array_intersect( $current, $originals ) )
  4726. );
  4727. }
  4728. /**
  4729. * Figures out the desired type given the cast string ID.
  4730. *
  4731. * @param string $cast cast identifier
  4732. *
  4733. * @return integer
  4734. *
  4735. * @throws Security
  4736. */
  4737. private function getTypeFromCast( $cast )
  4738. {
  4739. if ( $cast == 'string' ) {
  4740. $typeno = $this->writer->scanType( 'STRING' );
  4741. } elseif ( $cast == 'id' ) {
  4742. $typeno = $this->writer->getTypeForID();
  4743. } elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
  4744. $typeno = $this->writer->sqltype_typeno[$cast];
  4745. } else {
  4746. throw new RedException( 'Invalid Cast' );
  4747. }
  4748. return $typeno;
  4749. }
  4750. /**
  4751. * Processes an embedded bean.
  4752. *
  4753. * @param OODBBean|SimpleModel $embeddedBean the bean or model
  4754. *
  4755. * @return integer
  4756. */
  4757. private function prepareEmbeddedBean( $embeddedBean )
  4758. {
  4759. if ( !$embeddedBean->id || $embeddedBean->getMeta( 'tainted' ) ) {
  4760. $this->store( $embeddedBean );
  4761. }
  4762. return $embeddedBean->id;
  4763. }
  4764. /**
  4765. * Orders the Query Writer to create a table if it does not exist already and
  4766. * adds a note in the build report about the creation.
  4767. *
  4768. * @param OODBBean $bean bean to update report of
  4769. * @param string $table table to check and create if not exists
  4770. *
  4771. * @return void
  4772. */
  4773. private function createTableIfNotExists( OODBBean $bean, $table )
  4774. {
  4775. //Does table exist? If not, create
  4776. if ( !$this->isFrozen && !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
  4777. $this->writer->createTable( $table );
  4778. $bean->setMeta( 'buildreport.flags.created', TRUE );
  4779. }
  4780. }
  4781. /**
  4782. * Adds the unique constraints described in the meta data.
  4783. *
  4784. * @param OODBBean $bean bean
  4785. *
  4786. * @return void
  4787. */
  4788. private function addUniqueConstraints( OODBBean $bean )
  4789. {
  4790. if ( $uniques = $bean->getMeta( 'buildcommand.unique' ) ) {
  4791. $table = $bean->getMeta( 'type' );
  4792. foreach ( $uniques as $unique ) {
  4793. if ( !$this->isChilled($table) ) $this->writer->addUniqueIndex( $table, $unique );
  4794. }
  4795. }
  4796. }
  4797. /**
  4798. * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
  4799. * method. When all lists and embedded beans (parent objects) have been processed and
  4800. * removed from the original bean the bean is passed to this method to be stored
  4801. * in the database.
  4802. *
  4803. * @param OODBBean $bean the clean bean
  4804. *
  4805. * @return void
  4806. */
  4807. private function storeBean( OODBBean $bean )
  4808. {
  4809. if ( $bean->getMeta( 'tainted' ) ) {
  4810. if ( !$this->isFrozen ) {
  4811. $this->check( $bean );
  4812. $table = $bean->getMeta( 'type' );
  4813. $this->createTableIfNotExists( $bean, $table );
  4814. $updateValues = $this->getUpdateValues( $bean );
  4815. $this->addUniqueConstraints( $bean );
  4816. $bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
  4817. $bean->setMeta( 'tainted', FALSE );
  4818. } else {
  4819. list( $properties, $table ) = $bean->getPropertiesAndType();
  4820. $id = $properties['id'];
  4821. unset($properties['id']);
  4822. $updateValues = array();
  4823. $k1 = 'property';
  4824. $k2 = 'value';
  4825. foreach( $properties as $key => $value ) {
  4826. $updateValues[] = array( $k1 => $key, $k2 => $value );
  4827. }
  4828. $bean->id = $this->writer->updateRecord( $table, $updateValues, $id );
  4829. $bean->setMeta( 'tainted', FALSE );
  4830. }
  4831. }
  4832. }
  4833. /**
  4834. * Returns a structured array of update values using the following format:
  4835. * array(
  4836. * property => $property,
  4837. * value => $value
  4838. * );
  4839. *
  4840. * @param OODBBean $bean bean to extract update values from
  4841. *
  4842. * @return array
  4843. */
  4844. private function getUpdateValues( OODBBean $bean )
  4845. {
  4846. $updateValues = array();
  4847. foreach ( $bean as $property => $value ) {
  4848. if ( !$this->isFrozen && $property !== 'id' ) {
  4849. $this->moldTable( $bean, $property, $value );
  4850. }
  4851. if ( $property !== 'id' ) {
  4852. $updateValues[] = array( 'property' => $property, 'value' => $value );
  4853. }
  4854. }
  4855. return $updateValues;
  4856. }
  4857. /**
  4858. * Molds the table to fit the bean data.
  4859. * Given a property and a value and the bean, this method will
  4860. * adjust the table structure to fit the requirements of the property and value.
  4861. * This may include adding a new column or widening an existing column to hold a larger
  4862. * or different kind of value. This method employs the writer to adjust the table
  4863. * structure in the database. Schema updates are recorded in meta properties of the bean.
  4864. *
  4865. * @param OODBBean $bean bean to get cast data from and store meta in
  4866. * @param string $property property to store
  4867. * @param mixed $value value to store
  4868. *
  4869. * @return void
  4870. */
  4871. private function moldTable( OODBBean $bean, $property, $value )
  4872. {
  4873. $table = $bean->getMeta( 'type' );
  4874. $columns = $this->writer->getColumns( $table );
  4875. if ( !in_array( $bean->getMeta( 'type' ), $this->chillList ) ) {
  4876. if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
  4877. $cast = $bean->getMeta( "cast.$property" );
  4878. $typeno = $this->getTypeFromCast( $cast );
  4879. } else {
  4880. $cast = FALSE;
  4881. $typeno = $this->writer->scanType( $value, TRUE );
  4882. }
  4883. if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
  4884. if ( !$cast ) { //rescan without taking into account special types >80
  4885. $typeno = $this->writer->scanType( $value, FALSE );
  4886. }
  4887. $sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
  4888. if ( $typeno > $sqlt ) { //no, we have to widen the database column type
  4889. $this->writer->widenColumn( $table, $property, $typeno );
  4890. $bean->setMeta( 'buildreport.flags.widen', TRUE );
  4891. }
  4892. } else {
  4893. $this->writer->addColumn( $table, $property, $typeno );
  4894. $bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
  4895. $this->processBuildCommands( $table, $property, $bean );
  4896. }
  4897. }
  4898. }
  4899. /**
  4900. * Processes a list of beans from a bean. A bean may contain lists. This
  4901. * method handles shared addition lists; i.e. the $bean->sharedObject properties.
  4902. *
  4903. * @param OODBBean $bean the bean
  4904. * @param array $sharedAdditions list with shared additions
  4905. *
  4906. * @return void
  4907. *
  4908. * @throws Security
  4909. */
  4910. private function processSharedAdditions( $bean, $sharedAdditions )
  4911. {
  4912. foreach ( $sharedAdditions as $addition ) {
  4913. if ( $addition instanceof OODBBean ) {
  4914. $this->assocManager->associate( $addition, $bean );
  4915. } else {
  4916. throw new RedException( 'Array may only contain OODBBeans' );
  4917. }
  4918. }
  4919. }
  4920. /**
  4921. * Processes a list of beans from a bean. A bean may contain lists. This
  4922. * method handles own lists; i.e. the $bean->ownObject properties.
  4923. * A residue is a bean in an own-list that stays where it is. This method
  4924. * checks if there have been any modification to this bean, in that case
  4925. * the bean is stored once again, otherwise the bean will be left untouched.
  4926. *
  4927. * @param OODBBean $bean the bean
  4928. * @param array $ownresidue list
  4929. *
  4930. * @return void
  4931. */
  4932. private function processResidue( $ownresidue )
  4933. {
  4934. foreach ( $ownresidue as $residue ) {
  4935. if ( $residue->getMeta( 'tainted' ) ) {
  4936. $this->store( $residue );
  4937. }
  4938. }
  4939. }
  4940. /**
  4941. * Processes a list of beans from a bean. A bean may contain lists. This
  4942. * method handles own lists; i.e. the $bean->ownObject properties.
  4943. * A trash can bean is a bean in an own-list that has been removed
  4944. * (when checked with the shadow). This method
  4945. * checks if the bean is also in the dependency list. If it is the bean will be removed.
  4946. * If not, the connection between the bean and the owner bean will be broken by
  4947. * setting the ID to NULL.
  4948. *
  4949. * @param OODBBean $bean the bean
  4950. * @param array $ownTrashcan list
  4951. *
  4952. * @return void
  4953. */
  4954. private function processTrashcan( $bean, $ownTrashcan )
  4955. {
  4956. foreach ( $ownTrashcan as $trash ) {
  4957. $myFieldLink = $bean->getMeta( 'type' ) . '_id';
  4958. $alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) );
  4959. if ( $alias ) $myFieldLink = $alias . '_id';
  4960. if ( $trash->getMeta( 'sys.garbage' ) === true ) {
  4961. $this->trash( $trash );
  4962. } else {
  4963. $trash->$myFieldLink = NULL;
  4964. $this->store( $trash );
  4965. }
  4966. }
  4967. }
  4968. /**
  4969. * Unassociates the list items in the trashcan.
  4970. *
  4971. * @param OODBBean $bean bean
  4972. * @param array $sharedTrashcan list
  4973. *
  4974. * @return void
  4975. */
  4976. private function processSharedTrashcan( $bean, $sharedTrashcan )
  4977. {
  4978. foreach ( $sharedTrashcan as $trash ) {
  4979. $this->assocManager->unassociate( $trash, $bean );
  4980. }
  4981. }
  4982. /**
  4983. * Stores all the beans in the residue group.
  4984. *
  4985. * @param OODBBean $bean bean
  4986. * @param array $sharedresidue list
  4987. *
  4988. * @return void
  4989. */
  4990. private function processSharedResidue( $bean, $sharedresidue )
  4991. {
  4992. foreach ( $sharedresidue as $residue ) {
  4993. $this->store( $residue );
  4994. }
  4995. }
  4996. /**
  4997. * Processes embedded beans.
  4998. * Each embedded bean will be indexed and foreign keys will
  4999. * be created if the bean is in the dependency list.
  5000. *
  5001. * @param OODBBean $bean bean
  5002. * @param array $embeddedBeans embedded beans
  5003. *
  5004. * @return void
  5005. */
  5006. private function addForeignKeysForParentBeans( $bean, $embeddedBeans )
  5007. {
  5008. $cachedIndex = array();
  5009. foreach ( $embeddedBeans as $linkField => $embeddedBean ) {
  5010. $beanType = $bean->getMeta( 'type' );
  5011. $embeddedType = $embeddedBean->getMeta( 'type' );
  5012. $key = $beanType . '|' . $embeddedType . '>' . $linkField;
  5013. if ( !isset( $cachedIndex[$key] ) ) {
  5014. $this->writer->addIndex( $bean->getMeta( 'type' ),
  5015. 'index_foreignkey_' . $beanType . '_' . $embeddedType,
  5016. $linkField );
  5017. $this->writer->addFK( $beanType, $embeddedType, $linkField, 'id', FALSE );
  5018. $cachedIndex[$key] = TRUE;
  5019. }
  5020. }
  5021. }
  5022. /**
  5023. * Part of the store() functionality.
  5024. * Handles all new additions after the bean has been saved.
  5025. * Stores addition bean in own-list, extracts the id and
  5026. * adds a foreign key. Also adds a constraint in case the type is
  5027. * in the dependent list.
  5028. *
  5029. * @param OODBBean $bean bean
  5030. * @param array $ownAdditions list of addition beans in own-list
  5031. *
  5032. * @return void
  5033. *
  5034. * @throws Security
  5035. */
  5036. private function processAdditions( $bean, $ownAdditions )
  5037. {
  5038. $beanType = $bean->getMeta( 'type' );
  5039. $cachedIndex = array();
  5040. foreach ( $ownAdditions as $addition ) {
  5041. if ( $addition instanceof OODBBean ) {
  5042. $myFieldLink = $beanType . '_id';
  5043. $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
  5044. if ( $alias ) $myFieldLink = $alias . '_id';
  5045. $addition->$myFieldLink = $bean->id;
  5046. $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
  5047. $this->store( $addition );
  5048. if ( !$this->isFrozen ) {
  5049. $additionType = $addition->getMeta( 'type' );
  5050. $key = $additionType . '|' . $beanType . '>' . $myFieldLink;
  5051. if ( !isset( $cachedIndex[$key] ) ) {
  5052. $this->writer->addIndex( $additionType,
  5053. 'index_foreignkey_' . $additionType . '_' . $beanType,
  5054. $myFieldLink );
  5055. $isDep = $bean->getMeta( 'sys.exclusive-'.$additionType );
  5056. $this->writer->addFK( $additionType, $beanType, $myFieldLink, 'id', $isDep );
  5057. $cachedIndex[$key] = TRUE;
  5058. }
  5059. }
  5060. } else {
  5061. throw new RedException( 'Array may only contain OODBBeans' );
  5062. }
  5063. }
  5064. }
  5065. /**
  5066. * Determines whether the bean has 'loaded lists' or
  5067. * 'loaded embedded beans' that need to be processed
  5068. * by the store() method.
  5069. *
  5070. * @param OODBBean $bean bean to be examined
  5071. *
  5072. * @return boolean
  5073. */
  5074. private function hasListsOrObjects( OODBBean $bean )
  5075. {
  5076. $processLists = FALSE;
  5077. foreach ( $bean as $value ) {
  5078. if ( is_array( $value ) || is_object( $value ) ) {
  5079. $processLists = TRUE;
  5080. break;
  5081. }
  5082. }
  5083. return $processLists;
  5084. }
  5085. /**
  5086. * Processes all column based build commands.
  5087. * A build command is an additional instruction for the Query Writer. It is processed only when
  5088. * a column gets created. The build command is often used to instruct the writer to write some
  5089. * extra SQL to create indexes or constraints. Build commands are stored in meta data of the bean.
  5090. * They are only for internal use, try to refrain from using them in your code directly.
  5091. *
  5092. * @param string $table name of the table to process build commands for
  5093. * @param string $property name of the property to process build commands for
  5094. * @param OODBBean $bean bean that contains the build commands
  5095. *
  5096. * @return void
  5097. */
  5098. private function processBuildCommands( $table, $property, OODBBean $bean )
  5099. {
  5100. if ( $inx = ( $bean->getMeta( 'buildcommand.indexes' ) ) ) {
  5101. if ( isset( $inx[$property] ) ) {
  5102. $this->writer->addIndex( $table, $inx[$property], $property );
  5103. }
  5104. }
  5105. }
  5106. /**
  5107. * Converts an embedded bean to an ID, removed the bean property and
  5108. * stores the bean in the embedded beans array.
  5109. *
  5110. * @param array $embeddedBeans destination array for embedded bean
  5111. * @param OODBBean $bean target bean
  5112. * @param string $property property that contains the embedded bean
  5113. * @param OODBBean $value embedded bean itself
  5114. */
  5115. private function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value )
  5116. {
  5117. $linkField = $property . '_id';
  5118. $bean->$linkField = $this->prepareEmbeddedBean( $value );
  5119. $bean->setMeta( 'cast.' . $linkField, 'id' );
  5120. $embeddedBeans[$linkField] = $value;
  5121. unset( $bean->$property );
  5122. }
  5123. /**
  5124. * Stores a bean and its lists in one run.
  5125. *
  5126. * @param OODBBean $bean
  5127. *
  5128. * @return void
  5129. */
  5130. private function processLists( OODBBean $bean )
  5131. {
  5132. $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups
  5133. foreach ( $bean as $property => $value ) {
  5134. $value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value;
  5135. if ( $value instanceof OODBBean ) {
  5136. $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value );
  5137. } elseif ( is_array( $value ) ) {
  5138. $originals = $bean->getMeta( 'sys.shadow.' . $property, array() );
  5139. $bean->setMeta( 'sys.shadow.' . $property, NULL ); //clear shadow
  5140. if ( strpos( $property, 'own' ) === 0 ) {
  5141. list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue );
  5142. $listName = lcfirst( substr( $property, 3 ) );
  5143. if ($bean->getMeta( 'sys.exclusive-'. $listName ) ) {
  5144. OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE );
  5145. }
  5146. unset( $bean->$property );
  5147. } elseif ( strpos( $property, 'shared' ) === 0 ) {
  5148. list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue );
  5149. unset( $bean->$property );
  5150. }
  5151. }
  5152. }
  5153. $this->storeBean( $bean );
  5154. if ( !$this->isFrozen ) {
  5155. $this->addForeignKeysForParentBeans( $bean, $embeddedBeans );
  5156. }
  5157. $this->processTrashcan( $bean, $ownTrashcan );
  5158. $this->processAdditions( $bean, $ownAdditions );
  5159. $this->processResidue( $ownresidue );
  5160. $this->processSharedTrashcan( $bean, $sharedTrashcan );
  5161. $this->processSharedAdditions( $bean, $sharedAdditions );
  5162. $this->processSharedResidue( $bean, $sharedresidue );
  5163. }
  5164. /**
  5165. * Constructor, requires a query writer.
  5166. *
  5167. * @param QueryWriter $writer writer
  5168. */
  5169. public function __construct( QueryWriter $writer )
  5170. {
  5171. if ( $writer instanceof QueryWriter ) {
  5172. $this->writer = $writer;
  5173. }
  5174. }
  5175. /**
  5176. * Toggles fluid or frozen mode. In fluid mode the database
  5177. * structure is adjusted to accomodate your objects. In frozen mode
  5178. * this is not the case.
  5179. *
  5180. * You can also pass an array containing a selection of frozen types.
  5181. * Let's call this chilly mode, it's just like fluid mode except that
  5182. * certain types (i.e. tables) aren't touched.
  5183. *
  5184. * @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
  5185. *
  5186. * @return void
  5187. */
  5188. public function freeze( $toggle )
  5189. {
  5190. if ( is_array( $toggle ) ) {
  5191. $this->chillList = $toggle;
  5192. $this->isFrozen = FALSE;
  5193. } else {
  5194. $this->isFrozen = (boolean) $toggle;
  5195. }
  5196. }
  5197. /**
  5198. * Returns the current mode of operation of RedBean.
  5199. * In fluid mode the database
  5200. * structure is adjusted to accomodate your objects.
  5201. * In frozen mode
  5202. * this is not the case.
  5203. *
  5204. * @return boolean
  5205. */
  5206. public function isFrozen()
  5207. {
  5208. return (bool) $this->isFrozen;
  5209. }
  5210. /**
  5211. * Determines whether a type is in the chill list.
  5212. * If a type is 'chilled' it's frozen, so its schema cannot be
  5213. * changed anymore. However other bean types may still be modified.
  5214. * This method is a convenience method for other objects to check if
  5215. * the schema of a certain type is locked for modification.
  5216. *
  5217. * @param string $type the type you wish to check
  5218. *
  5219. * @return boolean
  5220. */
  5221. public function isChilled( $type )
  5222. {
  5223. return (boolean) ( in_array( $type, $this->chillList ) );
  5224. }
  5225. /**
  5226. * Dispenses a new bean (a OODBBean Bean Object)
  5227. * of the specified type. Always
  5228. * use this function to get an empty bean object. Never
  5229. * instantiate a OODBBean yourself because it needs
  5230. * to be configured before you can use it with RedBean. This
  5231. * function applies the appropriate initialization /
  5232. * configuration for you.
  5233. *
  5234. * @param string $type type of bean you want to dispense
  5235. * @param string $number number of beans you would like to get
  5236. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  5237. *
  5238. * @return OODBBean
  5239. */
  5240. public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
  5241. {
  5242. if ( $number < 1 ) {
  5243. if ( $alwaysReturnArray ) return array();
  5244. return NULL;
  5245. }
  5246. $beans = array();
  5247. for ( $i = 0; $i < $number; $i++ ) {
  5248. $bean = new OODBBean;
  5249. $bean->initializeForDispense( $type, $this->beanhelper );
  5250. if ( !$this->isFrozen ) {
  5251. $this->check( $bean );
  5252. }
  5253. $this->signal( 'dispense', $bean );
  5254. $beans[] = $bean;
  5255. }
  5256. return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
  5257. }
  5258. /**
  5259. * Sets bean helper to be given to beans.
  5260. * Bean helpers assist beans in getting a reference to a toolbox.
  5261. *
  5262. * @param BeanHelper $beanhelper helper
  5263. *
  5264. * @return void
  5265. */
  5266. public function setBeanHelper( BeanHelper $beanhelper )
  5267. {
  5268. $this->beanhelper = $beanhelper;
  5269. }
  5270. /**
  5271. * Checks whether a OODBBean bean is valid.
  5272. * If the type is not valid or the ID is not valid it will
  5273. * throw an exception: Security.
  5274. *
  5275. * @param OODBBean $bean the bean that needs to be checked
  5276. *
  5277. * @return void
  5278. *
  5279. * @throws Security $exception
  5280. */
  5281. public function check( OODBBean $bean )
  5282. {
  5283. //Is all meta information present?
  5284. if ( !isset( $bean->id ) ) {
  5285. throw new RedException( 'Bean has incomplete Meta Information id ' );
  5286. }
  5287. if ( !( $bean->getMeta( 'type' ) ) ) {
  5288. throw new RedException( 'Bean has incomplete Meta Information II' );
  5289. }
  5290. //Pattern of allowed characters
  5291. $pattern = '/[^a-z0-9_]/i';
  5292. //Does the type contain invalid characters?
  5293. if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) {
  5294. throw new RedException( 'Bean Type is invalid' );
  5295. }
  5296. //Are the properties and values valid?
  5297. foreach ( $bean as $prop => $value ) {
  5298. if (
  5299. is_array( $value )
  5300. || ( is_object( $value ) )
  5301. ) {
  5302. throw new RedException( "Invalid Bean value: property $prop" );
  5303. } else if (
  5304. strlen( $prop ) < 1
  5305. || preg_match( $pattern, $prop )
  5306. ) {
  5307. throw new RedException( "Invalid Bean property: property $prop" );
  5308. }
  5309. }
  5310. }
  5311. /**
  5312. * Searches the database for a bean that matches conditions $conditions and sql $addSQL
  5313. * and returns an array containing all the beans that have been found.
  5314. *
  5315. * Conditions need to take form:
  5316. *
  5317. * array(
  5318. * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
  5319. * 'PROPERTY' => array( POSSIBLE VALUES... )
  5320. * );
  5321. *
  5322. * All conditions are glued together using the AND-operator, while all value lists
  5323. * are glued using IN-operators thus acting as OR-conditions.
  5324. *
  5325. * Note that you can use property names; the columns will be extracted using the
  5326. * appropriate bean formatter.
  5327. *
  5328. * @param string $type type of beans you are looking for
  5329. * @param array $conditions list of conditions
  5330. * @param string $addSQL SQL to be used in query
  5331. * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
  5332. *
  5333. * @return array
  5334. *
  5335. * @throws SQL
  5336. */
  5337. public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
  5338. {
  5339. //for backward compatibility, allow mismatch arguments:
  5340. if ( is_array( $sql ) ) {
  5341. if ( isset( $sql[1] ) ) {
  5342. $bindings = $sql[1];
  5343. }
  5344. $sql = $sql[0];
  5345. }
  5346. try {
  5347. $beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) );
  5348. return $beans;
  5349. } catch ( SQL $exception ) {
  5350. $this->handleException( $exception );
  5351. }
  5352. return array();
  5353. }
  5354. /**
  5355. * Checks whether the specified table already exists in the database.
  5356. * Not part of the Object Database interface!
  5357. *
  5358. * @deprecated Use AQueryWriter::typeExists() instead.
  5359. *
  5360. * @param string $table table name
  5361. *
  5362. * @return boolean
  5363. */
  5364. public function tableExists( $table )
  5365. {
  5366. return $this->writer->tableExists( $table );
  5367. }
  5368. /**
  5369. * Stores a bean in the database. This method takes a
  5370. * OODBBean Bean Object $bean and stores it
  5371. * in the database. If the database schema is not compatible
  5372. * with this bean and RedBean runs in fluid mode the schema
  5373. * will be altered to store the bean correctly.
  5374. * If the database schema is not compatible with this bean and
  5375. * RedBean runs in frozen mode it will throw an exception.
  5376. * This function returns the primary key ID of the inserted
  5377. * bean.
  5378. *
  5379. * The return value is an integer if possible. If it is not possible to
  5380. * represent the value as an integer a string will be returned. We use
  5381. * explicit casts instead of functions to preserve performance
  5382. * (0.13 vs 0.28 for 10000 iterations on Core i3).
  5383. *
  5384. * @param OODBBean|SimpleModel $bean bean to store
  5385. *
  5386. * @return integer|string
  5387. *
  5388. * @throws Security
  5389. */
  5390. public function store( $bean )
  5391. {
  5392. $bean = $this->unboxIfNeeded( $bean );
  5393. $processLists = $this->hasListsOrObjects( $bean );
  5394. if ( !$processLists && !$bean->getMeta( 'tainted' ) ) {
  5395. return $bean->getID(); //bail out!
  5396. }
  5397. $this->signal( 'update', $bean );
  5398. $processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model!
  5399. if ( $processLists ) {
  5400. $this->processLists( $bean );
  5401. } else {
  5402. $this->storeBean( $bean );
  5403. }
  5404. $this->signal( 'after_update', $bean );
  5405. return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id;
  5406. }
  5407. /**
  5408. * Loads a bean from the object database.
  5409. * It searches for a OODBBean Bean Object in the
  5410. * database. It does not matter how this bean has been stored.
  5411. * RedBean uses the primary key ID $id and the string $type
  5412. * to find the bean. The $type specifies what kind of bean you
  5413. * are looking for; this is the same type as used with the
  5414. * dispense() function. If RedBean finds the bean it will return
  5415. * the OODB Bean object; if it cannot find the bean
  5416. * RedBean will return a new bean of type $type and with
  5417. * primary key ID 0. In the latter case it acts basically the
  5418. * same as dispense().
  5419. *
  5420. * Important note:
  5421. * If the bean cannot be found in the database a new bean of
  5422. * the specified type will be generated and returned.
  5423. *
  5424. * @param string $type type of bean you want to load
  5425. * @param integer $id ID of the bean you want to load
  5426. *
  5427. * @throws SQL
  5428. *
  5429. * @return OODBBean
  5430. *
  5431. */
  5432. public function load( $type, $id )
  5433. {
  5434. $bean = $this->dispense( $type );
  5435. if ( isset( $this->stash[$this->nesting][$id] ) ) {
  5436. $row = $this->stash[$this->nesting][$id];
  5437. } else {
  5438. try {
  5439. $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
  5440. } catch ( SQL $exception ) {
  5441. if ( $this->writer->sqlStateIn( $exception->getSQLState(),
  5442. array(
  5443. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  5444. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
  5445. )
  5446. ) {
  5447. $rows = 0;
  5448. if ( $this->isFrozen ) {
  5449. throw $exception; //only throw if frozen
  5450. }
  5451. }
  5452. }
  5453. if ( empty( $rows ) ) {
  5454. return $bean;
  5455. }
  5456. $row = array_pop( $rows );
  5457. }
  5458. $bean->importRow( $row );
  5459. $this->nesting++;
  5460. $this->signal( 'open', $bean );
  5461. $this->nesting--;
  5462. return $bean->setMeta( 'tainted', FALSE );
  5463. }
  5464. /**
  5465. * Removes a bean from the database.
  5466. * This function will remove the specified OODBBean
  5467. * Bean Object from the database.
  5468. *
  5469. * @param OODBBean|SimpleModel $bean bean you want to remove from database
  5470. *
  5471. * @return void
  5472. *
  5473. * @throws Security
  5474. */
  5475. public function trash( $bean )
  5476. {
  5477. if ( $bean instanceof SimpleModel ) {
  5478. $bean = $bean->unbox();
  5479. }
  5480. if ( !( $bean instanceof OODBBean ) ) {
  5481. throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
  5482. }
  5483. $this->signal( 'delete', $bean );
  5484. foreach ( $bean as $property => $value ) {
  5485. if ( $value instanceof OODBBean ) {
  5486. unset( $bean->$property );
  5487. }
  5488. if ( is_array( $value ) ) {
  5489. if ( strpos( $property, 'own' ) === 0 ) {
  5490. unset( $bean->$property );
  5491. } elseif ( strpos( $property, 'shared' ) === 0 ) {
  5492. unset( $bean->$property );
  5493. }
  5494. }
  5495. }
  5496. if ( !$this->isFrozen ) {
  5497. $this->check( $bean );
  5498. }
  5499. try {
  5500. $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ), NULL );
  5501. } catch ( SQL $exception ) {
  5502. $this->handleException( $exception );
  5503. }
  5504. $bean->id = 0;
  5505. $this->signal( 'after_delete', $bean );
  5506. }
  5507. /**
  5508. * Returns an array of beans. Pass a type and a series of ids and
  5509. * this method will bring you the corresponding beans.
  5510. *
  5511. * important note: Because this method loads beans using the load()
  5512. * function (but faster) it will return empty beans with ID 0 for
  5513. * every bean that could not be located. The resulting beans will have the
  5514. * passed IDs as their keys.
  5515. *
  5516. * @param string $type type of beans
  5517. * @param array $ids ids to load
  5518. *
  5519. * @return array
  5520. */
  5521. public function batch( $type, $ids )
  5522. {
  5523. if ( !$ids ) {
  5524. return array();
  5525. }
  5526. $collection = array();
  5527. try {
  5528. $rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) );
  5529. } catch ( SQL $e ) {
  5530. $this->handleException( $e );
  5531. $rows = FALSE;
  5532. }
  5533. $this->stash[$this->nesting] = array();
  5534. if ( !$rows ) {
  5535. return array();
  5536. }
  5537. foreach ( $rows as $row ) {
  5538. $this->stash[$this->nesting][$row['id']] = $row;
  5539. }
  5540. foreach ( $ids as $id ) {
  5541. $collection[$id] = $this->load( $type, $id );
  5542. }
  5543. $this->stash[$this->nesting] = NULL;
  5544. return $collection;
  5545. }
  5546. /**
  5547. * This is a convenience method; it converts database rows
  5548. * (arrays) into beans. Given a type and a set of rows this method
  5549. * will return an array of beans of the specified type loaded with
  5550. * the data fields provided by the result set from the database.
  5551. *
  5552. * @param string $type type of beans you would like to have
  5553. * @param array $rows rows from the database result
  5554. *
  5555. * @return array
  5556. */
  5557. public function convertToBeans( $type, $rows )
  5558. {
  5559. $collection = array();
  5560. $this->stash[$this->nesting] = array();
  5561. foreach ( $rows as $row ) {
  5562. $id = $row['id'];
  5563. $this->stash[$this->nesting][$id] = $row;
  5564. $collection[$id] = $this->load( $type, $id );
  5565. }
  5566. $this->stash[$this->nesting] = NULL;
  5567. return $collection;
  5568. }
  5569. /**
  5570. * Counts the number of beans of type $type.
  5571. * This method accepts a second argument to modify the count-query.
  5572. * A third argument can be used to provide bindings for the SQL snippet.
  5573. *
  5574. * @param string $type type of bean we are looking for
  5575. * @param string $addSQL additional SQL snippet
  5576. * @param array $bindings parameters to bind to SQL
  5577. *
  5578. * @return integer
  5579. *
  5580. * @throws SQL
  5581. */
  5582. public function count( $type, $addSQL = '', $bindings = array() )
  5583. {
  5584. $type = AQueryWriter::camelsSnake( $type );
  5585. if ( count( explode( '_', $type ) ) > 2 ) {
  5586. throw new RedException( 'Invalid type for count.' );
  5587. }
  5588. try {
  5589. return (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings );
  5590. } catch ( SQL $exception ) {
  5591. if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array(
  5592. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  5593. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) ) ) {
  5594. throw $exception;
  5595. }
  5596. }
  5597. return 0;
  5598. }
  5599. /**
  5600. * Trash all beans of a given type. Wipes an entire type of bean.
  5601. *
  5602. * @param string $type type of bean you wish to delete all instances of
  5603. *
  5604. * @return boolean
  5605. *
  5606. * @throws SQL
  5607. */
  5608. public function wipe( $type )
  5609. {
  5610. try {
  5611. $this->writer->wipe( $type );
  5612. return TRUE;
  5613. } catch ( SQL $exception ) {
  5614. if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ) ) ) {
  5615. throw $exception;
  5616. }
  5617. return FALSE;
  5618. }
  5619. }
  5620. /**
  5621. * Returns an Association Manager for use with OODB.
  5622. * A simple getter function to obtain a reference to the association manager used for
  5623. * storage and more.
  5624. *
  5625. * @return AssociationManager
  5626. *
  5627. * @throws Security
  5628. */
  5629. public function getAssociationManager()
  5630. {
  5631. if ( !isset( $this->assocManager ) ) {
  5632. throw new RedException( 'No association manager available.' );
  5633. }
  5634. return $this->assocManager;
  5635. }
  5636. /**
  5637. * Sets the association manager instance to be used by this OODB.
  5638. * A simple setter function to set the association manager to be used for storage and
  5639. * more.
  5640. *
  5641. * @param AssociationManager $assoc sets the association manager to be used
  5642. *
  5643. * @return void
  5644. */
  5645. public function setAssociationManager( AssociationManager $assocManager )
  5646. {
  5647. $this->assocManager = $assocManager;
  5648. }
  5649. }
  5650. }
  5651. namespace RedBeanPHP {
  5652. use RedBeanPHP\OODB as OODB;
  5653. use RedBeanPHP\QueryWriter as QueryWriter;
  5654. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  5655. use RedBeanPHP\Adapter as Adapter;
  5656. /**
  5657. * @file RedBean/ToolBox.php
  5658. * @desc A RedBeanPHP-wide service locator
  5659. * @author Gabor de Mooij and the RedBeanPHP community
  5660. * @license BSD/GPLv2
  5661. *
  5662. * ToolBox.
  5663. * The toolbox is an integral part of RedBeanPHP providing the basic
  5664. * architectural building blocks to manager objects, helpers and additional tools
  5665. * like plugins. A toolbox contains the three core components of RedBeanPHP:
  5666. * the adapter, the query writer and the core functionality of RedBeanPHP in
  5667. * OODB.
  5668. *
  5669. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  5670. * This source file is subject to the BSD/GPLv2 License that is bundled
  5671. * with this source code in the file license.txt.
  5672. */
  5673. class ToolBox
  5674. {
  5675. /**
  5676. * @var OODB
  5677. */
  5678. protected $oodb;
  5679. /**
  5680. * @var QueryWriter
  5681. */
  5682. protected $writer;
  5683. /**
  5684. * @var DBAdapter
  5685. */
  5686. protected $adapter;
  5687. /**
  5688. * Constructor.
  5689. * The toolbox is an integral part of RedBeanPHP providing the basic
  5690. * architectural building blocks to manager objects, helpers and additional tools
  5691. * like plugins. A toolbox contains the three core components of RedBeanPHP:
  5692. * the adapter, the query writer and the core functionality of RedBeanPHP in
  5693. * OODB.
  5694. *
  5695. * @param OODB $oodb Object Database
  5696. * @param DBAdapter $adapter Adapter
  5697. * @param QueryWriter $writer Writer
  5698. *
  5699. * @return ToolBox
  5700. */
  5701. public function __construct( OODB $oodb, Adapter $adapter, QueryWriter $writer )
  5702. {
  5703. $this->oodb = $oodb;
  5704. $this->adapter = $adapter;
  5705. $this->writer = $writer;
  5706. return $this;
  5707. }
  5708. /**
  5709. * Returns the query writer in this toolbox.
  5710. * The Query Writer is responsible for building the queries for a
  5711. * specific database and executing them through the adapter.
  5712. *
  5713. * @return QueryWriter
  5714. */
  5715. public function getWriter()
  5716. {
  5717. return $this->writer;
  5718. }
  5719. /**
  5720. * Returns the OODB instance in this toolbox.
  5721. * OODB is responsible for creating, storing, retrieving and deleting
  5722. * single beans. Other components rely
  5723. * on OODB for their basic functionality.
  5724. *
  5725. * @return OODB
  5726. */
  5727. public function getRedBean()
  5728. {
  5729. return $this->oodb;
  5730. }
  5731. /**
  5732. * Returns the database adapter in this toolbox.
  5733. * The adapter is responsible for executing the query and binding the values.
  5734. * The adapter also takes care of transaction handling.
  5735. *
  5736. * @return DBAdapter
  5737. */
  5738. public function getDatabaseAdapter()
  5739. {
  5740. return $this->adapter;
  5741. }
  5742. }
  5743. }
  5744. namespace RedBeanPHP {
  5745. use RedBeanPHP\ToolBox as ToolBox;
  5746. use RedBeanPHP\OODB as OODB;
  5747. use RedBeanPHP\RedException\Security as Security;
  5748. use RedBeanPHP\OODBBean as OODBBean;
  5749. /**
  5750. * RedBean Finder
  5751. *
  5752. * @file RedBean/Finder.php
  5753. * @desc Helper class to harmonize APIs.
  5754. * @author Gabor de Mooij and the RedBeanPHP Community
  5755. * @license BSD/GPLv2
  5756. *
  5757. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  5758. * This source file is subject to the BSD/GPLv2 License that is bundled
  5759. * with this source code in the file license.txt.
  5760. */
  5761. class Finder
  5762. {
  5763. /**
  5764. * @var ToolBox
  5765. */
  5766. protected $toolbox;
  5767. /**
  5768. * @var OODB
  5769. */
  5770. protected $redbean;
  5771. /**
  5772. * Constructor.
  5773. * The Finder requires a toolbox.
  5774. *
  5775. * @param ToolBox $toolbox
  5776. */
  5777. public function __construct( ToolBox $toolbox )
  5778. {
  5779. $this->toolbox = $toolbox;
  5780. $this->redbean = $toolbox->getRedBean();
  5781. }
  5782. /**
  5783. * Finds a bean using a type and a where clause (SQL).
  5784. * As with most Query tools in RedBean you can provide values to
  5785. * be inserted in the SQL statement by populating the value
  5786. * array parameter; you can either use the question mark notation
  5787. * or the slot-notation (:keyname).
  5788. *
  5789. * @param string $type type the type of bean you are looking for
  5790. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  5791. * @param array $bindings values array of values to be bound to parameters in query
  5792. *
  5793. * @return array
  5794. *
  5795. * @throws Security
  5796. */
  5797. public function find( $type, $sql = NULL, $bindings = array() )
  5798. {
  5799. if ( !is_array( $bindings ) ) {
  5800. throw new RedException(
  5801. 'Expected array, ' . gettype( $bindings ) . ' given.'
  5802. );
  5803. }
  5804. return $this->redbean->find( $type, array(), $sql, $bindings );
  5805. }
  5806. /**
  5807. * @see Finder::find
  5808. * The variation also exports the beans (i.e. it returns arrays).
  5809. *
  5810. * @param string $type type the type of bean you are looking for
  5811. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  5812. * @param array $bindings values array of values to be bound to parameters in query
  5813. *
  5814. * @return array
  5815. */
  5816. public function findAndExport( $type, $sql = NULL, $bindings = array() )
  5817. {
  5818. $arr = array();
  5819. foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
  5820. $arr[] = $item->export();
  5821. }
  5822. return $arr;
  5823. }
  5824. /**
  5825. * @see Finder::find
  5826. * This variation returns the first bean only.
  5827. *
  5828. * @param string $type type the type of bean you are looking for
  5829. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  5830. * @param array $bindings values array of values to be bound to parameters in query
  5831. *
  5832. * @return OODBBean
  5833. */
  5834. public function findOne( $type, $sql = NULL, $bindings = array() )
  5835. {
  5836. $items = $this->find( $type, $sql, $bindings );
  5837. if ( empty($items) ) {
  5838. return NULL;
  5839. }
  5840. return reset( $items );
  5841. }
  5842. /**
  5843. * @see Finder::find
  5844. * This variation returns the last bean only.
  5845. *
  5846. * @param string $type the type of bean you are looking for
  5847. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  5848. * @param array $bindings values array of values to be bound to parameters in query
  5849. *
  5850. * @return OODBBean
  5851. */
  5852. public function findLast( $type, $sql = NULL, $bindings = array() )
  5853. {
  5854. $items = $this->find( $type, $sql, $bindings );
  5855. if ( empty($items) ) {
  5856. return NULL;
  5857. }
  5858. return end( $items );
  5859. }
  5860. /**
  5861. * @see Finder::find
  5862. * Convience method. Tries to find beans of a certain type,
  5863. * if no beans are found, it dispenses a bean of that type.
  5864. *
  5865. * @param string $type the type of bean you are looking for
  5866. * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
  5867. * @param array $bindings values array of values to be bound to parameters in query
  5868. *
  5869. * @return array
  5870. */
  5871. public function findOrDispense( $type, $sql = NULL, $bindings = array() )
  5872. {
  5873. $foundBeans = $this->find( $type, $sql, $bindings );
  5874. if ( empty( $foundBeans ) ) {
  5875. return array( $this->redbean->dispense( $type ) );
  5876. } else {
  5877. return $foundBeans;
  5878. }
  5879. }
  5880. }
  5881. }
  5882. namespace RedBeanPHP {
  5883. use RedBeanPHP\Observable as Observable;
  5884. use RedBeanPHP\OODB as OODB;
  5885. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  5886. use RedBeanPHP\QueryWriter as QueryWriter;
  5887. use RedBeanPHP\OODBBean as OODBBean;
  5888. use RedBeanPHP\RedException as RedException;
  5889. use RedBeanPHP\RedException\Security as Security;
  5890. use RedBeanPHP\RedException\SQL as SQL;
  5891. use RedBeanPHP\ToolBox as ToolBox;
  5892. /**
  5893. * Association Manager
  5894. *
  5895. * @file RedBean/AssociationManager.php
  5896. * @desc Manages simple bean associations.
  5897. * @author Gabor de Mooij and the RedBeanPHP Community
  5898. * @license BSD/GPLv2
  5899. *
  5900. * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  5901. * This source file is subject to the BSD/GPLv2 License that is bundled
  5902. * with this source code in the file license.txt.
  5903. */
  5904. class AssociationManager extends Observable
  5905. {
  5906. /**
  5907. * @var OODB
  5908. */
  5909. protected $oodb;
  5910. /**
  5911. * @var DBAdapter
  5912. */
  5913. protected $adapter;
  5914. /**
  5915. * @var QueryWriter
  5916. */
  5917. protected $writer;
  5918. /**
  5919. * Handles\Exceptions. Suppresses exceptions caused by missing structures.
  5920. *
  5921. * @param\Exception $exception
  5922. *
  5923. * @return void
  5924. *
  5925. * @throws\Exception
  5926. */
  5927. private function handleException(\Exception $exception )
  5928. {
  5929. if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
  5930. array(
  5931. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
  5932. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN )
  5933. )
  5934. ) {
  5935. throw $exception;
  5936. }
  5937. }
  5938. /**
  5939. * Internal method.
  5940. * Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
  5941. * $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
  5942. *
  5943. * @param OODBBean $bean reference bean
  5944. * @param string $type target type
  5945. * @param string $sql additional SQL snippet
  5946. * @param array $bindings bindings
  5947. *
  5948. * @return array
  5949. *
  5950. * @throws Security
  5951. * @throws SQL
  5952. */
  5953. private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
  5954. {
  5955. $ids = array( $bean->id );
  5956. $sourceType = $bean->getMeta( 'type' );
  5957. try {
  5958. return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
  5959. } catch ( SQL $exception ) {
  5960. $this->handleException( $exception );
  5961. return array();
  5962. }
  5963. }
  5964. /**
  5965. * Associates a pair of beans. This method associates two beans, no matter
  5966. * what types. Accepts a base bean that contains data for the linking record.
  5967. * This method is used by associate. This method also accepts a base bean to be used
  5968. * as the template for the link record in the database.
  5969. *
  5970. * @param OODBBean $bean1 first bean
  5971. * @param OODBBean $bean2 second bean
  5972. * @param OODBBean $bean base bean (association record)
  5973. *
  5974. * @throws\Exception|SQL
  5975. *
  5976. * @return mixed
  5977. */
  5978. protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
  5979. {
  5980. $property1 = $bean1->getMeta( 'type' ) . '_id';
  5981. $property2 = $bean2->getMeta( 'type' ) . '_id';
  5982. if ( $property1 == $property2 ) {
  5983. $property2 = $bean2->getMeta( 'type' ) . '2_id';
  5984. }
  5985. //Dont mess with other tables, only add the unique constraint if:
  5986. //1. the table exists (otherwise we cant inspect it)
  5987. //2. the table only contains N-M fields: ID, N-ID, M-ID.
  5988. $unique = array( $property1, $property2 );
  5989. $type = $bean->getMeta( 'type' );
  5990. $tables = $this->writer->getTables();
  5991. if ( in_array( $type, $tables ) && !$this->oodb->isChilled( $type ) ) {
  5992. $columns = ( $this->writer->getColumns( $type ) );
  5993. if ( count( $columns ) === 3
  5994. && isset( $columns[ 'id' ] )
  5995. && isset( $columns[ $property1 ] )
  5996. && isset( $columns[ $property2 ] ) ) {
  5997. $bean->setMeta( 'buildcommand.unique', array( $unique ) );
  5998. }
  5999. }
  6000. //add a build command for Single Column Index (to improve performance in case unqiue cant be used)
  6001. $indexName1 = 'index_for_' . $bean->getMeta( 'type' ) . '_' . $property1;
  6002. $indexName2 = 'index_for_' . $bean->getMeta( 'type' ) . '_' . $property2;
  6003. $bean->setMeta( 'buildcommand.indexes', array( $property1 => $indexName1, $property2 => $indexName2 ) );
  6004. $this->oodb->store( $bean1 );
  6005. $this->oodb->store( $bean2 );
  6006. $bean->setMeta( "cast.$property1", "id" );
  6007. $bean->setMeta( "cast.$property2", "id" );
  6008. $bean->$property1 = $bean1->id;
  6009. $bean->$property2 = $bean2->id;
  6010. $results = array();
  6011. try {
  6012. $id = $this->oodb->store( $bean );
  6013. //On creation, add constraints....
  6014. if ( !$this->oodb->isFrozen() &&
  6015. $bean->getMeta( 'buildreport.flags.created' )
  6016. ) {
  6017. $bean->setMeta( 'buildreport.flags.created', 0 );
  6018. if ( !$this->oodb->isFrozen() ) {
  6019. $this->writer->addConstraintForTypes( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) );
  6020. }
  6021. }
  6022. $results[] = $id;
  6023. } catch ( SQL $exception ) {
  6024. if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
  6025. array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ) )
  6026. ) {
  6027. throw $exception;
  6028. }
  6029. }
  6030. return $results;
  6031. }
  6032. /**
  6033. * Constructor
  6034. *
  6035. * @param ToolBox $tools toolbox
  6036. */
  6037. public function __construct( ToolBox $tools )
  6038. {
  6039. $this->oodb = $tools->getRedBean();
  6040. $this->adapter = $tools->getDatabaseAdapter();
  6041. $this->writer = $tools->getWriter();
  6042. $this->toolbox = $tools;
  6043. }
  6044. /**
  6045. * Creates a table name based on a types array.
  6046. * Manages the get the correct name for the linking table for the
  6047. * types provided.
  6048. *
  6049. * @todo find a nice way to decouple this class from QueryWriter?
  6050. *
  6051. * @param array $types 2 types as strings
  6052. *
  6053. * @return string
  6054. */
  6055. public function getTable( $types )
  6056. {
  6057. return $this->writer->getAssocTable( $types );
  6058. }
  6059. /**
  6060. * Associates two beans in a many-to-many relation.
  6061. * This method will associate two beans and store the connection between the
  6062. * two in a link table. Instead of two single beans this method also accepts
  6063. * two sets of beans. Returns the ID or the IDs of the linking beans.
  6064. *
  6065. * @param OODBBean|array $beans1 one or more beans to form the association
  6066. * @param OODBBean|array $beans2 one or more beans to form the association
  6067. *
  6068. * @return array
  6069. */
  6070. public function associate( $beans1, $beans2 )
  6071. {
  6072. if ( !is_array( $beans1 ) ) {
  6073. $beans1 = array( $beans1 );
  6074. }
  6075. if ( !is_array( $beans2 ) ) {
  6076. $beans2 = array( $beans2 );
  6077. }
  6078. $results = array();
  6079. foreach ( $beans1 as $bean1 ) {
  6080. foreach ( $beans2 as $bean2 ) {
  6081. $table = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
  6082. $bean = $this->oodb->dispense( $table );
  6083. $results[] = $this->associateBeans( $bean1, $bean2, $bean );
  6084. }
  6085. }
  6086. return ( count( $results ) > 1 ) ? $results : reset( $results );
  6087. }
  6088. /**
  6089. * Counts the number of related beans in an N-M relation.
  6090. * This method returns the number of beans of type $type associated
  6091. * with reference bean(s) $bean. The query can be tuned using an
  6092. * SQL snippet for additional filtering.
  6093. *
  6094. * @param OODBBean|array $bean a bean object or an array of beans
  6095. * @param string $type type of bean you're interested in
  6096. * @param string $sql SQL snippet (optional)
  6097. * @param array $bindings bindings for your SQL string
  6098. *
  6099. * @return integer
  6100. *
  6101. * @throws Security
  6102. */
  6103. public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
  6104. {
  6105. if ( !( $bean instanceof OODBBean ) ) {
  6106. throw new RedException(
  6107. 'Expected array or OODBBean but got:' . gettype( $bean )
  6108. );
  6109. }
  6110. if ( !$bean->id ) {
  6111. return 0;
  6112. }
  6113. $beanType = $bean->getMeta( 'type' );
  6114. try {
  6115. return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
  6116. } catch ( SQL $exception ) {
  6117. $this->handleException( $exception );
  6118. return 0;
  6119. }
  6120. }
  6121. /**
  6122. * Breaks the association between two beans. This method unassociates two beans. If the
  6123. * method succeeds the beans will no longer form an association. In the database
  6124. * this means that the association record will be removed. This method uses the
  6125. * OODB trash() method to remove the association links, thus giving FUSE models the
  6126. * opportunity to hook-in additional business logic. If the $fast parameter is
  6127. * set to boolean TRUE this method will remove the beans without their consent,
  6128. * bypassing FUSE. This can be used to improve performance.
  6129. *
  6130. * @param OODBBean $bean1 first bean
  6131. * @param OODBBean $bean2 second bean
  6132. * @param boolean $fast If TRUE, removes the entries by query without FUSE
  6133. *
  6134. * @return void
  6135. */
  6136. public function unassociate( $beans1, $beans2, $fast = NULL )
  6137. {
  6138. $beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
  6139. $beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
  6140. foreach ( $beans1 as $bean1 ) {
  6141. foreach ( $beans2 as $bean2 ) {
  6142. try {
  6143. $this->oodb->store( $bean1 );
  6144. $this->oodb->store( $bean2 );
  6145. $type1 = $bean1->getMeta( 'type' );
  6146. $type2 = $bean2->getMeta( 'type' );
  6147. $row = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
  6148. $linkType = $this->getTable( array( $type1, $type2 ) );
  6149. if ( $fast ) {
  6150. $this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
  6151. return;
  6152. }
  6153. $beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
  6154. if ( count( $beans ) > 0 ) {
  6155. $bean = reset( $beans );
  6156. $this->oodb->trash( $bean );
  6157. }
  6158. } catch ( SQL $exception ) {
  6159. $this->handleException( $exception );
  6160. }
  6161. }
  6162. }
  6163. }
  6164. /**
  6165. * Removes all relations for a bean. This method breaks every connection between
  6166. * a certain bean $bean and every other bean of type $type. Warning: this method
  6167. * is really fast because it uses a direct SQL query however it does not inform the
  6168. * models about this. If you want to notify FUSE models about deletion use a foreach-loop
  6169. * with unassociate() instead. (that might be slower though)
  6170. *
  6171. * @param OODBBean $bean reference bean
  6172. * @param string $type type of beans that need to be unassociated
  6173. *
  6174. * @return void
  6175. */
  6176. public function clearRelations( OODBBean $bean, $type )
  6177. {
  6178. $this->oodb->store( $bean );
  6179. try {
  6180. $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
  6181. } catch ( SQL $exception ) {
  6182. $this->handleException( $exception );
  6183. }
  6184. }
  6185. /**
  6186. * Returns all the beans associated with $bean.
  6187. * This method will return an array containing all the beans that have
  6188. * been associated once with the associate() function and are still
  6189. * associated with the bean specified. The type parameter indicates the
  6190. * type of beans you are looking for. You can also pass some extra SQL and
  6191. * values for that SQL to filter your results after fetching the
  6192. * related beans.
  6193. *
  6194. * Don't try to make use of subqueries, a subquery using IN() seems to
  6195. * be slower than two queries!
  6196. *
  6197. * Since 3.2, you can now also pass an array of beans instead just one
  6198. * bean as the first parameter.
  6199. *
  6200. * @param OODBBean|array $bean the bean you have
  6201. * @param string $type the type of beans you want
  6202. * @param string $sql SQL snippet for extra filtering
  6203. * @param array $bindings values to be inserted in SQL slots
  6204. * @param boolean $glue whether the SQL should be prefixed with WHERE
  6205. *
  6206. * @return array
  6207. */
  6208. public function related( $bean, $type, $sql = '', $bindings = array() )
  6209. {
  6210. $sql = $this->writer->glueSQLCondition( $sql );
  6211. $rows = $this->relatedRows( $bean, $type, $sql, $bindings );
  6212. $links = array();
  6213. foreach ( $rows as $key => $row ) {
  6214. if ( !isset( $links[$row['id']] ) ) {
  6215. $links[$row['id']] = array();
  6216. }
  6217. $links[$row['id']][] = $row['linked_by'];
  6218. unset( $rows[$key]['linked_by'] );
  6219. }
  6220. $beans = $this->oodb->convertToBeans( $type, $rows );
  6221. foreach ( $beans as $bean ) {
  6222. $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
  6223. }
  6224. return $beans;
  6225. }
  6226. }
  6227. }
  6228. namespace RedBeanPHP {
  6229. use RedBeanPHP\ToolBox as ToolBox;
  6230. use RedBeanPHP\OODBBean as OODBBean;
  6231. /**
  6232. * Bean Helper Interface
  6233. *
  6234. * @file RedBean/IBeanHelper.php
  6235. * @desc Interface for Bean Helper.
  6236. * @author Gabor de Mooij and the RedBeanPHP Community
  6237. * @license BSD/GPLv2
  6238. *
  6239. * Interface for Bean Helper.
  6240. * A little bolt that glues the whole machinery together.
  6241. *
  6242. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6243. * This source file is subject to the BSD/GPLv2 License that is bundled
  6244. * with this source code in the file license.txt.
  6245. */
  6246. interface BeanHelper
  6247. {
  6248. /**
  6249. * Returns a toolbox to empower the bean.
  6250. * This allows beans to perform OODB operations by themselves,
  6251. * as such the bean is a proxy for OODB. This allows beans to implement
  6252. * their magic getters and setters and return lists.
  6253. *
  6254. * @return ToolBox $toolbox toolbox
  6255. */
  6256. public function getToolbox();
  6257. /**
  6258. * Does approximately the same as getToolbox but also extracts the
  6259. * toolbox for you.
  6260. * This method returns a list with all toolbox items in Toolbox Constructor order:
  6261. * OODB, adapter, writer and finally the toolbox itself!.
  6262. *
  6263. * @return array
  6264. */
  6265. public function getExtractedToolbox();
  6266. /**
  6267. * Given a certain bean this method will
  6268. * return the corresponding model.
  6269. *
  6270. * @param OODBBean $bean
  6271. *
  6272. * @return string
  6273. */
  6274. public function getModelForBean( OODBBean $bean );
  6275. }
  6276. }
  6277. namespace RedBeanPHP\BeanHelper {
  6278. use RedBeanPHP\BeanHelper as BeanHelper;
  6279. use RedBeanPHP\Facade as Facade;
  6280. use RedBeanPHP\OODBBean as OODBBean;
  6281. use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
  6282. /**
  6283. * Bean Helper.
  6284. * The Bean helper helps beans to access access the toolbox and
  6285. * FUSE models. This Bean Helper makes use of the facade to obtain a
  6286. * reference to the toolbox.
  6287. *
  6288. * @file RedBean/BeanHelperFacade.php
  6289. * @desc Finds the toolbox for the bean.
  6290. * @author Gabor de Mooij and the RedBeanPHP Community
  6291. * @license BSD/GPLv2
  6292. *
  6293. * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6294. * This source file is subject to the BSD/GPLv2 License that is bundled
  6295. * with this source code in the file license.txt.
  6296. */
  6297. class SimpleFacadeBeanHelper implements BeanHelper
  6298. {
  6299. /**
  6300. * Factory function to create instance of Simple Model, if any.
  6301. *
  6302. * @var closure
  6303. */
  6304. private static $factory = null;
  6305. /**
  6306. * @see BeanHelper::getToolbox
  6307. */
  6308. public function getToolbox()
  6309. {
  6310. return Facade::getToolBox();
  6311. }
  6312. /**
  6313. * @see BeanHelper::getModelForBean
  6314. */
  6315. public function getModelForBean( OODBBean $bean )
  6316. {
  6317. $model = $bean->getMeta( 'type' );
  6318. $prefix = defined( 'REDBEAN_MODEL_PREFIX' ) ? REDBEAN_MODEL_PREFIX : '\\Model_';
  6319. if ( strpos( $model, '_' ) !== FALSE ) {
  6320. $modelParts = explode( '_', $model );
  6321. $modelName = '';
  6322. foreach( $modelParts as $part ) {
  6323. $modelName .= ucfirst( $part );
  6324. }
  6325. $modelName = $prefix . $modelName;
  6326. if ( !class_exists( $modelName ) ) {
  6327. //second try
  6328. $modelName = $prefix . ucfirst( $model );
  6329. if ( !class_exists( $modelName ) ) {
  6330. return NULL;
  6331. }
  6332. }
  6333. } else {
  6334. $modelName = $prefix . ucfirst( $model );
  6335. if ( !class_exists( $modelName ) ) {
  6336. return NULL;
  6337. }
  6338. }
  6339. $obj = self::factory( $modelName );
  6340. $obj->loadBean( $bean );
  6341. return $obj;
  6342. }
  6343. /**
  6344. * @see BeanHelper::getExtractedToolbox
  6345. */
  6346. public function getExtractedToolbox()
  6347. {
  6348. return Facade::getExtractedToolbox();
  6349. }
  6350. /**
  6351. * Factory method using a customizable factory function to create
  6352. * the instance of the Simple Model.
  6353. *
  6354. * @param string $modelClassName name of the class
  6355. *
  6356. * @return SimpleModel
  6357. */
  6358. public static function factory( $modelClassName )
  6359. {
  6360. $factory = self::$factory;
  6361. return ( $factory ) ? $factory( $modelClassName ) : new $modelClassName();
  6362. }
  6363. /**
  6364. * Sets the factory function to create the model when using FUSE
  6365. * to connect a bean to a model.
  6366. *
  6367. * @param closure $factory
  6368. *
  6369. * @return void
  6370. */
  6371. public static function setFactoryFunction( $factory )
  6372. {
  6373. self::$factory = $factory;
  6374. }
  6375. }
  6376. }
  6377. namespace RedBeanPHP {
  6378. use RedBeanPHP\OODBBean as OODBBean;
  6379. /**
  6380. * SimpleModel
  6381. * Base Model For All RedBeanPHP Models using FUSE.
  6382. *
  6383. * @file RedBean/SimpleModel.php
  6384. * @desc Part of FUSE
  6385. * @author Gabor de Mooij and the RedBeanPHP Team
  6386. * @license BSD/GPLv2
  6387. *
  6388. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6389. * This source file is subject to the BSD/GPLv2 License that is bundled
  6390. * with this source code in the file license.txt.
  6391. */
  6392. class SimpleModel
  6393. {
  6394. /**
  6395. * @var OODBBean
  6396. */
  6397. protected $bean;
  6398. /**
  6399. * Used by FUSE: the ModelHelper class to connect a bean to a model.
  6400. * This method loads a bean in the model.
  6401. *
  6402. * @param OODBBean $bean bean
  6403. *
  6404. * @return void
  6405. */
  6406. public function loadBean( OODBBean $bean )
  6407. {
  6408. $this->bean = $bean;
  6409. }
  6410. /**
  6411. * Magic Getter to make the bean properties available from
  6412. * the $this-scope.
  6413. *
  6414. * @note this method returns a value, not a reference!
  6415. * To obtain a reference unbox the bean first!
  6416. *
  6417. * @param string $prop property
  6418. *
  6419. * @return mixed
  6420. */
  6421. public function __get( $prop )
  6422. {
  6423. return $this->bean->$prop;
  6424. }
  6425. /**
  6426. * Magic Setter.
  6427. * Sets the value directly as a bean property.
  6428. *
  6429. * @param string $prop property
  6430. * @param mixed $value value
  6431. *
  6432. * @return void
  6433. */
  6434. public function __set( $prop, $value )
  6435. {
  6436. $this->bean->$prop = $value;
  6437. }
  6438. /**
  6439. * Isset implementation.
  6440. * Implements the isset function for array-like access.
  6441. *
  6442. * @param string $key key to check
  6443. *
  6444. * @return boolean
  6445. */
  6446. public function __isset( $key )
  6447. {
  6448. return isset( $this->bean->$key );
  6449. }
  6450. /**
  6451. * Box the bean using the current model.
  6452. * This method wraps the current bean in this model.
  6453. * This method can be reached using FUSE through a simple
  6454. * OODBBean. The method returns a RedBeanPHP Simple Model.
  6455. * This is useful if you would like to rely on PHP type hinting.
  6456. * You can box your beans before passing them to functions or methods
  6457. * with typed parameters.
  6458. *
  6459. * @return SimpleModel
  6460. */
  6461. public function box()
  6462. {
  6463. return $this;
  6464. }
  6465. /**
  6466. * Unbox the bean from the model.
  6467. * This method returns the bean inside the model.
  6468. *
  6469. * @return OODBBean
  6470. */
  6471. public function unbox()
  6472. {
  6473. return $this->bean;
  6474. }
  6475. }
  6476. }
  6477. namespace RedBeanPHP {
  6478. use RedBeanPHP\Observer as Observer;
  6479. use RedBeanPHP\OODBBean as OODBBean;
  6480. use RedBeanPHP\Observable as Observable;
  6481. /**
  6482. * RedBean Model Helper
  6483. *
  6484. * @file RedBean/ModelHelper.php
  6485. * @desc Connects beans to models, in essence
  6486. * @author Gabor de Mooij and the RedBeanPHP Community
  6487. * @license BSD/GPLv2
  6488. *
  6489. * This is the core of so-called FUSE.
  6490. *
  6491. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6492. * This source file is subject to the BSD/GPLv2 License that is bundled
  6493. * with this source code in the file license.txt.
  6494. */
  6495. class SimpleModelHelper implements Observer
  6496. {
  6497. /**
  6498. * @see Observer::onEvent
  6499. */
  6500. public function onEvent( $eventName, $bean )
  6501. {
  6502. $bean->$eventName();
  6503. }
  6504. /**
  6505. * Attaches the FUSE event listeners. Now the Model Helper will listen for
  6506. * CRUD events. If a CRUD event occurs it will send a signal to the model
  6507. * that belongs to the CRUD bean and this model will take over control from
  6508. * there.
  6509. *
  6510. * @param Observable $observable
  6511. *
  6512. * @return void
  6513. */
  6514. public function attachEventListeners( Observable $observable )
  6515. {
  6516. foreach ( array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ) as $e ) {
  6517. $observable->addEventListener( $e, $this );
  6518. }
  6519. }
  6520. }
  6521. }
  6522. namespace RedBeanPHP {
  6523. use RedBeanPHP\ToolBox as ToolBox;
  6524. use RedBeanPHP\AssociationManager as AssociationManager;
  6525. use RedBeanPHP\OODBBean as OODBBean;
  6526. /**
  6527. * RedBean Tag Manager.
  6528. * The tag manager offers an easy way to quickly implement basic tagging
  6529. * functionality.
  6530. *
  6531. * @file RedBean/TagManager.php
  6532. * @desc RedBean Tag Manager
  6533. * @author Gabor de Mooij and the RedBeanPHP community
  6534. * @license BSD/GPLv2
  6535. *
  6536. * Provides methods to tag beans and perform tag-based searches in the
  6537. * bean database.
  6538. *
  6539. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6540. * This source file is subject to the BSD/GPLv2 License that is bundled
  6541. * with this source code in the file license.txt.
  6542. */
  6543. class TagManager
  6544. {
  6545. /**
  6546. * @var ToolBox
  6547. */
  6548. protected $toolbox;
  6549. /**
  6550. * @var AssociationManager
  6551. */
  6552. protected $associationManager;
  6553. /**
  6554. * @var OODBBean
  6555. */
  6556. protected $redbean;
  6557. /**
  6558. * Checks if the argument is a comma separated string, in this case
  6559. * it will split the string into words and return an array instead.
  6560. * In case of an array the argument will be returned 'as is'.
  6561. *
  6562. * @param array|string $tagList list of tags
  6563. *
  6564. * @return array
  6565. */
  6566. private function extractTagsIfNeeded( $tagList )
  6567. {
  6568. if ( $tagList !== FALSE && !is_array( $tagList ) ) {
  6569. $tags = explode( ',', (string) $tagList );
  6570. } else {
  6571. $tags = $tagList;
  6572. }
  6573. return $tags;
  6574. }
  6575. /**
  6576. * Constructor.
  6577. * The tag manager offers an easy way to quickly implement basic tagging
  6578. * functionality.
  6579. *
  6580. * @param ToolBox $toolbox
  6581. */
  6582. public function __construct( ToolBox $toolbox )
  6583. {
  6584. $this->toolbox = $toolbox;
  6585. $this->redbean = $toolbox->getRedBean();
  6586. $this->associationManager = $this->redbean->getAssociationManager();
  6587. }
  6588. /**
  6589. * Finds a tag bean by it's title.
  6590. * Internal method.
  6591. *
  6592. * @param string $title title
  6593. *
  6594. * @return OODBBean
  6595. */
  6596. protected function findTagByTitle( $title )
  6597. {
  6598. $beans = $this->redbean->find( 'tag', array( 'title' => array( $title ) ) );
  6599. if ( $beans ) {
  6600. $bean = reset( $beans );
  6601. return $bean;
  6602. }
  6603. return NULL;
  6604. }
  6605. /**
  6606. * Tests whether a bean has been associated with one ore more
  6607. * of the listed tags. If the third parameter is TRUE this method
  6608. * will return TRUE only if all tags that have been specified are indeed
  6609. * associated with the given bean, otherwise FALSE.
  6610. * If the third parameter is FALSE this
  6611. * method will return TRUE if one of the tags matches, FALSE if none
  6612. * match.
  6613. *
  6614. * Tag list can be either an array with tag names or a comma separated list
  6615. * of tag names.
  6616. *
  6617. * @param OODBBean $bean bean to check for tags
  6618. * @param array|string $tags list of tags
  6619. * @param boolean $all whether they must all match or just some
  6620. *
  6621. * @return boolean
  6622. */
  6623. public function hasTag( $bean, $tags, $all = FALSE )
  6624. {
  6625. $foundtags = $this->tag( $bean );
  6626. $tags = $this->extractTagsIfNeeded( $tags );
  6627. $same = array_intersect( $tags, $foundtags );
  6628. if ( $all ) {
  6629. return ( implode( ',', $same ) === implode( ',', $tags ) );
  6630. }
  6631. return (bool) ( count( $same ) > 0 );
  6632. }
  6633. /**
  6634. * Removes all sepcified tags from the bean. The tags specified in
  6635. * the second parameter will no longer be associated with the bean.
  6636. *
  6637. * Tag list can be either an array with tag names or a comma separated list
  6638. * of tag names.
  6639. *
  6640. * @param OODBBean $bean tagged bean
  6641. * @param array|string $tagList list of tags (names)
  6642. *
  6643. * @return void
  6644. */
  6645. public function untag( $bean, $tagList )
  6646. {
  6647. $tags = $this->extractTagsIfNeeded( $tagList );
  6648. foreach ( $tags as $tag ) {
  6649. if ( $t = $this->findTagByTitle( $tag ) ) {
  6650. $this->associationManager->unassociate( $bean, $t );
  6651. }
  6652. }
  6653. }
  6654. /**
  6655. * Tags a bean or returns tags associated with a bean.
  6656. * If $tagList is NULL or omitted this method will return a
  6657. * comma separated list of tags associated with the bean provided.
  6658. * If $tagList is a comma separated list (string) of tags all tags will
  6659. * be associated with the bean.
  6660. * You may also pass an array instead of a string.
  6661. *
  6662. * Tag list can be either an array with tag names or a comma separated list
  6663. * of tag names.
  6664. *
  6665. * @param OODBBean $bean bean to be tagged
  6666. * @param array|string $tagList a list of tags
  6667. *
  6668. * @return array
  6669. */
  6670. public function tag( OODBBean $bean, $tagList = NULL )
  6671. {
  6672. if ( is_null( $tagList ) ) {
  6673. $tags = $bean->sharedTag;
  6674. $foundTags = array();
  6675. foreach ( $tags as $tag ) {
  6676. $foundTags[] = $tag->title;
  6677. }
  6678. return $foundTags;
  6679. }
  6680. $this->associationManager->clearRelations( $bean, 'tag' );
  6681. $this->addTags( $bean, $tagList );
  6682. return $tagList;
  6683. }
  6684. /**
  6685. * Adds tags to a bean.
  6686. * If $tagList is a comma separated list of tags all tags will
  6687. * be associated with the bean.
  6688. * You may also pass an array instead of a string.
  6689. *
  6690. * Tag list can be either an array with tag names or a comma separated list
  6691. * of tag names.
  6692. *
  6693. * @param OODBBean $bean bean to add tags to
  6694. * @param array|string $tagList list of tags to add to bean
  6695. *
  6696. * @return void
  6697. */
  6698. public function addTags( OODBBean $bean, $tagList )
  6699. {
  6700. $tags = $this->extractTagsIfNeeded( $tagList );
  6701. if ( $tagList === FALSE ) {
  6702. return;
  6703. }
  6704. foreach ( $tags as $tag ) {
  6705. if ( !$t = $this->findTagByTitle( $tag ) ) {
  6706. $t = $this->redbean->dispense( 'tag' );
  6707. $t->title = $tag;
  6708. $this->redbean->store( $t );
  6709. }
  6710. $this->associationManager->associate( $bean, $t );
  6711. }
  6712. }
  6713. /**
  6714. * Returns all beans that have been tagged with one or more
  6715. * of the specified tags.
  6716. *
  6717. * Tag list can be either an array with tag names or a comma separated list
  6718. * of tag names.
  6719. *
  6720. * @param string $beanType type of bean you are looking for
  6721. * @param array|string $tagList list of tags to match
  6722. *
  6723. * @return array
  6724. */
  6725. public function tagged( $beanType, $tagList )
  6726. {
  6727. $tags = $this->extractTagsIfNeeded( $tagList );
  6728. $collection = array();
  6729. $tags = $this->redbean->find( 'tag', array( 'title' => $tags ) );
  6730. $list = 'shared'.ucfirst( $beanType );
  6731. if ( is_array( $tags ) && count( $tags ) > 0 ) {
  6732. foreach($tags as $tag) {
  6733. $collection += $tag->$list;
  6734. }
  6735. }
  6736. return $collection;
  6737. }
  6738. /**
  6739. * Returns all beans that have been tagged with ALL of the tags given.
  6740. *
  6741. * Tag list can be either an array with tag names or a comma separated list
  6742. * of tag names.
  6743. *
  6744. * @param string $beanType type of bean you are looking for
  6745. * @param array|string $tagList list of tags to match
  6746. *
  6747. * @return array
  6748. */
  6749. public function taggedAll( $beanType, $tagList )
  6750. {
  6751. $tags = $this->extractTagsIfNeeded( $tagList );
  6752. $beans = array();
  6753. foreach ( $tags as $tag ) {
  6754. $beans = $this->tagged( $beanType, $tag );
  6755. if ( isset( $oldBeans ) ) {
  6756. $beans = array_intersect_assoc( $beans, $oldBeans );
  6757. }
  6758. $oldBeans = $beans;
  6759. }
  6760. return $beans;
  6761. }
  6762. }
  6763. }
  6764. namespace RedBeanPHP {
  6765. use RedBeanPHP\ToolBox as ToolBox;
  6766. use RedBeanPHP\OODBBean as OODBBean;
  6767. /**
  6768. * Label Maker
  6769. *
  6770. * @file RedBean/LabelMaker.php
  6771. * @desc Makes so-called label beans
  6772. * @author Gabor de Mooij and the RedBeanPHP Community
  6773. * @license BSD/GPLv2
  6774. *
  6775. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6776. * This source file is subject to the BSD/GPLv2 License that is bundled
  6777. * with this source code in the file license.txt.
  6778. */
  6779. class LabelMaker
  6780. {
  6781. /**
  6782. * @var ToolBox
  6783. */
  6784. protected $toolbox;
  6785. /**
  6786. * Constructor.
  6787. *
  6788. * @param ToolBox $toolbox
  6789. */
  6790. public function __construct( ToolBox $toolbox )
  6791. {
  6792. $this->toolbox = $toolbox;
  6793. }
  6794. /**
  6795. * A label is a bean with only an id, type and name property.
  6796. * This function will dispense beans for all entries in the array. The
  6797. * values of the array will be assigned to the name property of each
  6798. * individual bean.
  6799. *
  6800. * @param string $type type of beans you would like to have
  6801. * @param array $labels list of labels, names for each bean
  6802. *
  6803. * @return array
  6804. */
  6805. public function dispenseLabels( $type, $labels )
  6806. {
  6807. $labelBeans = array();
  6808. foreach ( $labels as $label ) {
  6809. $labelBean = $this->toolbox->getRedBean()->dispense( $type );
  6810. $labelBean->name = $label;
  6811. $labelBeans[] = $labelBean;
  6812. }
  6813. return $labelBeans;
  6814. }
  6815. /**
  6816. * Gathers labels from beans. This function loops through the beans,
  6817. * collects the values of the name properties of each individual bean
  6818. * and stores the names in a new array. The array then gets sorted using the
  6819. * default sort function of PHP (sort).
  6820. *
  6821. * @param array $beans list of beans to loop
  6822. *
  6823. * @return array
  6824. */
  6825. public function gatherLabels( $beans )
  6826. {
  6827. $labels = array();
  6828. foreach ( $beans as $bean ) {
  6829. $labels[] = $bean->name;
  6830. }
  6831. sort( $labels );
  6832. return $labels;
  6833. }
  6834. /**
  6835. * Returns a label or an array of labels for use as ENUMs.
  6836. *
  6837. * @param string $enum ENUM specification for label
  6838. *
  6839. * @return array|OODBBean
  6840. */
  6841. public function enum( $enum )
  6842. {
  6843. $oodb = $this->toolbox->getRedBean();
  6844. if ( strpos( $enum, ':' ) === FALSE ) {
  6845. $type = $enum;
  6846. $value = FALSE;
  6847. } else {
  6848. list( $type, $value ) = explode( ':', $enum );
  6849. $value = preg_replace( '/\W+/', '_', strtoupper( trim( $value ) ) );
  6850. }
  6851. $values = $oodb->find( $type );
  6852. if ( $value === FALSE ) {
  6853. return $values;
  6854. }
  6855. foreach( $values as $enumItem ) {
  6856. if ( $enumItem->name === $value ) return $enumItem;
  6857. }
  6858. $newEnumItems = $this->dispenseLabels( $type, array( $value ) );
  6859. $newEnumItem = reset( $newEnumItems );
  6860. $oodb->store( $newEnumItem );
  6861. return $newEnumItem;
  6862. }
  6863. }
  6864. }
  6865. namespace RedBeanPHP {
  6866. use RedBeanPHP\ToolBox as ToolBox;
  6867. use RedBeanPHP\OODB as OODB;
  6868. use RedBeanPHP\QueryWriter as QueryWriter;
  6869. use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
  6870. use RedBeanPHP\AssociationManager as AssociationManager;
  6871. use RedBeanPHP\TagManager as TagManager;
  6872. use RedBeanPHP\DuplicationManager as DuplicationManager;
  6873. use RedBeanPHP\LabelMaker as LabelMaker;
  6874. use RedBeanPHP\Finder as Finder;
  6875. use RedBeanPHP\RedException\SQL as SQL;
  6876. use RedBeanPHP\RedException\Security as Security;
  6877. use RedBeanPHP\Logger as Logger;
  6878. use RedBeanPHP\Logger\RDefault as RDefault;
  6879. use RedBeanPHP\OODBBean as OODBBean;
  6880. use RedBeanPHP\SimpleModel as SimpleModel;
  6881. use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
  6882. use RedBeanPHP\Adapter as Adapter;
  6883. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  6884. use RedBeanPHP\RedException as RedException;
  6885. use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
  6886. use RedBeanPHP\Driver\RPDO as RPDO;
  6887. /**
  6888. * RedBean Facade
  6889. *
  6890. * Version Information
  6891. * RedBean Version @version 4
  6892. *
  6893. * @file RedBean/Facade.php
  6894. * @desc Convenience class for RedBeanPHP.
  6895. * @author Gabor de Mooij and the RedBeanPHP Community
  6896. * @license BSD/GPLv2
  6897. *
  6898. * This class hides the object landscape of
  6899. * RedBeanPHP behind a single letter class providing
  6900. * almost all functionality with simple static calls.
  6901. *
  6902. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  6903. * This source file is subject to the BSD/GPLv2 License that is bundled
  6904. * with this source code in the file license.txt.
  6905. */
  6906. class Facade
  6907. {
  6908. /**
  6909. * RedBeanPHP version constant.
  6910. */
  6911. const C_REDBEANPHP_VERSION = '4.0';
  6912. /**
  6913. * @var array
  6914. */
  6915. private static $toolboxes = array();
  6916. /**
  6917. * @var ToolBox
  6918. */
  6919. private static $toolbox;
  6920. /**
  6921. * @var OODB
  6922. */
  6923. private static $redbean;
  6924. /**
  6925. * @var QueryWriter
  6926. */
  6927. private static $writer;
  6928. /**
  6929. * @var DBAdapter
  6930. */
  6931. private static $adapter;
  6932. /**
  6933. * @var AssociationManager
  6934. */
  6935. private static $associationManager;
  6936. /**
  6937. * @var TagManager
  6938. */
  6939. private static $tagManager;
  6940. /**
  6941. * @var DuplicationManager
  6942. */
  6943. private static $duplicationManager;
  6944. /**
  6945. * @var LabelMaker
  6946. */
  6947. private static $labelMaker;
  6948. /**
  6949. * @var Finder
  6950. */
  6951. private static $finder;
  6952. /**
  6953. * @var string
  6954. */
  6955. private static $currentDB = '';
  6956. /**
  6957. * @var array
  6958. */
  6959. private static $plugins = array();
  6960. /**
  6961. * Internal Query function, executes the desired query. Used by
  6962. * all facade query functions. This keeps things DRY.
  6963. *
  6964. * @throws SQL
  6965. *
  6966. * @param string $method desired query method (i.e. 'cell', 'col', 'exec' etc..)
  6967. * @param string $sql the sql you want to execute
  6968. * @param array $bindings array of values to be bound to query statement
  6969. *
  6970. * @return array
  6971. */
  6972. private static function query( $method, $sql, $bindings )
  6973. {
  6974. if ( !self::$redbean->isFrozen() ) {
  6975. try {
  6976. $rs = Facade::$adapter->$method( $sql, $bindings );
  6977. } catch ( SQL $exception ) {
  6978. if ( self::$writer->sqlStateIn( $exception->getSQLState(),
  6979. array(
  6980. QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
  6981. QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
  6982. )
  6983. ) {
  6984. return ( $method === 'getCell' ) ? NULL : array();
  6985. } else {
  6986. throw $exception;
  6987. }
  6988. }
  6989. return $rs;
  6990. } else {
  6991. return Facade::$adapter->$method( $sql, $bindings );
  6992. }
  6993. }
  6994. /**
  6995. * Returns the RedBeanPHP version string.
  6996. * The RedBeanPHP version string always has the same format "X.Y"
  6997. * where X is the major version number and Y is the minor version number.
  6998. * Point releases are not mentioned in the version string.
  6999. *
  7000. * @return string
  7001. */
  7002. public static function getVersion()
  7003. {
  7004. return self::C_REDBEANPHP_VERSION;
  7005. }
  7006. /**
  7007. * Kickstarts redbean for you. This method should be called before you start using
  7008. * RedBean. The Setup() method can be called without any arguments, in this case it will
  7009. * try to create a SQLite database in /tmp called red.db (this only works on UNIX-like systems).
  7010. *
  7011. * @param string $dsn Database connection string
  7012. * @param string $username Username for database
  7013. * @param string $password Password for database
  7014. * @param boolean $frozen TRUE if you want to setup in frozen mode
  7015. *
  7016. * @return ToolBox
  7017. */
  7018. public static function setup( $dsn = NULL, $username = NULL, $password = NULL, $frozen = FALSE )
  7019. {
  7020. if ( is_null( $dsn ) ) {
  7021. $dsn = 'sqlite:/' . sys_get_temp_dir() . '/red.db';
  7022. }
  7023. self::addDatabase( 'default', $dsn, $username, $password, $frozen );
  7024. self::selectDatabase( 'default' );
  7025. return self::$toolbox;
  7026. }
  7027. /**
  7028. * Starts a transaction within a closure (or other valid callback).
  7029. * If an\Exception is thrown inside, the operation is automatically rolled back.
  7030. * If no\Exception happens, it commits automatically.
  7031. * It also supports (simulated) nested transactions (that is useful when
  7032. * you have many methods that needs transactions but are unaware of
  7033. * each other).
  7034. * ex:
  7035. * $from = 1;
  7036. * $to = 2;
  7037. * $amount = 300;
  7038. *
  7039. * R::transaction(function() use($from, $to, $amount)
  7040. * {
  7041. * $accountFrom = R::load('account', $from);
  7042. * $accountTo = R::load('account', $to);
  7043. *
  7044. * $accountFrom->money -= $amount;
  7045. * $accountTo->money += $amount;
  7046. *
  7047. * R::store($accountFrom);
  7048. * R::store($accountTo);
  7049. * });
  7050. *
  7051. * @param callable $callback Closure (or other callable) with the transaction logic
  7052. *
  7053. * @throws Security
  7054. *
  7055. * @return mixed
  7056. *
  7057. */
  7058. public static function transaction( $callback )
  7059. {
  7060. if ( !is_callable( $callback ) ) {
  7061. throw new RedException( 'R::transaction needs a valid callback.' );
  7062. }
  7063. static $depth = 0;
  7064. $result = null;
  7065. try {
  7066. if ( $depth == 0 ) {
  7067. self::begin();
  7068. }
  7069. $depth++;
  7070. $result = call_user_func( $callback ); //maintain 5.2 compatibility
  7071. $depth--;
  7072. if ( $depth == 0 ) {
  7073. self::commit();
  7074. }
  7075. } catch (\Exception $exception ) {
  7076. $depth--;
  7077. if ( $depth == 0 ) {
  7078. self::rollback();
  7079. }
  7080. throw $exception;
  7081. }
  7082. return $result;
  7083. }
  7084. /**
  7085. * Adds a database to the facade, afterwards you can select the database using
  7086. * selectDatabase($key), where $key is the name you assigned to this database.
  7087. *
  7088. * Usage:
  7089. *
  7090. * R::addDatabase( 'database-1', 'sqlite:/tmp/db1.txt' );
  7091. * R::selectDatabase( 'database-1' ); //to select database again
  7092. *
  7093. * This method allows you to dynamically add (and select) new databases
  7094. * to the facade. Adding a database with the same key will cause an exception.
  7095. *
  7096. * @param string $key ID for the database
  7097. * @param string $dsn DSN for the database
  7098. * @param string $user User for connection
  7099. * @param NULL|string $pass Password for connection
  7100. * @param bool $frozen Whether this database is frozen or not
  7101. *
  7102. * @return void
  7103. */
  7104. public static function addDatabase( $key, $dsn, $user = NULL, $pass = NULL, $frozen = FALSE )
  7105. {
  7106. if ( isset( self::$toolboxes[$key] ) ) {
  7107. throw new RedException( 'A database has already be specified for this key.' );
  7108. }
  7109. if ( is_object($dsn) ) {
  7110. $db = new RPDO( $dsn );
  7111. $dbType = $db->getDatabaseType();
  7112. } else {
  7113. $db = new RPDO( $dsn, $user, $pass, TRUE );
  7114. $dbType = substr( $dsn, 0, strpos( $dsn, ':' ) );
  7115. }
  7116. $adapter = new DBAdapter( $db );
  7117. $writers = array('pgsql' => 'PostgreSQL',
  7118. 'sqlite' => 'SQLiteT',
  7119. 'cubrid' => 'CUBRID',
  7120. 'mysql' => 'MySQL');
  7121. $wkey = trim( strtolower( $dbType ) );
  7122. if ( !isset( $writers[$wkey] ) ) trigger_error( 'Unsupported DSN: '.$wkey );
  7123. $writerClass = '\\RedBeanPHP\\QueryWriter\\'.$writers[$wkey];
  7124. $writer = new $writerClass( $adapter );
  7125. $redbean = new OODB( $writer );
  7126. $redbean->freeze( ( $frozen === TRUE ) );
  7127. self::$toolboxes[$key] = new ToolBox( $redbean, $adapter, $writer );
  7128. }
  7129. /**
  7130. * Selects a different database for the Facade to work with.
  7131. * If you use the R::setup() you don't need this method. This method is meant
  7132. * for multiple database setups. This method selects the database identified by the
  7133. * database ID ($key). Use addDatabase() to add a new database, which in turn
  7134. * can be selected using selectDatabase(). If you use R::setup(), the resulting
  7135. * database will be stored under key 'default', to switch (back) to this database
  7136. * use R::selectDatabase( 'default' ). This method returns TRUE if the database has been
  7137. * switched and FALSE otherwise (for instance if you already using the specified database).
  7138. *
  7139. * @param string $key Key of the database to select
  7140. *
  7141. * @return boolean
  7142. */
  7143. public static function selectDatabase( $key )
  7144. {
  7145. if ( self::$currentDB === $key ) {
  7146. return FALSE;
  7147. }
  7148. self::configureFacadeWithToolbox( self::$toolboxes[$key] );
  7149. self::$currentDB = $key;
  7150. return TRUE;
  7151. }
  7152. /**
  7153. * Toggles DEBUG mode.
  7154. * In Debug mode all SQL that happens under the hood will
  7155. * be printed to the screen or logged by provided logger.
  7156. * If no database connection has been configured using R::setup() or
  7157. * R::selectDatabase() this method will throw an exception.
  7158. * Returns the attached logger instance.
  7159. *
  7160. * @param boolean $tf
  7161. * @param integer $mode (0 = to STDOUT, 1 = to ARRAY)
  7162. *
  7163. * @throws Security
  7164. *
  7165. * @return Logger\RDefault
  7166. */
  7167. public static function debug( $tf = TRUE, $mode = 0 )
  7168. {
  7169. $logger = new RDefault;
  7170. if ( !isset( self::$adapter ) ) {
  7171. throw new RedException( 'Use R::setup() first.' );
  7172. }
  7173. $logger->setMode($mode);
  7174. self::$adapter->getDatabase()->setDebugMode( $tf, $logger );
  7175. return $logger;
  7176. }
  7177. /**
  7178. * Inspects the database schema. If you pass the type of a bean this
  7179. * method will return the fields of its table in the database.
  7180. * The keys of this array will be the field names and the values will be
  7181. * the column types used to store their values.
  7182. * If no type is passed, this method returns a list of all tables in the database.
  7183. *
  7184. * @param string $type Type of bean (i.e. table) you want to inspect
  7185. *
  7186. * @return array
  7187. */
  7188. public static function inspect( $type = NULL )
  7189. {
  7190. return ($type === NULL) ? self::$writer->getTables() : self::$writer->getColumns( $type );
  7191. }
  7192. /**
  7193. * Stores a bean in the database. This method takes a
  7194. * OODBBean Bean Object $bean and stores it
  7195. * in the database. If the database schema is not compatible
  7196. * with this bean and RedBean runs in fluid mode the schema
  7197. * will be altered to store the bean correctly.
  7198. * If the database schema is not compatible with this bean and
  7199. * RedBean runs in frozen mode it will throw an exception.
  7200. * This function returns the primary key ID of the inserted
  7201. * bean.
  7202. *
  7203. * The return value is an integer if possible. If it is not possible to
  7204. * represent the value as an integer a string will be returned.
  7205. *
  7206. * @param OODBBean|SimpleModel $bean bean to store
  7207. *
  7208. * @return integer|string
  7209. *
  7210. * @throws Security
  7211. */
  7212. public static function store( $bean )
  7213. {
  7214. return self::$redbean->store( $bean );
  7215. }
  7216. /**
  7217. * Toggles fluid or frozen mode. In fluid mode the database
  7218. * structure is adjusted to accomodate your objects. In frozen mode
  7219. * this is not the case.
  7220. *
  7221. * You can also pass an array containing a selection of frozen types.
  7222. * Let's call this chilly mode, it's just like fluid mode except that
  7223. * certain types (i.e. tables) aren't touched.
  7224. *
  7225. * @param boolean|array $trueFalse
  7226. */
  7227. public static function freeze( $tf = TRUE )
  7228. {
  7229. self::$redbean->freeze( $tf );
  7230. }
  7231. /**
  7232. * Loads multiple types of beans with the same ID.
  7233. * This might look like a strange method, however it can be useful
  7234. * for loading a one-to-one relation.
  7235. *
  7236. * Usage:
  7237. * list($author, $bio) = R::load('author, bio', $id);
  7238. *
  7239. * @param string|array $types
  7240. * @param mixed $id
  7241. *
  7242. * @return OODBBean
  7243. */
  7244. public static function loadMulti( $types, $id )
  7245. {
  7246. if ( is_string( $types ) ) {
  7247. $types = explode( ',', $types );
  7248. }
  7249. if ( !is_array( $types ) ) {
  7250. return array();
  7251. }
  7252. foreach ( $types as $k => $typeItem ) {
  7253. $types[$k] = self::$redbean->load( $typeItem, $id );
  7254. }
  7255. return $types;
  7256. }
  7257. /**
  7258. * Loads a bean from the object database.
  7259. * It searches for a OODBBean Bean Object in the
  7260. * database. It does not matter how this bean has been stored.
  7261. * RedBean uses the primary key ID $id and the string $type
  7262. * to find the bean. The $type specifies what kind of bean you
  7263. * are looking for; this is the same type as used with the
  7264. * dispense() function. If RedBean finds the bean it will return
  7265. * the OODB Bean object; if it cannot find the bean
  7266. * RedBean will return a new bean of type $type and with
  7267. * primary key ID 0. In the latter case it acts basically the
  7268. * same as dispense().
  7269. *
  7270. * Important note:
  7271. * If the bean cannot be found in the database a new bean of
  7272. * the specified type will be generated and returned.
  7273. *
  7274. * @param string $type type of bean you want to load
  7275. * @param integer $id ID of the bean you want to load
  7276. *
  7277. * @throws SQL
  7278. *
  7279. * @return OODBBean
  7280. */
  7281. public static function load( $type, $id )
  7282. {
  7283. return self::$redbean->load( $type, $id );
  7284. }
  7285. /**
  7286. * Removes a bean from the database.
  7287. * This function will remove the specified OODBBean
  7288. * Bean Object from the database.
  7289. *
  7290. * @param OODBBean|SimpleModel $bean bean you want to remove from database
  7291. *
  7292. * @return void
  7293. *
  7294. * @throws Security
  7295. */
  7296. public static function trash( $bean )
  7297. {
  7298. self::$redbean->trash( $bean );
  7299. }
  7300. /**
  7301. * Dispenses a new RedBean OODB Bean for use with
  7302. * the rest of the methods.
  7303. *
  7304. * @param string|array $typeOrBeanArray type or bean array to import
  7305. * @param integer $number number of beans to dispense
  7306. * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
  7307. *
  7308. * @return array|OODBBean
  7309. *
  7310. * @throws Security
  7311. */
  7312. public static function dispense( $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE )
  7313. {
  7314. if ( is_array($typeOrBeanArray) ) {
  7315. if ( !isset( $typeOrBeanArray['_type'] ) ) throw new RedException('Missing _type field.');
  7316. $import = $typeOrBeanArray;
  7317. $type = $import['_type'];
  7318. unset( $import['_type'] );
  7319. } else {
  7320. $type = $typeOrBeanArray;
  7321. }
  7322. if ( !preg_match( '/^[a-z0-9]+$/', $type ) ) {
  7323. throw new RedException( 'Invalid type: ' . $type );
  7324. }
  7325. $beanOrBeans = self::$redbean->dispense( $type, $num, $alwaysReturnArray );
  7326. if ( isset( $import ) ) {
  7327. $beanOrBeans->import( $import );
  7328. }
  7329. return $beanOrBeans;
  7330. }
  7331. /**
  7332. * Takes a comma separated list of bean types
  7333. * and dispenses these beans. For each type in the list
  7334. * you can specify the number of beans to be dispensed.
  7335. *
  7336. * Usage:
  7337. *
  7338. * list($book, $page, $text) = R::dispenseAll('book,page,text');
  7339. *
  7340. * This will dispense a book, a page and a text. This way you can
  7341. * quickly dispense beans of various types in just one line of code.
  7342. *
  7343. * Usage:
  7344. *
  7345. * list($book, $pages) = R::dispenseAll('book,page*100');
  7346. *
  7347. * This returns an array with a book bean and then another array
  7348. * containing 100 page beans.
  7349. *
  7350. * @param string $order a description of the desired dispense order using the syntax above
  7351. * @param boolean $onlyArrays return only arrays even if amount < 2
  7352. *
  7353. * @return array
  7354. */
  7355. public static function dispenseAll( $order, $onlyArrays = FALSE )
  7356. {
  7357. $list = array();
  7358. foreach( explode( ',', $order ) as $order ) {
  7359. if ( strpos( $order, '*' ) !== false ) {
  7360. list( $type, $amount ) = explode( '*', $order );
  7361. } else {
  7362. $type = $order;
  7363. $amount = 1;
  7364. }
  7365. $list[] = self::dispense( $type, $amount, $onlyArrays );
  7366. }
  7367. return $list;
  7368. }
  7369. /**
  7370. * Convience method. Tries to find beans of a certain type,
  7371. * if no beans are found, it dispenses a bean of that type.
  7372. *
  7373. * @param string $type type of bean you are looking for
  7374. * @param string $sql SQL code for finding the bean
  7375. * @param array $bindings parameters to bind to SQL
  7376. *
  7377. * @return array
  7378. */
  7379. public static function findOrDispense( $type, $sql = NULL, $bindings = array() )
  7380. {
  7381. return self::$finder->findOrDispense( $type, $sql, $bindings );
  7382. }
  7383. /**
  7384. * Finds a bean using a type and a where clause (SQL).
  7385. * As with most Query tools in RedBean you can provide values to
  7386. * be inserted in the SQL statement by populating the value
  7387. * array parameter; you can either use the question mark notation
  7388. * or the slot-notation (:keyname).
  7389. *
  7390. * @param string $type type the type of bean you are looking for
  7391. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  7392. * @param array $bindings values array of values to be bound to parameters in query
  7393. *
  7394. * @return array
  7395. */
  7396. public static function find( $type, $sql = NULL, $bindings = array() )
  7397. {
  7398. return self::$finder->find( $type, $sql, $bindings );
  7399. }
  7400. /**
  7401. * @see Facade::find
  7402. * The findAll() method differs from the find() method in that it does
  7403. * not assume a WHERE-clause, so this is valid:
  7404. *
  7405. * R::findAll('person',' ORDER BY name DESC ');
  7406. *
  7407. * Your SQL does not have to start with a valid WHERE-clause condition.
  7408. *
  7409. * @param string $type type the type of bean you are looking for
  7410. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  7411. * @param array $bindings values array of values to be bound to parameters in query
  7412. *
  7413. * @return array
  7414. */
  7415. public static function findAll( $type, $sql = NULL, $bindings = array() )
  7416. {
  7417. return self::$finder->find( $type, $sql, $bindings );
  7418. }
  7419. /**
  7420. * @see Facade::find
  7421. * The variation also exports the beans (i.e. it returns arrays).
  7422. *
  7423. * @param string $type type the type of bean you are looking for
  7424. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  7425. * @param array $bindings values array of values to be bound to parameters in query
  7426. *
  7427. * @return array
  7428. */
  7429. public static function findAndExport( $type, $sql = NULL, $bindings = array() )
  7430. {
  7431. return self::$finder->findAndExport( $type, $sql, $bindings );
  7432. }
  7433. /**
  7434. * @see Facade::find
  7435. * This variation returns the first bean only.
  7436. *
  7437. * @param string $type type the type of bean you are looking for
  7438. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  7439. * @param array $bindings values array of values to be bound to parameters in query
  7440. *
  7441. * @return OODBBean
  7442. */
  7443. public static function findOne( $type, $sql = NULL, $bindings = array() )
  7444. {
  7445. return self::$finder->findOne( $type, $sql, $bindings );
  7446. }
  7447. /**
  7448. * @see Facade::find
  7449. * This variation returns the last bean only.
  7450. *
  7451. * @param string $type type the type of bean you are looking for
  7452. * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
  7453. * @param array $bindings values array of values to be bound to parameters in query
  7454. *
  7455. * @return OODBBean
  7456. */
  7457. public static function findLast( $type, $sql = NULL, $bindings = array() )
  7458. {
  7459. return self::$finder->findLast( $type, $sql, $bindings );
  7460. }
  7461. /**
  7462. * Returns an array of beans. Pass a type and a series of ids and
  7463. * this method will bring you the corresponding beans.
  7464. *
  7465. * important note: Because this method loads beans using the load()
  7466. * function (but faster) it will return empty beans with ID 0 for
  7467. * every bean that could not be located. The resulting beans will have the
  7468. * passed IDs as their keys.
  7469. *
  7470. * @param string $type type of beans
  7471. * @param array $ids ids to load
  7472. *
  7473. * @return array
  7474. */
  7475. public static function batch( $type, $ids )
  7476. {
  7477. return self::$redbean->batch( $type, $ids );
  7478. }
  7479. /**
  7480. * @see Facade::batch
  7481. *
  7482. * Alias for batch(). Batch method is older but since we added so-called *All
  7483. * methods like storeAll, trashAll, dispenseAll and findAll it seemed logical to
  7484. * improve the consistency of the Facade API and also add an alias for batch() called
  7485. * loadAll.
  7486. *
  7487. * @param string $type type of beans
  7488. * @param array $ids ids to load
  7489. *
  7490. * @return array
  7491. */
  7492. public static function loadAll( $type, $ids )
  7493. {
  7494. return self::$redbean->batch( $type, $ids );
  7495. }
  7496. /**
  7497. * Convenience function to execute Queries directly.
  7498. * Executes SQL.
  7499. *
  7500. * @param string $sql sql SQL query to execute
  7501. * @param array $bindings values a list of values to be bound to query parameters
  7502. *
  7503. * @return integer
  7504. */
  7505. public static function exec( $sql, $bindings = array() )
  7506. {
  7507. return self::query( 'exec', $sql, $bindings );
  7508. }
  7509. /**
  7510. * Convenience function to execute Queries directly.
  7511. * Executes SQL.
  7512. *
  7513. * @param string $sql sql SQL query to execute
  7514. * @param array $bindings values a list of values to be bound to query parameters
  7515. *
  7516. * @return array
  7517. */
  7518. public static function getAll( $sql, $bindings = array() )
  7519. {
  7520. return self::query( 'get', $sql, $bindings );
  7521. }
  7522. /**
  7523. * Convenience function to execute Queries directly.
  7524. * Executes SQL.
  7525. *
  7526. * @param string $sql sql SQL query to execute
  7527. * @param array $bindings values a list of values to be bound to query parameters
  7528. *
  7529. * @return string
  7530. */
  7531. public static function getCell( $sql, $bindings = array() )
  7532. {
  7533. return self::query( 'getCell', $sql, $bindings );
  7534. }
  7535. /**
  7536. * Convenience function to execute Queries directly.
  7537. * Executes SQL.
  7538. *
  7539. * @param string $sql sql SQL query to execute
  7540. * @param array $bindings values a list of values to be bound to query parameters
  7541. *
  7542. * @return array
  7543. */
  7544. public static function getRow( $sql, $bindings = array() )
  7545. {
  7546. return self::query( 'getRow', $sql, $bindings );
  7547. }
  7548. /**
  7549. * Convenience function to execute Queries directly.
  7550. * Executes SQL.
  7551. *
  7552. * @param string $sql sql SQL query to execute
  7553. * @param array $bindings values a list of values to be bound to query parameters
  7554. *
  7555. * @return array
  7556. */
  7557. public static function getCol( $sql, $bindings = array() )
  7558. {
  7559. return self::query( 'getCol', $sql, $bindings );
  7560. }
  7561. /**
  7562. * Convenience function to execute Queries directly.
  7563. * Executes SQL.
  7564. * Results will be returned as an associative array. The first
  7565. * column in the select clause will be used for the keys in this array and
  7566. * the second column will be used for the values. If only one column is
  7567. * selected in the query, both key and value of the array will have the
  7568. * value of this field for each row.
  7569. *
  7570. * @param string $sql sql SQL query to execute
  7571. * @param array $bindings values a list of values to be bound to query parameters
  7572. *
  7573. * @return array
  7574. */
  7575. public static function getAssoc( $sql, $bindings = array() )
  7576. {
  7577. return self::query( 'getAssoc', $sql, $bindings );
  7578. }
  7579. /**
  7580. * Convenience function to execute Queries directly.
  7581. * Executes SQL.
  7582. * Results will be returned as an associative array indexed by the first
  7583. * column in the select.
  7584. *
  7585. * @param string $sql sql SQL query to execute
  7586. * @param array $bindings values a list of values to be bound to query parameters
  7587. *
  7588. * @return array
  7589. */
  7590. public static function getAssocRow( $sql, $bindings = array() )
  7591. {
  7592. return self::query( 'getAssocRow', $sql, $bindings );
  7593. }
  7594. /**
  7595. * Makes a copy of a bean. This method makes a deep copy
  7596. * of the bean.The copy will have the following features.
  7597. * - All beans in own-lists will be duplicated as well
  7598. * - All references to shared beans will be copied but not the shared beans themselves
  7599. * - All references to parent objects (_id fields) will be copied but not the parents themselves
  7600. * In most cases this is the desired scenario for copying beans.
  7601. * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
  7602. * (i.e. one that already has been processed) the ID of the bean will be returned.
  7603. * This should not happen though.
  7604. *
  7605. * Note:
  7606. * This function does a reflectional database query so it may be slow.
  7607. *
  7608. * @param OODBBean $bean bean to be copied
  7609. * @param array $trail for internal usage, pass array()
  7610. * @param boolean $pid for internal usage
  7611. *
  7612. * @return array
  7613. */
  7614. public static function dup( $bean, $trail = array(), $pid = FALSE, $filters = array() )
  7615. {
  7616. self::$duplicationManager->setFilters( $filters );
  7617. return self::$duplicationManager->dup( $bean, $trail, $pid );
  7618. }
  7619. /**
  7620. * Exports a collection of beans. Handy for XML/JSON exports with a
  7621. * Javascript framework like Dojo or ExtJS.
  7622. * What will be exported:
  7623. * - contents of the bean
  7624. * - all own bean lists (recursively)
  7625. * - all shared beans (not THEIR own lists)
  7626. *
  7627. * @param array|OODBBean $beans beans to be exported
  7628. * @param boolean $parents whether you want parent beans to be exported
  7629. * @param array $filters whitelist of types
  7630. *
  7631. * @return array
  7632. */
  7633. public static function exportAll( $beans, $parents = FALSE, $filters = array() )
  7634. {
  7635. return self::$duplicationManager->exportAll( $beans, $parents, $filters );
  7636. }
  7637. /**
  7638. * Converts a series of rows to beans.
  7639. * This method converts a series of rows to beans.
  7640. * The type of the desired output beans can be specified in the
  7641. * first parameter. The second parameter is meant for the database
  7642. * result rows.
  7643. *
  7644. * @param string $type type of beans to produce
  7645. * @param array $rows must contain an array of array
  7646. *
  7647. * @return array
  7648. */
  7649. public static function convertToBeans( $type, $rows )
  7650. {
  7651. return self::$redbean->convertToBeans( $type, $rows );
  7652. }
  7653. /**
  7654. * Part of RedBeanPHP Tagging API.
  7655. * Tests whether a bean has been associated with one ore more
  7656. * of the listed tags. If the third parameter is TRUE this method
  7657. * will return TRUE only if all tags that have been specified are indeed
  7658. * associated with the given bean, otherwise FALSE.
  7659. * If the third parameter is FALSE this
  7660. * method will return TRUE if one of the tags matches, FALSE if none
  7661. * match.
  7662. *
  7663. * @param OODBBean $bean bean to check for tags
  7664. * @param array $tags list of tags
  7665. * @param boolean $all whether they must all match or just some
  7666. *
  7667. * @return boolean
  7668. */
  7669. public static function hasTag( $bean, $tags, $all = FALSE )
  7670. {
  7671. return self::$tagManager->hasTag( $bean, $tags, $all );
  7672. }
  7673. /**
  7674. * Part of RedBeanPHP Tagging API.
  7675. * Removes all specified tags from the bean. The tags specified in
  7676. * the second parameter will no longer be associated with the bean.
  7677. *
  7678. * @param OODBBean $bean tagged bean
  7679. * @param array $tagList list of tags (names)
  7680. *
  7681. * @return void
  7682. */
  7683. public static function untag( $bean, $tagList )
  7684. {
  7685. self::$tagManager->untag( $bean, $tagList );
  7686. }
  7687. /**
  7688. * Part of RedBeanPHP Tagging API.
  7689. * Tags a bean or returns tags associated with a bean.
  7690. * If $tagList is NULL or omitted this method will return a
  7691. * comma separated list of tags associated with the bean provided.
  7692. * If $tagList is a comma separated list (string) of tags all tags will
  7693. * be associated with the bean.
  7694. * You may also pass an array instead of a string.
  7695. *
  7696. * @param OODBBean $bean bean
  7697. * @param mixed $tagList tags
  7698. *
  7699. * @return string
  7700. */
  7701. public static function tag( OODBBean $bean, $tagList = NULL )
  7702. {
  7703. return self::$tagManager->tag( $bean, $tagList );
  7704. }
  7705. /**
  7706. * Part of RedBeanPHP Tagging API.
  7707. * Adds tags to a bean.
  7708. * If $tagList is a comma separated list of tags all tags will
  7709. * be associated with the bean.
  7710. * You may also pass an array instead of a string.
  7711. *
  7712. * @param OODBBean $bean bean
  7713. * @param array $tagList list of tags to add to bean
  7714. *
  7715. * @return void
  7716. */
  7717. public static function addTags( OODBBean $bean, $tagList )
  7718. {
  7719. self::$tagManager->addTags( $bean, $tagList );
  7720. }
  7721. /**
  7722. * Part of RedBeanPHP Tagging API.
  7723. * Returns all beans that have been tagged with one of the tags given.
  7724. *
  7725. * @param string $beanType type of bean you are looking for
  7726. * @param array $tagList list of tags to match
  7727. *
  7728. * @return array
  7729. */
  7730. public static function tagged( $beanType, $tagList )
  7731. {
  7732. return self::$tagManager->tagged( $beanType, $tagList );
  7733. }
  7734. /**
  7735. * Part of RedBeanPHP Tagging API.
  7736. * Returns all beans that have been tagged with ALL of the tags given.
  7737. *
  7738. * @param string $beanType type of bean you are looking for
  7739. * @param array $tagList list of tags to match
  7740. *
  7741. * @return array
  7742. */
  7743. public static function taggedAll( $beanType, $tagList )
  7744. {
  7745. return self::$tagManager->taggedAll( $beanType, $tagList );
  7746. }
  7747. /**
  7748. * Wipes all beans of type $beanType.
  7749. *
  7750. * @param string $beanType type of bean you want to destroy entirely
  7751. *
  7752. * @return boolean
  7753. */
  7754. public static function wipe( $beanType )
  7755. {
  7756. return Facade::$redbean->wipe( $beanType );
  7757. }
  7758. /**
  7759. * Counts the number of beans of type $type.
  7760. * This method accepts a second argument to modify the count-query.
  7761. * A third argument can be used to provide bindings for the SQL snippet.
  7762. *
  7763. * @param string $type type of bean we are looking for
  7764. * @param string $addSQL additional SQL snippet
  7765. * @param array $bindings parameters to bind to SQL
  7766. *
  7767. * @return integer
  7768. *
  7769. * @throws SQL
  7770. */
  7771. public static function count( $type, $addSQL = '', $bindings = array() )
  7772. {
  7773. return Facade::$redbean->count( $type, $addSQL, $bindings );
  7774. }
  7775. /**
  7776. * Configures the facade, want to have a new Writer? A new Object Database or a new
  7777. * Adapter and you want it on-the-fly? Use this method to hot-swap your facade with a new
  7778. * toolbox.
  7779. *
  7780. * @param ToolBox $tb toolbox
  7781. *
  7782. * @return ToolBox
  7783. */
  7784. public static function configureFacadeWithToolbox( ToolBox $tb )
  7785. {
  7786. $oldTools = self::$toolbox;
  7787. self::$toolbox = $tb;
  7788. self::$writer = self::$toolbox->getWriter();
  7789. self::$adapter = self::$toolbox->getDatabaseAdapter();
  7790. self::$redbean = self::$toolbox->getRedBean();
  7791. self::$finder = new Finder( self::$toolbox );
  7792. self::$associationManager = new AssociationManager( self::$toolbox );
  7793. self::$redbean->setAssociationManager( self::$associationManager );
  7794. self::$labelMaker = new LabelMaker( self::$toolbox );
  7795. $helper = new SimpleModelHelper();
  7796. $helper->attachEventListeners( self::$redbean );
  7797. self::$redbean->setBeanHelper( new SimpleFacadeBeanHelper );
  7798. self::$associationManager->addEventListener( 'delete', $helper );
  7799. self::$duplicationManager = new DuplicationManager( self::$toolbox );
  7800. self::$tagManager = new TagManager( self::$toolbox );
  7801. return $oldTools;
  7802. }
  7803. /**
  7804. * Facade Convience method for adapter transaction system.
  7805. * Begins a transaction.
  7806. *
  7807. * @return bool
  7808. */
  7809. public static function begin()
  7810. {
  7811. if ( !self::$redbean->isFrozen() ) return FALSE;
  7812. self::$adapter->startTransaction();
  7813. return TRUE;
  7814. }
  7815. /**
  7816. * Facade Convience method for adapter transaction system.
  7817. * Commits a transaction.
  7818. *
  7819. * @return bool
  7820. */
  7821. public static function commit()
  7822. {
  7823. if ( !self::$redbean->isFrozen() ) return FALSE;
  7824. self::$adapter->commit();
  7825. return TRUE;
  7826. }
  7827. /**
  7828. * Facade Convience method for adapter transaction system.
  7829. * Rolls back a transaction.
  7830. *
  7831. * @return bool
  7832. */
  7833. public static function rollback()
  7834. {
  7835. if ( !self::$redbean->isFrozen() ) return FALSE;
  7836. self::$adapter->rollback();
  7837. return TRUE;
  7838. }
  7839. /**
  7840. * Returns a list of columns. Format of this array:
  7841. * array( fieldname => type )
  7842. * Note that this method only works in fluid mode because it might be
  7843. * quite heavy on production servers!
  7844. *
  7845. * @param string $table name of the table (not type) you want to get columns of
  7846. *
  7847. * @return array
  7848. */
  7849. public static function getColumns( $table )
  7850. {
  7851. return self::$writer->getColumns( $table );
  7852. }
  7853. /**
  7854. * Generates question mark slots for an array of values.
  7855. *
  7856. * @param array $array
  7857. *
  7858. * @return string
  7859. */
  7860. public static function genSlots( $array )
  7861. {
  7862. return ( count( $array ) ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : '';
  7863. }
  7864. /**
  7865. * Nukes the entire database.
  7866. * This will remove all schema structures from the database.
  7867. * Only works in fluid mode. Be careful with this method.
  7868. *
  7869. * @warning dangerous method, will remove all tables, columns etc.
  7870. *
  7871. * @return void
  7872. */
  7873. public static function nuke()
  7874. {
  7875. if ( !self::$redbean->isFrozen() ) {
  7876. self::$writer->wipeAll();
  7877. }
  7878. }
  7879. /**
  7880. * Short hand function to store a set of beans at once, IDs will be
  7881. * returned as an array. For information please consult the R::store()
  7882. * function.
  7883. * A loop saver.
  7884. *
  7885. * @param array $beans list of beans to be stored
  7886. *
  7887. * @return array
  7888. */
  7889. public static function storeAll( $beans )
  7890. {
  7891. $ids = array();
  7892. foreach ( $beans as $bean ) {
  7893. $ids[] = self::store( $bean );
  7894. }
  7895. return $ids;
  7896. }
  7897. /**
  7898. * Short hand function to trash a set of beans at once.
  7899. * For information please consult the R::trash() function.
  7900. * A loop saver.
  7901. *
  7902. * @param array $beans list of beans to be trashed
  7903. *
  7904. * @return void
  7905. */
  7906. public static function trashAll( $beans )
  7907. {
  7908. foreach ( $beans as $bean ) {
  7909. self::trash( $bean );
  7910. }
  7911. }
  7912. /**
  7913. * Toggles Writer Cache.
  7914. * Turns the Writer Cache on or off. The Writer Cache is a simple
  7915. * query based caching system that may improve performance without the need
  7916. * for cache management. This caching system will cache non-modifying queries
  7917. * that are marked with special SQL comments. As soon as a non-marked query
  7918. * gets executed the cache will be flushed. Only non-modifying select queries
  7919. * have been marked therefore this mechanism is a rather safe way of caching, requiring
  7920. * no explicit flushes or reloads. Of course this does not apply if you intend to test
  7921. * or simulate concurrent querying.
  7922. *
  7923. * @param boolean $yesNo TRUE to enable cache, FALSE to disable cache
  7924. *
  7925. * @return void
  7926. */
  7927. public static function useWriterCache( $yesNo )
  7928. {
  7929. self::getWriter()->setUseCache( $yesNo );
  7930. }
  7931. /**
  7932. * A label is a bean with only an id, type and name property.
  7933. * This function will dispense beans for all entries in the array. The
  7934. * values of the array will be assigned to the name property of each
  7935. * individual bean.
  7936. *
  7937. * @param string $type type of beans you would like to have
  7938. * @param array $labels list of labels, names for each bean
  7939. *
  7940. * @return array
  7941. */
  7942. public static function dispenseLabels( $type, $labels )
  7943. {
  7944. return self::$labelMaker->dispenseLabels( $type, $labels );
  7945. }
  7946. /**
  7947. * Generates and returns an ENUM value. This is how RedBeanPHP handles ENUMs.
  7948. * Either returns a (newly created) bean respresenting the desired ENUM
  7949. * value or returns a list of all enums for the type.
  7950. *
  7951. * To obtain (and add if necessary) an ENUM value:
  7952. *
  7953. * $tea->flavour = R::enum( 'flavour:apple' );
  7954. *
  7955. * Returns a bean of type 'flavour' with name = apple.
  7956. * This will add a bean with property name (set to APPLE) to the database
  7957. * if it does not exist yet.
  7958. *
  7959. * To obtain all flavours:
  7960. *
  7961. * R::enum('flavour');
  7962. *
  7963. * To get a list of all flavour names:
  7964. *
  7965. * R::gatherLabels( R::enum( 'flavour' ) );
  7966. *
  7967. * @param string $enum either type or type-value
  7968. *
  7969. * @return array|OODBBean
  7970. */
  7971. public static function enum( $enum )
  7972. {
  7973. return self::$labelMaker->enum( $enum );
  7974. }
  7975. /**
  7976. * Gathers labels from beans. This function loops through the beans,
  7977. * collects the values of the name properties of each individual bean
  7978. * and stores the names in a new array. The array then gets sorted using the
  7979. * default sort function of PHP (sort).
  7980. *
  7981. * @param array $beans list of beans to loop
  7982. *
  7983. * @return array
  7984. */
  7985. public static function gatherLabels( $beans )
  7986. {
  7987. return self::$labelMaker->gatherLabels( $beans );
  7988. }
  7989. /**
  7990. * Closes the database connection.
  7991. *
  7992. * @return void
  7993. */
  7994. public static function close()
  7995. {
  7996. if ( isset( self::$adapter ) ) {
  7997. self::$adapter->close();
  7998. }
  7999. }
  8000. /**
  8001. * Simple convenience function, returns ISO date formatted representation
  8002. * of $time.
  8003. *
  8004. * @param mixed $time UNIX timestamp
  8005. *
  8006. * @return string
  8007. */
  8008. public static function isoDate( $time = NULL )
  8009. {
  8010. if ( !$time ) {
  8011. $time = time();
  8012. }
  8013. return @date( 'Y-m-d', $time );
  8014. }
  8015. /**
  8016. * Simple convenience function, returns ISO date time
  8017. * formatted representation
  8018. * of $time.
  8019. *
  8020. * @param mixed $time UNIX timestamp
  8021. *
  8022. * @return string
  8023. */
  8024. public static function isoDateTime( $time = NULL )
  8025. {
  8026. if ( !$time ) $time = time();
  8027. return @date( 'Y-m-d H:i:s', $time );
  8028. }
  8029. /**
  8030. * Optional accessor for neat code.
  8031. * Sets the database adapter you want to use.
  8032. *
  8033. * @param Adapter $adapter
  8034. *
  8035. * @return void
  8036. */
  8037. public static function setDatabaseAdapter( Adapter $adapter )
  8038. {
  8039. self::$adapter = $adapter;
  8040. }
  8041. /**
  8042. * Optional accessor for neat code.
  8043. * Sets the database adapter you want to use.
  8044. *
  8045. * @param QueryWriter $writer
  8046. *
  8047. * @return void
  8048. */
  8049. public static function setWriter( QueryWriter $writer )
  8050. {
  8051. self::$writer = $writer;
  8052. }
  8053. /**
  8054. * Optional accessor for neat code.
  8055. * Sets the database adapter you want to use.
  8056. *
  8057. * @param OODB $redbean
  8058. */
  8059. public static function setRedBean( OODB $redbean )
  8060. {
  8061. self::$redbean = $redbean;
  8062. }
  8063. /**
  8064. * Optional accessor for neat code.
  8065. * Sets the database adapter you want to use.
  8066. *
  8067. * @return DBAdapter
  8068. */
  8069. public static function getDatabaseAdapter()
  8070. {
  8071. return self::$adapter;
  8072. }
  8073. /**
  8074. * Returns the current duplication manager instance.
  8075. *
  8076. * @return DuplicationManager
  8077. */
  8078. public static function getDuplicationManager()
  8079. {
  8080. return self::$duplicationManager;
  8081. }
  8082. /**
  8083. * Optional accessor for neat code.
  8084. * Sets the database adapter you want to use.
  8085. *
  8086. * @return QueryWriter
  8087. */
  8088. public static function getWriter()
  8089. {
  8090. return self::$writer;
  8091. }
  8092. /**
  8093. * Optional accessor for neat code.
  8094. * Sets the database adapter you want to use.
  8095. *
  8096. * @return OODB
  8097. */
  8098. public static function getRedBean()
  8099. {
  8100. return self::$redbean;
  8101. }
  8102. /**
  8103. * Returns the toolbox currently used by the facade.
  8104. * To set the toolbox use R::setup() or R::configureFacadeWithToolbox().
  8105. * To create a toolbox use Setup::kickstart(). Or create a manual
  8106. * toolbox using the ToolBox class.
  8107. *
  8108. * @return ToolBox
  8109. */
  8110. public static function getToolBox()
  8111. {
  8112. return self::$toolbox;
  8113. }
  8114. public static function getExtractedToolbox()
  8115. {
  8116. return array(
  8117. self::$redbean,
  8118. self::$adapter,
  8119. self::$writer,
  8120. self::$toolbox
  8121. );
  8122. }
  8123. /**
  8124. * Facade method for AQueryWriter::renameAssociation()
  8125. *
  8126. * @param string|array $from
  8127. * @param string $to
  8128. *
  8129. * @return void
  8130. */
  8131. public static function renameAssociation( $from, $to = NULL )
  8132. {
  8133. AQueryWriter::renameAssociation( $from, $to );
  8134. }
  8135. /**
  8136. * Little helper method for Resty Bean Can server and others.
  8137. * Takes an array of beans and exports each bean.
  8138. * Unlike exportAll this method does not recurse into own lists
  8139. * and shared lists, the beans are exported as-is, only loaded lists
  8140. * are exported.
  8141. *
  8142. * @param array $beans beans
  8143. *
  8144. * @return array
  8145. */
  8146. public static function beansToArray( $beans )
  8147. {
  8148. $list = array();
  8149. foreach( $beans as $bean ) {
  8150. $list[] = $bean->export();
  8151. }
  8152. return $list;
  8153. }
  8154. /**
  8155. * Dynamically extends the facade with a plugin.
  8156. * Using this method you can register your plugin with the facade and then
  8157. * use the plugin by invoking the name specified plugin name as a method on
  8158. * the facade.
  8159. *
  8160. * Usage:
  8161. *
  8162. * R::ext( 'makeTea', function() { ... } );
  8163. *
  8164. * Now you can use your makeTea plugin like this:
  8165. *
  8166. * R::makeTea();
  8167. *
  8168. * @param string $pluginName name of the method to call the plugin
  8169. * @param callable $callable a PHP callable
  8170. */
  8171. public static function ext( $pluginName, $callable )
  8172. {
  8173. if ( !ctype_alnum( $pluginName ) ) {
  8174. throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
  8175. }
  8176. self::$plugins[$pluginName] = $callable;
  8177. }
  8178. /**
  8179. * Call static for use with dynamic plugins. This magic method will
  8180. * intercept static calls and route them to the specified plugin.
  8181. *
  8182. * @param string $pluginName name of the plugin
  8183. * @param array $params list of arguments to pass to plugin method
  8184. *
  8185. * @return mixed
  8186. */
  8187. public static function __callStatic( $pluginName, $params )
  8188. {
  8189. if ( !ctype_alnum( $pluginName) ) {
  8190. throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
  8191. }
  8192. if ( !isset( self::$plugins[$pluginName] ) ) {
  8193. throw new RedException( 'Plugin \''.$pluginName.'\' does not exist, add this plugin using: R::ext(\''.$pluginName.'\')' );
  8194. }
  8195. return call_user_func_array( self::$plugins[$pluginName], $params );
  8196. }
  8197. }
  8198. }
  8199. namespace RedBeanPHP {
  8200. use RedBeanPHP\ToolBox as ToolBox;
  8201. use RedBeanPHP\AssociationManager as AssociationManager;
  8202. use RedBeanPHP\OODB as OODB;
  8203. use RedBeanPHP\OODBBean as OODBBean;
  8204. use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
  8205. /**
  8206. * Duplication Manager
  8207. *
  8208. * @file RedBean/DuplicationManager.php
  8209. * @desc Creates deep copies of beans
  8210. * @author Gabor de Mooij and the RedBeanPHP Community
  8211. * @license BSD/GPLv2
  8212. *
  8213. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  8214. * This source file is subject to the BSD/GPLv2 License that is bundled
  8215. * with this source code in the file license.txt.
  8216. */
  8217. class DuplicationManager
  8218. {
  8219. /**
  8220. * @var ToolBox
  8221. */
  8222. protected $toolbox;
  8223. /**
  8224. * @var AssociationManager
  8225. */
  8226. protected $associationManager;
  8227. /**
  8228. * @var OODB
  8229. */
  8230. protected $redbean;
  8231. /**
  8232. * @var array
  8233. */
  8234. protected $tables = array();
  8235. /**
  8236. * @var array
  8237. */
  8238. protected $columns = array();
  8239. /**
  8240. * @var array
  8241. */
  8242. protected $filters = array();
  8243. /**
  8244. * @var array
  8245. */
  8246. protected $cacheTables = FALSE;
  8247. /**
  8248. * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
  8249. *
  8250. * @param OODBBean $copy target bean to copy lists to
  8251. * @param string $shared name of the shared list
  8252. * @param array $beans array with shared beans to copy
  8253. *
  8254. * @return void
  8255. */
  8256. private function copySharedBeans( OODBBean $copy, $shared, $beans )
  8257. {
  8258. $copy->$shared = array();
  8259. foreach ( $beans as $subBean ) {
  8260. array_push( $copy->$shared, $subBean );
  8261. }
  8262. }
  8263. /**
  8264. * Copies the own beans in a bean, i.e. all the ownBean-lists.
  8265. * Each bean in the own-list belongs exclusively to its owner so
  8266. * we need to invoke the duplicate method again to duplicate each bean here.
  8267. *
  8268. * @param OODBBean $copy target bean to copy lists to
  8269. * @param string $owned name of the own list
  8270. * @param array $beans array with shared beans to copy
  8271. * @param array $trail array with former beans to detect recursion
  8272. * @param boolean $preserveIDs TRUE means preserve IDs, for export only
  8273. *
  8274. * @return void
  8275. */
  8276. private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
  8277. {
  8278. $copy->$owned = array();
  8279. foreach ( $beans as $subBean ) {
  8280. array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
  8281. }
  8282. }
  8283. /**
  8284. * Creates a copy of bean $bean and copies all primitive properties (not lists)
  8285. * and the parents beans to the newly created bean. Also sets the ID of the bean
  8286. * to 0.
  8287. *
  8288. * @param OODBBean $bean bean to copy
  8289. *
  8290. * @return OODBBean
  8291. */
  8292. private function createCopy( OODBBean $bean )
  8293. {
  8294. $type = $bean->getMeta( 'type' );
  8295. $copy = $this->redbean->dispense( $type );
  8296. $copy->importFrom( $bean );
  8297. $copy->id = 0;
  8298. return $copy;
  8299. }
  8300. /**
  8301. * Generates a key from the bean type and its ID and determines if the bean
  8302. * occurs in the trail, if not the bean will be added to the trail.
  8303. * Returns TRUE if the bean occurs in the trail and FALSE otherwise.
  8304. *
  8305. * @param array $trail list of former beans
  8306. * @param OODBBean $bean currently selected bean
  8307. *
  8308. * @return boolean
  8309. */
  8310. private function inTrailOrAdd( &$trail, OODBBean $bean )
  8311. {
  8312. $type = $bean->getMeta( 'type' );
  8313. $key = $type . $bean->getID();
  8314. if ( isset( $trail[$key] ) ) {
  8315. return TRUE;
  8316. }
  8317. $trail[$key] = $bean;
  8318. return FALSE;
  8319. }
  8320. /**
  8321. * Given the type name of a bean this method returns the canonical names
  8322. * of the own-list and the shared-list properties respectively.
  8323. * Returns a list with two elements: name of the own-list, and name
  8324. * of the shared list.
  8325. *
  8326. * @param string $typeName bean type name
  8327. *
  8328. * @return array
  8329. */
  8330. private function getListNames( $typeName )
  8331. {
  8332. $owned = 'own' . ucfirst( $typeName );
  8333. $shared = 'shared' . ucfirst( $typeName );
  8334. return array( $owned, $shared );
  8335. }
  8336. /**
  8337. * Determines whether the bean has an own list based on
  8338. * schema inspection from realtime schema or cache.
  8339. *
  8340. * @param string $type bean type to get list for
  8341. * @param string $target type of list you want to detect
  8342. *
  8343. * @return boolean
  8344. */
  8345. protected function hasOwnList( $type, $target )
  8346. {
  8347. return isset( $this->columns[$target][$type . '_id'] );
  8348. }
  8349. /**
  8350. * Determines whether the bea has a shared list based on
  8351. * schema inspection from realtime schema or cache.
  8352. *
  8353. * @param string $type bean type to get list for
  8354. * @param string $target type of list you are looking for
  8355. *
  8356. * @return boolean
  8357. */
  8358. protected function hasSharedList( $type, $target )
  8359. {
  8360. return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
  8361. }
  8362. /**
  8363. * @see DuplicationManager::dup
  8364. *
  8365. * @param OODBBean $bean bean to be copied
  8366. * @param array $trail trail to prevent infinite loops
  8367. * @param boolean $preserveIDs preserve IDs
  8368. *
  8369. * @return OODBBean
  8370. */
  8371. protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
  8372. {
  8373. if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
  8374. $type = $bean->getMeta( 'type' );
  8375. $copy = $this->createCopy( $bean );
  8376. foreach ( $this->tables as $table ) {
  8377. if ( !empty( $this->filters ) ) {
  8378. if ( !in_array( $table, $this->filters ) ) continue;
  8379. }
  8380. list( $owned, $shared ) = $this->getListNames( $table );
  8381. if ( $this->hasSharedList( $type, $table ) ) {
  8382. if ( $beans = $bean->$shared ) {
  8383. $this->copySharedBeans( $copy, $shared, $beans );
  8384. }
  8385. } elseif ( $this->hasOwnList( $type, $table ) ) {
  8386. if ( $beans = $bean->$owned ) {
  8387. $this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
  8388. }
  8389. $copy->setMeta( 'sys.shadow.' . $owned, NULL );
  8390. }
  8391. $copy->setMeta( 'sys.shadow.' . $shared, NULL );
  8392. }
  8393. $copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
  8394. return $copy;
  8395. }
  8396. /**
  8397. * Constructor,
  8398. * creates a new instance of DupManager.
  8399. *
  8400. * @param ToolBox $toolbox
  8401. */
  8402. public function __construct( ToolBox $toolbox )
  8403. {
  8404. $this->toolbox = $toolbox;
  8405. $this->redbean = $toolbox->getRedBean();
  8406. $this->associationManager = $this->redbean->getAssociationManager();
  8407. }
  8408. /**
  8409. * For better performance you can pass the tables in an array to this method.
  8410. * If the tables are available the duplication manager will not query them so
  8411. * this might be beneficial for performance.
  8412. *
  8413. * @param array $tables
  8414. *
  8415. * @return void
  8416. */
  8417. public function setTables( $tables )
  8418. {
  8419. foreach ( $tables as $key => $value ) {
  8420. if ( is_numeric( $key ) ) {
  8421. $this->tables[] = $value;
  8422. } else {
  8423. $this->tables[] = $key;
  8424. $this->columns[$key] = $value;
  8425. }
  8426. }
  8427. $this->cacheTables = TRUE;
  8428. }
  8429. /**
  8430. * Returns a schema array for cache.
  8431. *
  8432. * @return array
  8433. */
  8434. public function getSchema()
  8435. {
  8436. return $this->columns;
  8437. }
  8438. /**
  8439. * Indicates whether you want the duplication manager to cache the database schema.
  8440. * If this flag is set to TRUE the duplication manager will query the database schema
  8441. * only once. Otherwise the duplicationmanager will, by default, query the schema
  8442. * every time a duplication action is performed (dup()).
  8443. *
  8444. * @param boolean $yesNo
  8445. */
  8446. public function setCacheTables( $yesNo )
  8447. {
  8448. $this->cacheTables = $yesNo;
  8449. }
  8450. /**
  8451. * A filter array is an array with table names.
  8452. * By setting a table filter you can make the duplication manager only take into account
  8453. * certain bean types. Other bean types will be ignored when exporting or making a
  8454. * deep copy. If no filters are set all types will be taking into account, this is
  8455. * the default behavior.
  8456. *
  8457. * @param array $filters
  8458. */
  8459. public function setFilters( $filters )
  8460. {
  8461. if ( !is_array( $filters ) ) {
  8462. $filters = array( $filters );
  8463. }
  8464. $this->filters = $filters;
  8465. }
  8466. /**
  8467. * Makes a copy of a bean. This method makes a deep copy
  8468. * of the bean.The copy will have the following features.
  8469. * - All beans in own-lists will be duplicated as well
  8470. * - All references to shared beans will be copied but not the shared beans themselves
  8471. * - All references to parent objects (_id fields) will be copied but not the parents themselves
  8472. * In most cases this is the desired scenario for copying beans.
  8473. * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
  8474. * (i.e. one that already has been processed) the ID of the bean will be returned.
  8475. * This should not happen though.
  8476. *
  8477. * Note:
  8478. * This function does a reflectional database query so it may be slow.
  8479. *
  8480. * Note:
  8481. * this function actually passes the arguments to a protected function called
  8482. * duplicate() that does all the work. This method takes care of creating a clone
  8483. * of the bean to avoid the bean getting tainted (triggering saving when storing it).
  8484. *
  8485. * @param OODBBean $bean bean to be copied
  8486. * @param array $trail for internal usage, pass array()
  8487. * @param boolean $preserveIDs for internal usage
  8488. *
  8489. * @return OODBBean
  8490. */
  8491. public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
  8492. {
  8493. if ( !count( $this->tables ) ) {
  8494. $this->tables = $this->toolbox->getWriter()->getTables();
  8495. }
  8496. if ( !count( $this->columns ) ) {
  8497. foreach ( $this->tables as $table ) {
  8498. $this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
  8499. }
  8500. }
  8501. $rs = $this->duplicate( clone( $bean ), $trail, $preserveIDs );
  8502. if ( !$this->cacheTables ) {
  8503. $this->tables = array();
  8504. $this->columns = array();
  8505. }
  8506. return $this->duplicate( $rs, $trail, $preserveIDs );
  8507. }
  8508. /**
  8509. * Exports a collection of beans. Handy for XML/JSON exports with a
  8510. * Javascript framework like Dojo or ExtJS.
  8511. * What will be exported:
  8512. * - contents of the bean
  8513. * - all own bean lists (recursively)
  8514. * - all shared beans (not THEIR own lists)
  8515. *
  8516. * @param array|OODBBean $beans beans to be exported
  8517. * @param boolean $parents also export parents
  8518. * @param array $filters only these types (whitelist)
  8519. *
  8520. * @return array
  8521. */
  8522. public function exportAll( $beans, $parents = FALSE, $filters = array() )
  8523. {
  8524. $array = array();
  8525. if ( !is_array( $beans ) ) {
  8526. $beans = array( $beans );
  8527. }
  8528. foreach ( $beans as $bean ) {
  8529. $this->setFilters( $filters );
  8530. $duplicate = $this->dup( $bean, array(), TRUE );
  8531. $array[] = $duplicate->export( FALSE, $parents, FALSE, $filters );
  8532. }
  8533. return $array;
  8534. }
  8535. }
  8536. }
  8537. namespace RedBeanPHP {
  8538. /**
  8539. * RedBean Plugin
  8540. *
  8541. * @file RedBean/Plugin.php
  8542. * @desc Marker interface for plugins.
  8543. * @author Gabor de Mooij and the RedBeanPHP Community
  8544. * @license BSD/GPLv2
  8545. *
  8546. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
  8547. * This source file is subject to the BSD/GPLv2 License that is bundled
  8548. * with this source code in the file license.txt.
  8549. */
  8550. interface Plugin
  8551. {
  8552. }
  8553. ;
  8554. }
  8555. namespace {
  8556. //make some classes available for backward compatibility
  8557. class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
  8558. if (!class_exists('R')) {
  8559. class R extends \RedBeanPHP\Facade{};
  8560. }
  8561. /**
  8562. * Support functions for RedBeanPHP.
  8563. *
  8564. * @file RedBeanPHP/Functions.php
  8565. * @desc Additional convenience shortcut functions for RedBeanPHP
  8566. * @author Gabor de Mooij and the RedBeanPHP community
  8567. * @license BSD/GPLv2
  8568. *
  8569. * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
  8570. * This source file is subject to the BSD/GPLv2 License that is bundled
  8571. * with this source code in the file license.txt.
  8572. */
  8573. /**
  8574. * Convenience function for ENUM short syntax in queries.
  8575. *
  8576. * Usage:
  8577. *
  8578. * R::find( 'paint', ' color_id = ? ', [ EID('color:yellow') ] );
  8579. *
  8580. * If a function called EID() already exists you'll have to write this
  8581. * wrapper yourself ;)
  8582. *
  8583. * @param string $enumName enum code as you would pass to R::enum()
  8584. *
  8585. * @return mixed
  8586. */
  8587. if (!function_exists('EID')) {
  8588. function EID($enumName)
  8589. {
  8590. return \RedBeanPHP\Facade::enum( $enumName )->id;
  8591. }
  8592. }
  8593. }