Fixed Broken Shit

This commit is contained in:
2022-10-30 14:32:20 -07:00
parent 7335796263
commit 4dabf5a6bf
635 changed files with 74885 additions and 17688 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,203 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
/**
*
* Manages ExtendedPdo instances for default, read, and write connections.
*
* @package Aura.Sql
*
*/
class ConnectionLocator implements ConnectionLocatorInterface
{
/**
*
* A default ExtendedPdo connection factory/instance.
*
* @var callable
*
*/
protected $default;
/**
*
* A registry of ExtendedPdo "read" factories/instances.
*
* @var array
*
*/
protected $read = [];
/**
*
* A registry of ExtendedPdo "write" factories/instances.
*
* @var array
*
*/
protected $write = [];
/**
*
* Constructor.
*
* @param callable $default A callable to create a default connection.
*
* @param array $read An array of callables to create read connections.
*
* @param array $write An array of callables to create write connections.
*
*/
public function __construct(
$default = null,
array $read = [],
array $write = []
) {
if ($default) {
$this->setDefault($default);
}
foreach ($read as $name => $callable) {
$this->setRead($name, $callable);
}
foreach ($write as $name => $callable) {
$this->setWrite($name, $callable);
}
}
/**
*
* Sets the default connection factory.
*
* @param callable $callable The factory for the connection.
*
* @return null
*
*/
public function setDefault(callable $callable)
{
$this->default = $callable;
}
/**
*
* Returns the default connection object.
*
* @return ExtendedPdoInterface
*
*/
public function getDefault()
{
if (! $this->default instanceof ExtendedPdo) {
$this->default = call_user_func($this->default);
}
return $this->default;
}
/**
*
* Sets a read connection factory by name.
*
* @param string $name The name of the connection.
*
* @param callable $callable The factory for the connection.
*
* @return null
*
*/
public function setRead($name, callable $callable)
{
$this->read[$name] = $callable;
}
/**
*
* 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 The read connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getRead($name = '')
{
return $this->getConnection('read', $name);
}
/**
*
* Sets a write connection factory by name.
*
* @param string $name The name of the connection.
*
* @param callable $callable The factory for the connection.
*
* @return null
*
*/
public function setWrite($name, callable $callable)
{
$this->write[$name] = $callable;
}
/**
*
* 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 The write connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getWrite($name = '')
{
return $this->getConnection('write', $name);
}
/**
*
* Returns a connection by name.
*
* @param string $type The connection type ('read' or 'write').
*
* @param string $name The name of the connection.
*
* @return ExtendedPdoInterface
*
* @throws Exception\ConnectionNotFound
*
*/
protected function getConnection($type, $name)
{
$conn = &$this->{$type};
if (empty($conn)) {
return $this->getDefault();
}
if ($name === '') {
$name = array_rand($conn);
}
if (! isset($conn[$name])) {
throw new Exception\ConnectionNotFound("{$type}:{$name}");
}
if (! $conn[$name] instanceof ExtendedPdo) {
$conn[$name] = call_user_func($conn[$name]);
}
return $conn[$name];
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
/**
*
* Locates PDO connections for default, read, and write databases.
*
* @package Aura.Sql
*
*/
interface ConnectionLocatorInterface
{
/**
*
* Sets the default connection registry entry.
*
* @param callable $callable The registry entry.
*
* @return null
*
*/
public function setDefault(callable $callable);
/**
*
* Returns the default connection object.
*
* @return ExtendedPdoInterface
*
*/
public function getDefault();
/**
*
* Sets a read connection registry entry by name.
*
* @param string $name The name of the registry entry.
*
* @param callable $callable The registry entry.
*
* @return null
*
*/
public function setRead($name, callable $callable);
/**
*
* 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 The read connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getRead($name = '');
/**
*
* Sets a write connection registry entry by name.
*
* @param string $name The name of the registry entry.
*
* @param callable $callable The registry entry.
*
* @return null
*
*/
public function setWrite($name, callable $callable);
/**
*
* 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 The write connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getWrite($name = '');
}

View File

@@ -0,0 +1,74 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use Aura\Sql\Profiler\Profiler;
use Aura\Sql\Profiler\ProfilerInterface;
use PDO;
/**
*
* Decorates an existing PDO instance with the extended methods.
*
* @package Aura.Sql
*
*/
class DecoratedPdo extends AbstractExtendedPdo
{
/**
*
* Constructor.
*
* This overrides the parent so that it can take an existing PDO instance
* and decorate it with the extended methods.
*
* @param PDO $pdo An existing PDO instance to decorate.
*
* @param ProfilerInterface $profiler Tracks and logs query profiles.
*
*/
public function __construct(PDO $pdo, ProfilerInterface $profiler = null)
{
$this->pdo = $pdo;
if ($profiler === null) {
$profiler = new Profiler();
}
$this->setProfiler($profiler);
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$this->setParser($this->newParser($driver));
$this->setQuoteName($driver);
}
/**
*
* Connects to the database.
*
* @return null
*
*/
public function connect()
{
// already connected
}
/**
*
* Disconnects from the database; disallowed with decorated PDO connections.
*
* @return null
*
*/
public function disconnect()
{
$message = "Cannot disconnect a DecoratedPdo instance.";
throw new Exception\CannotDisconnect($message);
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
/**
*
* Base Exception class for Aura Sql
*
* @package Aura.Sql
*
*/
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,23 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* Could not bind a value to a placeholder in a statement, generally because
* the value is an array, object, or resource.
*
* @package Aura.Sql
*
*/
class CannotBindValue extends Exception
{
}

View File

@@ -0,0 +1,23 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* ExtendedPdo could not disconnect; e.g., because its PDO connection was
* created externally and then injected.
*
* @package Aura.Sql
*
*/
class CannotDisconnect extends Exception
{
}

View File

@@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* Locator could not find a named connection.
*
* @package Aura.Sql
*
*/
class ConnectionNotFound extends Exception
{
}

View File

@@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* Missing a parameter in the values bound to a statement
*
* @package Aura.Sql
*
*/
class MissingParameter extends Exception
{
}

View File

@@ -0,0 +1,163 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use Aura\Sql\Profiler\Profiler;
use Aura\Sql\Profiler\ProfilerInterface;
use PDO;
/**
*
* A lazy-connecting PDO with extended methods.
*
* @package Aura.Sql
*
*/
class ExtendedPdo extends AbstractExtendedPdo
{
/**
*
* Constructor arguments for instantiating the PDO connection.
*
* @var array
*
*/
protected $args = [];
/**
*
* Constructor.
*
* This overrides the parent so that it can take connection attributes as a
* constructor parameter, and set them after connection.
*
* @param string $dsn The data source name for the connection.
*
* @param string $username The username for the connection.
*
* @param string $password The password for the connection.
*
* @param array $options Driver-specific options for the connection.
*
* @param array $queries Queries to execute after the connection.
*
* @param ProfilerInterface $profiler Tracks and logs query profiles.
*
* @see http://php.net/manual/en/pdo.construct.php
*
*/
public function __construct(
$dsn,
$username = null,
$password = null,
array $options = [],
array $queries = [],
ProfilerInterface $profiler = null
) {
// if no error mode is specified, use exceptions
if (! isset($options[PDO::ATTR_ERRMODE])) {
$options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
}
// retain the arguments for later
$this->args = [
$dsn,
$username,
$password,
$options,
$queries
];
// retain a profiler, instantiating a default one if needed
if ($profiler === null) {
$profiler = new Profiler();
}
$this->setProfiler($profiler);
// retain a query parser
$parts = explode(':', $dsn);
$parser = $this->newParser($parts[0]);
$this->setParser($parser);
// set quotes for identifier names
$this->setQuoteName($parts[0]);
}
/**
*
* Connects to the database.
*
* @return null
*
*/
public function connect()
{
if ($this->pdo) {
return;
}
// connect
$this->profiler->start(__FUNCTION__);
list($dsn, $username, $password, $options, $queries) = $this->args;
$this->pdo = new PDO($dsn, $username, $password, $options);
$this->profiler->finish();
// connection-time queries
foreach ($queries as $query) {
$this->exec($query);
}
}
/**
*
* Disconnects from the database.
*
* @return null
*
*/
public function disconnect()
{
$this->profiler->start(__FUNCTION__);
$this->pdo = null;
$this->profiler->finish();
}
/**
*
* The purpose of this method is to hide sensitive data from stack traces.
*
* @return array
*
*/
public function __debugInfo()
{
return [
'args' => [
$this->args[0],
'****',
'****',
$this->args[3],
$this->args[4],
]
];
}
/**
*
* Return the inner PDO (if any)
*
* @return \PDO
*
*/
public function getPdo()
{
$this->connect();
return $this->pdo;
}
}

View File

@@ -0,0 +1,412 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use Aura\Sql\Parser\ParserInterface;
use Aura\Sql\Profiler\ProfilerInterface;
use PDO;
/**
*
* An interface to the Aura.Sql extended PDO object.
*
* @package Aura.Sql
*
*/
interface ExtendedPdoInterface extends PdoInterface
{
/**
*
* Connects to the database.
*
*/
public function connect();
/**
*
* Disconnects from the database.
*
*/
public function disconnect();
/**
*
* Performs a statement and returns the number of affected rows.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return int
*
*/
public function fetchAffected($statement, array $values = []);
/**
*
* Fetches a sequential array of rows from the database; the rows
* are represented as associative arrays.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchAll($statement, array $values = []);
/**
*
* Fetches an associative array of rows from the database; the rows
* are represented as associative arrays. The array of rows is keyed
* on the first column of each row.
*
* N.b.: if multiple rows have the same first column value, the last
* row with that value will override earlier rows.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchAssoc($statement, array $values = []);
/**
*
* Fetches the first column of rows as a sequential array.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchCol($statement, array $values = []);
/**
*
* Fetches multiple from the database as an associative array.
* The first column will be the index
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param int $style a fetch style defaults to PDO::FETCH_COLUMN for single
* values, use PDO::FETCH_NAMED when fetching a multiple columns
*
* @return array
*
*/
public function fetchGroup(
$statement,
array $values = [],
$style = PDO::FETCH_COLUMN
);
/**
*
* Fetches one row from the database as an object, mapping column values
* to object properties.
*
* Warning: PDO "injects property-values BEFORE invoking the constructor -
* in other words, if your class initializes property-values to defaults
* in the constructor, you will be overwriting the values injected by
* fetchObject() !"
* <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744>
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param string $class The name of the class to create.
*
* @param array $args Arguments to pass to the object constructor.
*
* @return object
*
*/
public function fetchObject(
$statement,
array $values = [],
$class = 'stdClass',
array $args = []
);
/**
*
* Fetches a sequential array of rows from the database; the rows
* are represented as objects, where the column values are mapped to
* object properties.
*
* Warning: PDO "injects property-values BEFORE invoking the constructor -
* in other words, if your class initializes property-values to defaults
* in the constructor, you will be overwriting the values injected by
* fetchObject() !"
* <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744>
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param string $class The name of the class to create from each
* row.
*
* @param array $args Arguments to pass to each object constructor.
*
* @return array
*
*/
public function fetchObjects(
$statement,
array $values = [],
$class = 'stdClass',
array $args = []
);
/**
*
* Fetches one row from the database as an associative array.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchOne($statement, array $values = []);
/**
*
* Fetches an associative array of rows as key-value pairs (first
* column is the key, second column is the value).
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchPairs($statement, array $values = []);
/**
*
* Fetches the very first value (i.e., first column of the first row).
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return mixed
*
*/
public function fetchValue($statement, array $values = []);
/**
*
* Returns the Parser instance.
*
* @return ParserInterface
*
*/
public function getParser();
/**
*
* Return the inner PDO (if any)
*
* @return \PDO
*
*/
public function getPdo();
/**
*
* Returns the Profiler instance.
*
* @return ProfilerInterface
*
*/
public function getProfiler();
/**
*
* Quotes a multi-part (dotted) identifier name.
*
* @param string $name The multi-part identifier name.
*
* @return string The multi-part identifier name, quoted.
*
*/
public function quoteName($name);
/**
*
* Quotes a single identifier name.
*
* @param string $name The identifier name.
*
* @return string The quoted identifier name.
*
*/
public function quoteSingleName($name);
/**
*
* Is the PDO connection active?
*
* @return bool
*
*/
public function isConnected();
/**
*
* Sets the Parser instance.
*
* @param ParserInterface $parser The Parser instance.
*
*/
public function setParser(ParserInterface $parser);
/**
*
* Sets the Profiler instance.
*
* @param ProfilerInterface $profiler The Profiler instance.
*
*/
public function setProfiler(ProfilerInterface $profiler);
/**
*
* Yields rows from the database
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldAll($statement, array $values = []);
/**
*
* Yields rows from the database keyed on the first column of each row.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldAssoc($statement, array $values = []);
/**
*
* Yields the first column of all rows
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldCol($statement, array $values = []);
/**
*
* Yields objects where the column values are mapped to object properties.
*
* Warning: PDO "injects property-values BEFORE invoking the constructor -
* in other words, if your class initializes property-values to defaults
* in the constructor, you will be overwriting the values injected by
* fetchObject() !"
* <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744>
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param string $class The name of the class to create from each
* row.
*
* @param array $args Arguments to pass to each object constructor.
*
* @return \Generator
*
*/
public function yieldObjects(
$statement,
array $values = [],
$class = 'stdClass',
array $args = []
);
/**
*
* Yields key-value pairs (first column is the key, second column is the
* value).
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldPairs($statement, array $values = []);
/**
*
* Performs a query after preparing the statement with bound values, then
* returns the result as a PDOStatement.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \PDOStatement
*
*/
public function perform($statement, array $values = []);
/**
*
* Prepares an SQL statement with bound values.
*
* This method only binds values that have placeholders in the
* statement, thereby avoiding errors from PDO regarding too many bound
* values. It also binds all sequential (question-mark) placeholders.
*
* If a placeholder value is an array, the array is converted to a string
* of comma-separated quoted values; e.g., for an `IN (...)` condition.
* The quoted string is replaced directly into the statement instead of
* using `PDOStatement::bindValue()` proper.
*
* @param string $statement The SQL statement to prepare for execution.
*
* @param array $values The values to bind to the statement, if any.
*
* @return \PDOStatement
*
* @see http://php.net/manual/en/pdo.prepare.php
*
*/
public function prepareWithValues($statement, array $values = []);
}

View File

@@ -0,0 +1,318 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
use Aura\Sql\Exception\MissingParameter;
/**
*
* Parsing/rebuilding functionality for all drivers.
*
* Note that this does not validate the syntax; it only replaces/rebuilds
* placeholders in the query.
*
* @package Aura.Sql
*
*/
abstract class AbstractParser implements ParserInterface
{
/**
*
* Split the query string on these regexes.
*
* @var array
*
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
];
/**
*
* Skip query parts matching this regex.
*
* @var string
*
*/
protected $skip = '/^(\'|\"|\:[^a-zA-Z_])/um';
/**
*
* The current numbered-placeholder in the original statement.
*
* @var int
*
*/
protected $num = 0;
/**
*
* How many times has a named placeholder been used?
*
* @var array
*
*/
protected $count = [
'__' => null,
];
/**
*
* The initial values to be bound.
*
* @var array
*
*/
protected $values = [];
/**
*
* Final placeholders and values to bind.
*
* @var array
*
*/
protected $final_values = [];
/**
*
* Rebuilds a statement with placeholders and bound values.
*
* @param string $statement The statement to rebuild.
*
* @param array $values The values to bind and/or replace into a statement.
*
* @return array An array where element 0 is the rebuilt statement and
* element 1 is the rebuilt array of values.
*
*/
public function rebuild($statement, array $values = [])
{
// match standard PDO execute() behavior of zero-indexed arrays
if (array_key_exists(0, $values)) {
array_unshift($values, null);
}
$this->values = $values;
$statement = $this->rebuildStatement($statement);
return [$statement, $this->final_values];
}
/**
*
* Given a statement, rebuilds it with array values embedded.
*
* @param string $statement The SQL statement.
*
* @return string The rebuilt statement.
*
*/
protected function rebuildStatement($statement)
{
$parts = $this->getParts($statement);
return $this->rebuildParts($parts);
}
/**
*
* Given an array of statement parts, rebuilds each part.
*
* @param array $parts The statement parts.
*
* @return string The rebuilt statement.
*
*/
protected function rebuildParts(array $parts)
{
$statement = '';
foreach ($parts as $part) {
$statement .= $this->rebuildPart($part);
}
return $statement;
}
/**
*
* Rebuilds a single statement part.
*
* @param string $part The statement part.
*
* @return string The rebuilt statement.
*
*/
protected function rebuildPart($part)
{
if (preg_match($this->skip, $part)) {
return $part;
}
// split into subparts by ":name" and "?"
$subs = preg_split(
"/(?<!:)(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/um",
$part,
-1,
PREG_SPLIT_DELIM_CAPTURE
);
// check subparts to expand placeholders for bound arrays
return $this->prepareValuePlaceholders($subs);
}
/**
*
* Prepares the sub-parts of a query with placeholders.
*
* @param array $subs The query subparts.
*
* @return string The prepared subparts.
*
*/
protected function prepareValuePlaceholders(array $subs)
{
$str = '';
foreach ($subs as $i => $sub) {
$char = substr($sub, 0, 1);
if ($char == '?') {
$str .= $this->prepareNumberedPlaceholder();
} elseif ($char == ':') {
$str .= $this->prepareNamedPlaceholder($sub);
} else {
$str .= $sub;
}
}
return $str;
}
/**
*
* Bind or quote a numbered placeholder in a query subpart.
*
* @return string The prepared query subpart.
*
* @throws MissingParameter
*/
protected function prepareNumberedPlaceholder()
{
$this->num ++;
if (array_key_exists($this->num, $this->values) === false) {
throw new MissingParameter("Parameter {$this->num} is missing from the bound values");
}
$expanded = [];
$values = (array) $this->values[$this->num];
if (is_null($this->values[$this->num])) {
$values[] = null;
}
foreach ($values as $value) {
$count = ++ $this->count['__'];
$name = "__{$count}";
$expanded[] = ":{$name}";
$this->final_values[$name] = $value;
}
return implode(', ', $expanded);
}
/**
*
* Bind or quote a named placeholder in a query subpart.
*
* @param string $sub The query subpart.
*
* @return string The prepared query subpart.
*
*/
protected function prepareNamedPlaceholder($sub)
{
$orig = substr($sub, 1);
if (array_key_exists($orig, $this->values) === false) {
throw new MissingParameter("Parameter '{$orig}' is missing from the bound values");
}
$name = $this->getPlaceholderName($orig);
// is the corresponding data element an array?
$bind_array = is_array($this->values[$orig]);
if ($bind_array) {
// expand to multiple placeholders
return $this->expandNamedPlaceholder($name, $this->values[$orig]);
}
// not an array, retain the placeholder for later
$this->final_values[$name] = $this->values[$orig];
return ":$name";
}
/**
*
* Given an original placeholder name, return a replacement name.
*
* @param string $orig The original placeholder name.
*
* @return string
*
*/
protected function getPlaceholderName($orig)
{
if (! isset($this->count[$orig])) {
$this->count[$orig] = 0;
return $orig;
}
$count = ++ $this->count[$orig];
return "{$orig}__{$count}";
}
/**
*
* Given a named placeholder for an array, expand it for the array values,
* and bind those values to the expanded names.
*
* @param string $prefix The named placeholder.
*
* @param array $values The array values to be bound.
*
* @return string
*
*/
protected function expandNamedPlaceholder($prefix, array $values)
{
$i = 0;
$expanded = [];
foreach ($values as $value) {
$name = "{$prefix}_{$i}";
$expanded[] = ":{$name}";
$this->final_values[$name] = $value;
$i ++;
}
return implode(', ', $expanded);
}
/**
*
* Given a query string, split it into parts.
*
* @param string $statement The query string.
*
* @return array
*
*/
protected function getParts($statement)
{
$split = implode('|', $this->split);
return preg_split(
"/($split)/um",
$statement,
-1,
PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the mysql driver.
*
* @package Aura.Sql
*
*/
class MysqlParser extends AbstractParser
{
/**
*
* Split the query string on these regexes.
*
* @var array
*
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
// backtick-quoted string
'`(?:[^`\\\\]|\\\\`?)*`',
];
/**
*
* Skip query parts matching this regex.
*
* @var string
*
*/
protected $skip = '/^(\'|\"|\`)/um';
}

View File

@@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* A parser/rebuilder that does nothing at all; use this when your placeholders
* and bound-values are already perfectly matched.
*
* @package Aura.Sql
*
*/
class NullParser implements ParserInterface
{
/**
*
* Leaves the query and parameters alone.
*
* @param string $statement The query statement string.
*
* @param array $values Bind these values into the query.
*
* @return array
*
*/
public function rebuild($statement, array $values = [])
{
return [$statement, $values];
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Interface for query parsing/rebuilding functionality.
*
* @package Aura.Sql
*
*/
interface ParserInterface
{
/**
*
* Rebuilds a query and its parameters to adapt it to PDO's limitations,
* and returns a list of queries.
*
* @param string $string The query statement string.
*
* @param array $parameters Bind these values into the query.
*
* @return array An array where element 0 is the rebuilt statement and
* element 1 is the rebuilt array of values.
*
*/
public function rebuild($string, array $parameters = []);
}

View File

@@ -0,0 +1,46 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the pgsl driver.
*
* @package Aura.Sql
*
*/
class PgsqlParser extends AbstractParser
{
/**
*
* Split the query string on these regexes.
*
* @var array
*
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
// double-dollar string (empty dollar-tag)
'\$\$(?:[^\$]?)*\$\$',
// dollar-tag string -- DOES NOT match tags properly
'\$[^\$]+\$.*\$[^\$]+\$',
];
/**
*
* Skip query parts matching this regex.
*
* @var string
*
*/
protected $skip = '/^(\'|\"|\$|\:[^a-zA-Z_])/um';
}

View File

@@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the sqlite driver.
*
* @package Aura.Sql
*
*/
class SqliteParser extends AbstractParser
{
/**
* {@inheritDoc}
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
// backticked column names
'`(?:[^`\\\\]|\\\\`?)*`',
];
/**
* {@inheritDoc}
*/
protected $skip = '/^(\'|"|`|\:[^a-zA-Z_])/um';
}

View File

@@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the sqlsrv driver.
*
* @package Aura.Sql
*
* @todo add $split and $skip for single quote, double quote, and square brackets
*
*/
class SqlsrvParser extends AbstractParser
{
}

View File

@@ -0,0 +1,189 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use PDO;
/**
*
* An interface to the native PDO object.
*
* @package Aura.Sql
*
*/
interface PdoInterface
{
/**
*
* Begins a transaction and turns off autocommit mode.
*
* @return bool True on success, false on failure.
*
* @see http://php.net/manual/en/pdo.begintransaction.php
*
*/
public function beginTransaction();
/**
*
* Commits the existing transaction and restores autocommit mode.
*
* @return bool True on success, false on failure.
*
* @see http://php.net/manual/en/pdo.commit.php
*
*/
public function commit();
/**
*
* Gets the most recent error code.
*
* @return mixed
*
*/
public function errorCode();
/**
*
* Gets the most recent error info.
*
* @return array
*
*/
public function errorInfo();
/**
*
* Executes an SQL statement and returns the number of affected rows.
*
* @param string $statement The SQL statement to execute.
*
* @return int The number of rows affected.
*
* @see http://php.net/manual/en/pdo.exec.php
*
*/
public function exec($statement);
/**
*
* Gets a PDO attribute value.
*
* @param mixed $attribute The PDO::ATTR_* constant.
*
* @return mixed The value for the attribute.
*
*/
public function getAttribute($attribute);
/**
*
* Is a transaction currently active?
*
* @return bool
*
* @see http://php.net/manual/en/pdo.intransaction.php
*
*/
public function inTransaction();
/**
*
* Returns the last inserted autoincrement sequence value.
*
* @param string $name The name of the sequence to check; typically needed
* only for PostgreSQL, where it takes the form of `<table>_<column>_seq`.
*
* @return string
*
* @see http://php.net/manual/en/pdo.lastinsertid.php
*
*/
public function lastInsertId($name = null);
/**
*
* Prepares an SQL statement for execution.
*
* @param string $statement The SQL statement to prepare for execution.
*
* @param array $options Set these attributes on the returned
* PDOStatement.
*
* @return \PDOStatement
*
* @see http://php.net/manual/en/pdo.prepare.php
*
*/
public function prepare($statement, $options = null);
/**
*
* Queries the database and returns a PDOStatement.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param mixed ...$fetch Optional fetch-related parameters.
*
* @return \PDOStatement
*
* @see http://php.net/manual/en/pdo.query.php
*
*/
public function query($statement, ...$fetch);
/**
*
* Quotes a value for use in an SQL statement.
*
* @param mixed $value The value to quote.
*
* @param int $parameter_type A data type hint for the database driver.
*
* @return string The quoted value.
*
* @see http://php.net/manual/en/pdo.quote.php
*
*/
public function quote($value, $parameter_type = PDO::PARAM_STR);
/**
*
* Rolls back the current transaction and restores autocommit mode.
*
* @return bool True on success, false on failure.
*
* @see http://php.net/manual/en/pdo.rollback.php
*
*/
public function rollBack();
/**
*
* Sets a PDO attribute value.
*
* @param mixed $attribute The PDO::ATTR_* constant.
*
* @param mixed $value The value for the attribute.
*
* @return bool
*
*/
public function setAttribute($attribute, $value);
/**
*
* Returns all currently available PDO drivers.
*
* @return array
*
*/
public static function getAvailableDrivers();
}

View File

@@ -0,0 +1,64 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Profiler;
use Psr\Log\AbstractLogger;
/**
*
* A naive memory-based logger.
*
* @package Aura.Sql
*
*/
class MemoryLogger extends AbstractLogger
{
/**
*
* Log messages.
*
* @var array
*
*/
protected $messages = [];
/**
*
* Logs a message.
*
* @param mixed $level The log level (ignored).
*
* @param string $message The log message.
*
* @param array $context Data to interpolate into the message.
*
* @return null
*
*/
public function log($level, $message, array $context = [])
{
$replace = [];
foreach ($context as $key => $val) {
$replace['{' . $key . '}'] = $val;
}
$this->messages[] = strtr($message, $replace);
}
/**
*
* Returns the logged messages.
*
* @return array
*
*/
public function getMessages()
{
return $this->messages;
}
}

View File

@@ -0,0 +1,229 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Profiler;
use Aura\Sql\Exception;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
*
* Sends query profiles to a logger.
*
* @package Aura.Sql
*
*/
class Profiler implements ProfilerInterface
{
/**
*
* The current profile information.
*
* @var array
*
*/
protected $context = [];
/**
*
* Log profile data through this interface.
*
* @var LoggerInterface
*
*/
protected $logger;
/**
*
* Turns profile logging off and on.
*
* @var bool
*
* @see setActive()
*
*/
protected $active = false;
/**
*
* The log level for all messages.
*
* @var string
*
* @see setLogLevel()
*
*/
protected $logLevel = LogLevel::DEBUG;
/**
*
* Sets the format for the log message, with placeholders.
*
* @var string
*
* @see setLogFormat()
*
*/
protected $logFormat = "{function} ({duration} seconds): {statement} {backtrace}";
/**
*
* Constructor.
*
* @param LoggerInterface $logger Record profiles through this interface.
*
*/
public function __construct(LoggerInterface $logger = null)
{
if ($logger === null) {
$logger = new MemoryLogger();
}
$this->logger = $logger;
}
/**
*
* Enable or disable profiler logging.
*
* @param bool $active
*
*/
public function setActive($active)
{
$this->active = (bool) $active;
}
/**
*
* Returns true if logging is active.
*
* @return bool
*
*/
public function isActive()
{
return $this->active;
}
/**
*
* Returns the underlying logger instance.
*
* @return \Psr\Log\LoggerInterface
*
*/
public function getLogger()
{
return $this->logger;
}
/**
*
* Returns the level at which to log profile messages.
*
* @return string
*
*/
public function getLogLevel()
{
return $this->logLevel;
}
/**
*
* Level at which to log profile messages.
*
* @param string $logLevel A PSR LogLevel constant.
*
* @return null
*
*/
public function setLogLevel($logLevel)
{
$this->logLevel = $logLevel;
}
/**
*
* Returns the log message format string, with placeholders.
*
* @return string
*
*/
public function getLogFormat()
{
return $this->logFormat;
}
/**
*
* Sets the log message format string, with placeholders.
*
* @param string $logFormat
*
* @return null
*
*/
public function setLogFormat($logFormat)
{
$this->logFormat = $logFormat;
}
/**
*
* Starts a profile entry.
*
* @param string $function The function starting the profile entry.
*
* @return null
*
*/
public function start($function)
{
if (! $this->active) {
return;
}
$this->context = [
'function' => $function,
'start' => microtime(true),
];
}
/**
*
* Finishes and logs a profile entry.
*
* @param string $statement The statement being profiled, if any.
*
* @param array $values The values bound to the statement, if any.
*
* @return null
*
*/
public function finish($statement = null, array $values = [])
{
if (! $this->active) {
return;
}
$finish = microtime(true);
$e = new Exception();
$this->context['finish'] = $finish;
$this->context['duration'] = $finish - $this->context['start'];
$this->context['statement'] = $statement;
$this->context['values'] = empty($values) ? '' : print_r($values, true);
$this->context['backtrace'] = $e->getTraceAsString();
$this->logger->log($this->logLevel, $this->logFormat, $this->context);
$this->context = [];
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Profiler;
/**
*
* Interface to send query profiles to a logger.
*
* @package Aura.Sql
*
*/
interface ProfilerInterface
{
/**
*
* Enable or disable profiler logging.
*
* @param bool $active
*
*/
public function setActive($active);
/**
*
* Returns true if logging is active.
*
* @return bool
*
*/
public function isActive();
/**
*
* Returns the underlying logger instance.
*
* @return \Psr\Log\LoggerInterface
*
*/
public function getLogger();
/**
*
* Returns the level at which to log profile messages.
*
* @return string
*
*/
public function getLogLevel();
/**
*
* Level at which to log profile messages.
*
* @param string $logLevel A PSR LogLevel constant.
*
* @return null
*
*/
public function setLogLevel($logLevel);
/**
*
* Returns the log message format string, with placeholders.
*
* @return string
*
*/
public function getLogFormat();
/**
*
* Sets the log message format string, with placeholders.
*
* @param string $logFormat
*
* @return null
*
*/
public function setLogFormat($logFormat);
/**
*
* Starts a profile entry.
*
* @param string $function The function starting the profile entry.
*
* @return null
*
*/
public function start($function);
/**
*
* Finishes and logs a profile entry.
*
* @param string $statement The statement being profiled, if any.
*
* @param array $values The values bound to the statement, if any.
*
* @return null
*
*/
public function finish($statement = null, array $values = []);
}