diff --git a/phalcon/DM/Pdo/Connection.zep b/phalcon/DM/Pdo/Connection.zep new file mode 100644 index 0000000000..a6855d5e7f --- /dev/null +++ b/phalcon/DM/Pdo/Connection.zep @@ -0,0 +1,163 @@ +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team <team@phalcon.io> + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + * + * Implementation of this file has been influenced by AtlasPHP + * + * @link https://github.com/atlasphp/Atlas.Pdo + * @license https://github.com/atlasphp/Atlas.Pdo/blob/1.x/LICENSE.md + */ + +namespace Phalcon\DM\Pdo; + +use InvalidArgumentException; +use Phalcon\DM\Pdo\Connection\AbstractConnection; +use Phalcon\DM\Pdo\Parser\ParserInterface; +use Phalcon\DM\Pdo\Profiler\Profiler; +use Phalcon\DM\Pdo\Profiler\ProfilerInterface; + +/** + * Provides array quoting, profiling, a new `perform()` method, new `fetch*()` + * methods + * + * @property array $args + * @property ParserInterface $parser + * @property PDO $pdo + * @property ProfilerInterface $profiler + */ +class Connection extends AbstractConnection +{ + /** + * @var array + */ + protected arguments = []; + + /** + * Constructor. + * + * This overrides the parent so that it can take connection attributes as a + * constructor parameter, and set them after connection. + * + * @param string $dsn + * @param string $username + * @param string $password + * @param array $options + * @param array $queries + * @param ProfilerInterface $profiler + */ + public function __construct( + string dsn, + string username = null, + string password = null, + array options = [], + array queries = [], + <ProfilerInterface> profiler = null + ) { + var parser, parts; + array available; + + let parts = explode(":", dsn), + available = [ + "mysql" : true, + "pgsql" : true, + "sqlite" : true, + "mssql" : true + ]; + + if !isset available[parts[0]] { + throw new InvalidArgumentException( + "Driver not supported [" . parts[0] . "]" + ); + } + + + // if no error mode is specified, use exceptions + if !isset options[\PDO::ATTR_ERRMODE] { + let options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_EXCEPTION; + } + + // Arguments store + let this->args = [ + dsn, + username, + password, + options, + queries + ]; + + // Create a new profiler if none has been passed + if profiler === null { + let profiler = new Profiler(); + } + this->setProfiler(profiler); + + // Set the new Query parser + let parser = this->newParser(parts[0]); + this->setParser(parser); + + // Quotes + let this->quote = this->getQuoteNames(parts[0]); + } + + /** + * The purpose of this method is to hide sensitive data from stack traces. + * + * @return array + */ + public function __debugInfo() -> array + { + return [ + "arguments" : [ + this->arguments[0], + "****", + "****", + this->arguments[3], + this->arguments[4] + ] + ]; + } + + /** + * Connects to the database. + */ + public function connect() -> void + { + var dsn, options, password, query, queries, username; + + if !this->pdo { + // connect + this->profiler->start(__FUNCTION__); + + let dsn = this->arguments[0], + username = this->arguments[1], + password = this->arguments[2], + options = this->arguments[3], + queries = this->arguments[4]; + + let this->pdo = new \PDO(dsn, username, password, options); + + this->profiler->finish(); + + // connection-time queries + for query in queries { + this->exec(query); + } + } + } + + /** + * Disconnects from the database. + */ + public function disconnect() -> void + { + this->profiler->start(__FUNCTION__); + + let this->pdo = null; + + this->profiler->finish(); + } +} diff --git a/phalcon/DM/Pdo/Connection/AbstractConnection.zep b/phalcon/DM/Pdo/Connection/AbstractConnection.zep index 669773aaf7..f6146de317 100644 --- a/phalcon/DM/Pdo/Connection/AbstractConnection.zep +++ b/phalcon/DM/Pdo/Connection/AbstractConnection.zep @@ -16,7 +16,6 @@ namespace Phalcon\DM\Pdo\Connection; use BadMethodCallException; -use PDO; use PDOStatement; use Phalcon\DM\Pdo\Exception\CannotBindValue; use Phalcon\DM\Pdo\Parser\ParserInterface; @@ -237,8 +236,10 @@ abstract class AbstractConnection implements ConnectionInterface let data = [], sth = this->perform(statement, values); - while (row = sth->$fetch(\PDO::FETCH_ASSOC)) { + let row = sth->$fetch(\PDO::FETCH_ASSOC); + while (row) { let data[current(row)] = row; + let row = sth->$fetch(\PDO::FETCH_ASSOC); } return data; @@ -415,7 +416,7 @@ abstract class AbstractConnection implements ConnectionInterface * * @return PDO */ - public function getAdapter() -> <PDO> + public function getAdapter() -> <\PDO> { this->connect(); @@ -488,6 +489,7 @@ abstract class AbstractConnection implements ConnectionInterface public function getQuoteNames(string driver = "") -> array { var option; + array quotes; let option = driver; if empty option { @@ -496,29 +498,34 @@ abstract class AbstractConnection implements ConnectionInterface switch option { case "mysql": - return [ + let quotes = [ "prefix" : "`", "suffix" : "`", "find" : "`", - "replace" : "``", + "replace" : "``" ]; + break; case "sqlsrv": - return [ + let quotes = [ "prefix" : "[", "suffix" : "]", "find" : "]", - "replace" : "][", + "replace" : "][" ]; + break; default: - return [ + let quotes = [ "prefix" : "\"", "suffix" : "\"", "find" : "\"", - "replace" : "\"\"", + "replace" : "\"\"" ]; + break; } + + return quotes; } /** @@ -587,7 +594,7 @@ abstract class AbstractConnection implements ConnectionInterface * @return PDOStatement * @throws CannotBindValue */ - public function perform(string statement, array values = []) -> <PDOStatement> + public function perform(string statement, array values = []) -> <\PDOStatement> { var sth; @@ -630,7 +637,7 @@ abstract class AbstractConnection implements ConnectionInterface * @return PDOStatement|false * @throws CannotBindValue */ - public function prepareWithValues(string statement, array values = []) -> <PDOStatement> + public function prepareWithValues(string statement, array values = []) -> <\PDOStatement> { var key, parser, parts, statement, value, values; array valueNames = []; @@ -844,7 +851,7 @@ abstract class AbstractConnection implements ConnectionInterface * @return bool * @throws CannotBindValue */ - protected function bindValue(<PDOStatement> statement, var key, var value) -> bool + protected function bindValue(<\PDOStatement> statement, var key, var value) -> bool { var type; diff --git a/phalcon/DM/Pdo/Connection/ConnectionInterface.zep b/phalcon/DM/Pdo/Connection/ConnectionInterface.zep index 5d5a7c1b9d..6ad80bf76f 100644 --- a/phalcon/DM/Pdo/Connection/ConnectionInterface.zep +++ b/phalcon/DM/Pdo/Connection/ConnectionInterface.zep @@ -15,8 +15,6 @@ namespace Phalcon\DM\Pdo\Connection; -use PDO; -use PDOStatement; use Phalcon\DM\Pdo\Exception\CannotBindValue; use Phalcon\DM\Pdo\Parser\ParserInterface; use Phalcon\DM\Pdo\Profiler\ProfilerInterface; @@ -187,7 +185,7 @@ interface ConnectionInterface extends PdoInterface * * @return PDO */ - public function getAdapter() -> <PDO>; + public function getAdapter() -> <\PDO>; /** * Returns the Parser instance. @@ -222,7 +220,7 @@ interface ConnectionInterface extends PdoInterface * @return PDOStatement * @throws CannotBindValue */ - public function perform(string statement, array values = []) -> <PDOStatement>; + public function perform(string statement, array values = []) -> <\PDOStatement>; /** * Sets the Parser instance. diff --git a/phalcon/DM/Pdo/Connection/Decorated.zep b/phalcon/DM/Pdo/Connection/Decorated.zep new file mode 100644 index 0000000000..2be734574c --- /dev/null +++ b/phalcon/DM/Pdo/Connection/Decorated.zep @@ -0,0 +1,75 @@ + +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team <team@phalcon.io> + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + * + * Implementation of this file has been influenced by AtlasPHP + * + * @link https://github.com/atlasphp/Atlas.Pdo + * @license https://github.com/atlasphp/Atlas.Pdo/blob/1.x/LICENSE.md + */ + +namespace Phalcon\DM\Pdo\Connection; + +use Phalcon\DM\Pdo\Exception\CannotDisconnect; +use Phalcon\DM\Pdo\Profiler\Profiler; +use Phalcon\DM\Pdo\Profiler\ProfilerInterface; + +/** + * Decorates an existing PDO instance with the extended methods. + */ +class Decorated extends AbstractConnection +{ + /** + * + * Constructor. + * + * This overrides the parent so that it can take an existing PDO instance + * and decorate it with the extended methods. + * + * @param PDO $pdo + * @param ProfilerInterface|null $profiler + * + */ + public function __construct(<\PDO> pdo, <ProfilerInterface> profiler = null) + { + var driver; + + let this->pdo = pdo; + + if null === profiler { + let profiler = new Profiler(); + } + this->setProfiler(profiler); + + let driver = pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + + this->setParser(this->newParser(driver)); + + let this->quote = this->getQuoteNames(driver); + } + + /** + * Connects to the database. + */ + public function connect() -> void + { + // already connected + } + + /** + * Disconnects from the database; disallowed with decorated PDO connections. + * + * @throws CannotDisconnect + */ + public function disconnect() -> void + { + throw new CannotDisconnect( + "Cannot disconnect a Decorated connection instance" + ); + } +} diff --git a/phalcon/DM/Pdo/Connection/PdoInterface.zep b/phalcon/DM/Pdo/Connection/PdoInterface.zep index b308f2b0dd..830dcabf21 100644 --- a/phalcon/DM/Pdo/Connection/PdoInterface.zep +++ b/phalcon/DM/Pdo/Connection/PdoInterface.zep @@ -15,8 +15,6 @@ namespace Phalcon\DM\Pdo\Connection; -use PDO; -use PDOStatement; use Phalcon\DM\Pdo\Exception\CannotBindValue; /** @@ -107,7 +105,7 @@ interface PdoInterface * * @return PDOStatement|false */ - public function prepare(string statement, array options = []) -> <PDOStatement> | bool; + public function prepare(string statement, array options = []) -> <\PDOStatement> | bool; /** * Prepares an SQL statement with bound values. The method only binds values @@ -122,7 +120,7 @@ interface PdoInterface * @return PDOStatement|false * @throws CannotBindValue */ - public function prepareWithValues(string statement, array values = []) -> <PDOStatement>; + public function prepareWithValues(string statement, array values = []) -> <\PDOStatement>; /** * Queries the database and returns a PDOStatement. If the profiler is @@ -133,7 +131,7 @@ interface PdoInterface * * @return PDOStatement|false */ - public function query(string statement) -> <PDOStatement> | bool; + public function query(string statement) -> <\PDOStatement> | bool; /** * Quotes a value for use in an SQL statement. This differs from @@ -181,5 +179,5 @@ interface PdoInterface * * @return bool */ - public function setAttribute(int attribute, mixed value) -> bool; + public function setAttribute(int attribute, var value) -> bool; } diff --git a/phalcon/DM/Pdo/ConnectionLocator.zep b/phalcon/DM/Pdo/ConnectionLocator.zep new file mode 100644 index 0000000000..d68eb87f13 --- /dev/null +++ b/phalcon/DM/Pdo/ConnectionLocator.zep @@ -0,0 +1,219 @@ +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team <team@phalcon.io> + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + * + * Implementation of this file has been influenced by AtlasPHP + * + * @link https://github.com/atlasphp/Atlas.Pdo + * @license https://github.com/atlasphp/Atlas.Pdo/blob/1.x/LICENSE.md + */ + +namespace Phalcon\DM\Pdo; + +use Phalcon\DM\Pdo\Connection\ConnectionInterface; +use Phalcon\DM\Pdo\Exception\ConnectionNotFound; + +/** + * Manages Connection instances for default, read, and write connections. + * + * @property callable $master + * @property array $read + * @property array $write + */ +class ConnectionLocator implements ConnectionLocatorInterface +{ + /** + * A default Connection connection factory/instance. + * + * @var callable + */ + protected master; + + /** + * A registry of Connection "read" factories/instances. + * + * @var array + */ + protected read = []; + + /** + * A registry of Connection "write" factories/instances. + * + * @var array + */ + protected write = []; + + /** + * Collection of resolved instances + * + * @var array + */ + private instances = []; + + /** + * Constructor. + * + * @param callable $master + * @param array $read + * @param array $write + */ + public function __construct( + callable master, + array read = [], + array write = [] + ) { + var name, callableObject; + + if master { + this->setMaster(master); + } + + for name, callableObject in read { + this->setRead(name, callableObject); + } + + for name, callableObject in write { + this->setWrite(name, callableObject); + } + } + + /** + * Returns the default connection object. + * + * @return ConnectionInterface + */ + public function getMaster() -> <ConnectionInterface> + { + return this->master; + } + + /** + * Returns a read connection by name; if no name is given, picks a + * random connection; if no read connections are present, returns the + * default connection. + * + * @param string $name + * + * @return ConnectionInterface + * @throws ConnectionNotFound + */ + public function getRead(string name = "") -> <ConnectionInterface> + { + return this->getConnection("read", name); + } + + /** + * Returns a write connection by name; if no name is given, picks a + * random connection; if no write connections are present, returns the + * default connection. + * + * @param string $name + * + * @return ConnectionInterface + * @throws ConnectionNotFound + */ + public function getWrite(string name = "") -> <ConnectionInterface> + { + return this->getConnection("write", name); + } + + /** + * Sets the default connection factory. + * + * @param callable|null $callable + * + * @return ConnectionLocatorInterface + */ + public function setMaster(callable callableObject) -> <ConnectionLocatorInterface> + { + let this->master = callableObject; + + return this; + } + + /** + * Sets a read connection factory by name. + * + * @param string $name + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setRead( + string name, + callable callableObject + ) -> <ConnectionLocatorInterface> { + let this->read[name] = callableObject; + + return this; + } + + /** + * Sets a write connection factory by name. + * + * @param string $name + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setWrite( + string name, + callable callableObject + ) -> <ConnectionLocatorInterface> { + let this->write[name] = callableObject; + + return this; + } + + protected function getConnection( + string type, + string name = "" + ) -> <ConnectionInterface> { + var collection, instanceName, instances, requested; + + let collection = this->{type}, + requested = name, + instances = this->instances; + + /** + * No collection returns the master + */ + if empty collection { + return this->getMaster(); + } + + /** + * If the requested name is empty, get a random connection + */ + if "" === requested { + let requested = array_rand(collection); + } + + /** + * If the connection name does not exist, send an exception back + */ + if !isset collection[requested] { + throw new ConnectionNotFound( + "Connection not found: " . type . ":" . requested + ); + } + + /** + * Check if the connection has been resolved already, if yes return + * it, otherwise resolve it. The keys in the `resolved` array are + * formatted as "type-name" + */ + let instanceName = this->{type} . "-" . requested; + + if !isset instances[instanceName] { + let instances[instanceName] = call_user_func(collection[requested]), + this->instances = instances; + } + + return instances[instanceName]; + } +} diff --git a/phalcon/DM/Pdo/ConnectionLocatorInterface.zep b/phalcon/DM/Pdo/ConnectionLocatorInterface.zep new file mode 100644 index 0000000000..913c97af8a --- /dev/null +++ b/phalcon/DM/Pdo/ConnectionLocatorInterface.zep @@ -0,0 +1,82 @@ + +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team <team@phalcon.io> + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + * + * Implementation of this file has been influenced by AtlasPHP + * + * @link https://github.com/atlasphp/Atlas.Pdo + * @license https://github.com/atlasphp/Atlas.Pdo/blob/1.x/LICENSE.md + */ + +namespace Phalcon\DM\Pdo; + +use Phalcon\DM\Pdo\Connection\ConnectionInterface; + +/** + * Locates PDO connections for default, read, and write databases. + */ +interface ConnectionLocatorInterface +{ + /** + * Returns the default connection object. + * + * @return ConnectionInterface + */ + public function getMaster() -> <ConnectionInterface>; + + /** + * Returns a read connection by name; if no name is given, picks a + * random connection; if no read connections are present, returns the + * default connection. + * + * @param string $name + * + * @return ConnectionInterface + */ + public function getRead(string name = "") -> <ConnectionInterface>; + + /** + * Returns a write connection by name; if no name is given, picks a + * random connection; if no write connections are present, returns the + * default connection. + * + * @param string $name + * + * @return ConnectionInterface + */ + public function getWrite(string name = "") -> <ConnectionInterface>; + + /** + * Sets the default connection registry entry. + * + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setMaster(callable callableObject) -> <ConnectionLocatorInterface>; + + /** + * Sets a read connection registry entry by name. + * + * @param string $name + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setRead(string name, callable callableObject) -> <ConnectionLocatorInterface>; + + /** + * Sets a write connection registry entry by name. + * + * @param string $name + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setWrite(string name, callable callableObject) -> <ConnectionLocatorInterface>; +}