From 4bde759a57dd82fa1d2d0d26fb9ffc3e8de10583 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 25 Jan 2020 00:28:28 -0500 Subject: [PATCH] [#14733] - Connection and connection locator; Fixes for the abstract class --- phalcon/DM/Pdo/Connection.zep | 163 +++++++++++++ .../DM/Pdo/Connection/AbstractConnection.zep | 31 ++- .../DM/Pdo/Connection/ConnectionInterface.zep | 6 +- phalcon/DM/Pdo/Connection/Decorated.zep | 75 ++++++ phalcon/DM/Pdo/Connection/PdoInterface.zep | 10 +- phalcon/DM/Pdo/ConnectionLocator.zep | 219 ++++++++++++++++++ phalcon/DM/Pdo/ConnectionLocatorInterface.zep | 82 +++++++ 7 files changed, 564 insertions(+), 22 deletions(-) create mode 100644 phalcon/DM/Pdo/Connection.zep create mode 100644 phalcon/DM/Pdo/Connection/Decorated.zep create mode 100644 phalcon/DM/Pdo/ConnectionLocator.zep create mode 100644 phalcon/DM/Pdo/ConnectionLocatorInterface.zep diff --git a/phalcon/DM/Pdo/Connection.zep b/phalcon/DM/Pdo/Connection.zep new file mode 100644 index 00000000000..a6855d5e7fb --- /dev/null +++ b/phalcon/DM/Pdo/Connection.zep @@ -0,0 +1,163 @@ +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team + * + * 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 = [], + 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 669773aaf73..f6146de3172 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() -> + 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 = []) -> + 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 = []) -> + 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( 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 5d5a7c1b9d7..6ad80bf76f6 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() -> ; + 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 = []) -> ; + 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 00000000000..2be734574c3 --- /dev/null +++ b/phalcon/DM/Pdo/Connection/Decorated.zep @@ -0,0 +1,75 @@ + +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team + * + * 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, 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 b308f2b0dd3..830dcabf21f 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 = []) -> | 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 = []) -> ; + 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) -> | 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 00000000000..d68eb87f13f --- /dev/null +++ b/phalcon/DM/Pdo/ConnectionLocator.zep @@ -0,0 +1,219 @@ +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team + * + * 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() -> + { + 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 = "") -> + { + 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 = "") -> + { + return this->getConnection("write", name); + } + + /** + * Sets the default connection factory. + * + * @param callable|null $callable + * + * @return ConnectionLocatorInterface + */ + public function setMaster(callable callableObject) -> + { + 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 + ) -> { + 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 + ) -> { + let this->write[name] = callableObject; + + return this; + } + + protected function getConnection( + string type, + string name = "" + ) -> { + 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 00000000000..913c97af8a7 --- /dev/null +++ b/phalcon/DM/Pdo/ConnectionLocatorInterface.zep @@ -0,0 +1,82 @@ + +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team + * + * 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() -> ; + + /** + * 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 = "") -> ; + + /** + * 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 = "") -> ; + + /** + * Sets the default connection registry entry. + * + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setMaster(callable callableObject) -> ; + + /** + * Sets a read connection registry entry by name. + * + * @param string $name + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setRead(string name, callable callableObject) -> ; + + /** + * Sets a write connection registry entry by name. + * + * @param string $name + * @param callable $callable + * + * @return ConnectionLocatorInterface + */ + public function setWrite(string name, callable callableObject) -> ; +}