Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,197 @@
<?php
namespace Xentral\Components\Database\Adapter;
use Generator;
interface AdapterInterface
{
/**
* @return void
*/
public function connect();
/**
* @return void
*/
public function disconnect();
/**
* @return bool
*/
public function inTransaction();
/**
* @return void
*/
public function beginTransaction();
/**
* @return void
*/
public function rollback();
/**
* @return void
*/
public function commit();
/**
* @return int
*/
public function lastInsertId();
/**
* @param array $values
* @param string $statement
*
* @return void
*/
public function perform($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return int
*/
public function fetchAffected($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAll($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAssoc($statement, array $values = []);
/**
* @param string $statement
* @param array $values
* @param bool $includeGroupColumn
*
* @return array
*/
public function fetchGroup($statement, array $values = [], $includeGroupColumn = false);
/**
* @param string $statement
* @param array $values
*
* @return int|float|string|false false on empty result
*/
public function fetchValue($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchRow($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchCol($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchPairs($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldCol($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAssoc($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAll($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldPairs($statement, array $values = []);
/**
* Escapes values for "BOOLEAN" and (TINY)INT columns
*
* @param bool|null $value
* @param bool $isNullable
*
* @return string
*/
public function escapeBool($value, $isNullable = false);
/**
* Escapes values for INT columns
*
* @param mixed $value
* @param bool $isNullable
*
* @return string
*/
public function escapeInt($value, $isNullable = false);
/**
* Escapes values for FLOAT, DOUBLE and REAL columns
*
* @param mixed $value
* @param bool $isNullable
*
* @return string
*/
public function escapeDecimal($value, $isNullable = false);
/**
* Escapes values for CHAR, VARCHAR, TEXT and BLOB columns
*
* @param mixed $value
* @param bool $isNullable
*
* @return string
*/
public function escapeString($value, $isNullable = false);
/**
* Escapes and quotes an identifier (column or table name)
*
* @param string $value
*
* @return string
*/
public function escapeIdentifier($value);
}
@@ -0,0 +1,883 @@
<?php
namespace Xentral\Components\Database\Adapter;
use Generator;
use mysqli;
use mysqli_result;
use mysqli_stmt;
use Xentral\Components\Database\DatabaseConfig;
use Xentral\Components\Database\Exception\BindParameterException;
use Xentral\Components\Database\Exception\ConnectionException;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Components\Database\Exception\QueryFailureException;
use Xentral\Components\Database\Exception\TransactionException;
use Xentral\Components\Database\Parser\MysqliArrayValueParser;
use Xentral\Components\Database\Parser\MysqliNamedParameterParser;
use Xentral\Components\Database\Profiler\ProfilerInterface;
final class MysqliAdapter implements AdapterInterface
{
/** @var mysqli|null $connection */
private $connection;
/** @var DatabaseConfig $config */
private $config;
/** @var bool $transactionActive */
private $transactionActive = false;
/** @var int|null $reconnectCounter */
private $reconnectCounter;
/** @var int $reconnectLimit */
private $reconnectMaxCount = 5;
/** @var ProfilerInterface|null $profiler */
private $profiler;
/**
* @param DatabaseConfig $config
* @param ProfilerInterface|null $profiler
*/
public function __construct(DatabaseConfig $config, ProfilerInterface $profiler = null)
{
$this->config = $config;
$this->profiler = $profiler;
}
/**
* @throws ConnectionException
*
* @return void
*/
public function connect()
{
if ($this->connection !== null) {
return;
}
if ($this->reconnectCounter === null) {
$this->reconnectCounter = 0;
} else {
$this->reconnectCounter++;
}
if ($this->reconnectCounter >= $this->reconnectMaxCount) {
throw new ConnectionException(sprintf(
'Too many reconnects. Reconnect count: %d (Max allowed %d)',
$this->reconnectCounter,
$this->reconnectMaxCount
));
}
$this->startProfiler(__FUNCTION__);
$connection = new mysqli(
$this->config->getHostname(),
$this->config->getUsername(),
$this->config->getPassword(),
null,
$this->config->getPort()
);
if ($connection->connect_errno > 0) {
throw new ConnectionException(sprintf(
'Database connection to host "%s" failed. Error code #%s. Error message: %s',
$this->config->getHostname(),
$connection->connect_errno,
$connection->connect_error
));
}
$connection->select_db($this->config->getDatabase());
if ($connection->errno > 0) {
throw new ConnectionException(sprintf(
'Database selection failed for database "%s". Error code #%s. Error message: %s',
$this->config->getDatabase(),
$connection->errno,
$connection->error
));
}
// @see https://www.php.net/manual/de/mysqlinfo.concepts.charset.php
if (!$connection->set_charset($this->config->getCharset())) {
throw new ConnectionException(sprintf(
'Failed to set character set "%s". Error: %s',
$this->config->getCharset(),
$connection->error
));
}
if (!$connection->autocommit(true)) {
throw new ConnectionException(sprintf(
'Failed to activate auto commit. Error: %s',
$connection->error
));
}
$this->finishProfiler(null, [
'dbname' => $this->config->getDatabase(),
'host' => $this->config->getHostname(),
'port' => $this->config->getPort(),
]);
$this->connection = $connection;
foreach ($this->config->getQueries() as $query) {
$this->perform($query);
}
}
/**
* @return void
*/
public function disconnect()
{
if ($this->connection === null) {
return;
}
$this->startProfiler(__FUNCTION__);
$this->connection->close();
$this->connection = null;
$this->finishProfiler();
}
/**
* @return bool
*/
public function inTransaction()
{
return $this->transactionActive;
}
/**
* @throws TransactionException If transaction is already started
*
* @return void
*/
public function beginTransaction()
{
if ($this->inTransaction()) {
throw new TransactionException('Transaction is already started.');
}
$this->connect();
if ($this->connection->begin_transaction() === false) {
throw new TransactionException(sprintf('Transaction start failed: %s', $this->connection->error));
}
$this->transactionActive = true;
}
/**
* @throws TransactionException
*
* @return void
*/
public function commit()
{
if (!$this->inTransaction()) {
throw new TransactionException('Transaction not started.');
}
$this->connection->commit();
$this->connection->autocommit(true);
$this->transactionActive = false;
}
/**
* @throws TransactionException
*
* @return void
*/
public function rollback()
{
if (!$this->inTransaction()) {
throw new TransactionException('Transaction not started.');
}
$this->connection->rollback();
$this->connection->autocommit(true);
$this->transactionActive = false;
}
/**
* @return int
*/
public function lastInsertId()
{
return (int)$this->connection->insert_id;
}
/**
* @param array $values
* @param string $statement
*
* @throws QueryFailureException
*
* @return void
*/
public function perform($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$query = $this->getMysqliStatement($statement, $values);
if (!is_object($query)) {
throw new QueryFailureException(sprintf('Database query failed: %s', $this->connection->error));
}
$query->close();
$this->finishProfiler($statement, $values);
}
/**
* @param string $statement
* @param array $values
*
* @throws QueryFailureException
*
* @return int
*/
public function fetchAffected($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$query = $this->getMysqliStatement($statement, $values);
if (!is_object($query)) {
throw new QueryFailureException(sprintf('Database query failed: %s', $this->connection->error));
}
$affectedRows = (int)$query->affected_rows;
$query->close();
$this->finishProfiler($statement, $values);
return $affectedRows;
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAll($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAssoc($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
while ($row = $result->fetch_assoc()) {
$assocKey = reset($row); // Fetch first array value
$data[$assocKey] = $row;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchRow($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
if ($result->num_rows === 0) {
$result->close();
return [];
}
$data = $result->fetch_assoc();
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return int|float|string|false false on empty result
*/
public function fetchValue($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
if ($result->num_rows === 0) {
$result->close();
return false;
}
$data = $result->fetch_assoc();
$result->close();
$this->finishProfiler($statement, $values);
return reset($data);
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchCol($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
while ($row = $result->fetch_assoc()) {
$firstValue = reset($row); // Fetch first array value
$data[] = $firstValue;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @throws QueryFailureException
*
* @return array
*/
public function fetchPairs($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
if ($result->field_count !== 2) {
throw new QueryFailureException('Field count does not match. fetchPairs() allows only two fields.');
}
$data = [];
while ($row = $result->fetch_assoc()) {
$key = array_shift($row);
$value = array_shift($row);
$data[$key] = $value;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
* @param bool $includeGroupColumn
*
* @return array
*/
public function fetchGroup($statement, array $values = [], $includeGroupColumn = false)
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
$includeGroupColumn = (bool)$includeGroupColumn;
while ($row = $result->fetch_assoc()) {
$group = $includeGroupColumn === true ? reset($row) : array_shift($row); // Fetch first array value
if (!isset($data[$group])) {
$data[$group] = [];
}
$data[$group][] = $row;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAll($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
while ($row = $result->fetch_assoc()) {
yield $row;
}
$result->close();
}
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAssoc($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
while ($row = $result->fetch_assoc()) {
$assocKey = reset($row); // Fetch first array value
yield $assocKey => $row;
}
$result->close();
}
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldCol($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
while ($row = $result->fetch_assoc()) {
$firstValue = reset($row); // Fetch first array value
yield $firstValue;
}
$result->close();
}
/**
* @param string $statement
* @param array $values
*
* @throws QueryFailureException
*
* @return Generator
*/
public function yieldPairs($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
if ($result->field_count !== 2) {
throw new QueryFailureException('Field count does not match. yieldPairs() allows only two fields.');
}
while ($row = $result->fetch_assoc()) {
$key = array_shift($row);
$value = array_shift($row);
yield $key => $value;
}
$result->close();
}
/**
* Escapes values for "BOOLEAN" and (TINY)INT columns
*
* @param bool|null $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeBool($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_bool($value)) {
throw new EscapingException('Can not escape bool. Value is not a bool.');
}
return $value === true ? '1' : '0';
}
/**
* Escapes values for INT columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeInt($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_int($value)) {
throw new EscapingException('Can not escape integer. Value is not an integer.');
}
return (string)(int)$value;
}
/**
* Escapes values for FLOAT, DOUBLE and REAL columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeDecimal($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_numeric($value)) {
throw new EscapingException('Can not escape decimal. Value is not numeric.');
}
return (string)$value;
}
/**
* Escapes values for CHAR, VARCHAR, TEXT and BLOB columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeString($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_string($value)) {
throw new EscapingException('Can not escape string. Value is not a string.');
}
$this->connect();
return "'" . $this->connection->real_escape_string($value) . "'";
}
/**
* Escapes and quotes an identifier (column or table name)
*
* @param string $value
*
* @throws EscapingException
*
* @return string
*/
public function escapeIdentifier($value)
{
if (!is_string($value)) {
throw new EscapingException('Can not escape identifier. Passed value is not a string.');
}
if (empty(trim($value))) {
throw new EscapingException('Can not escape identifier. Passed value is empty.');
}
$parts = explode('.', $value);
if (count($parts) > 2) {
throw new EscapingException('Can not escape identifier. Identifier contains more than one dots.');
}
$partsCleaned = [];
foreach ($parts as $part) {
if (empty(trim($part))) {
throw new EscapingException(
'Can not escape identifier. Parts before and after the dot can not be empty.'
);
}
if (strlen($part) > 64) {
throw new EscapingException(
'Can not escape identifier. Identifier is too long. Only 64 characters are allowed.'
);
}
$partCleaned = preg_replace('/[^A-Za-z0-9_]+/', '', $part);
if (strlen($partCleaned) !== strlen($part)) {
throw new EscapingException(
'Can not escape identifier. Passed value contains invalid characters. ' .
'Valid characters: A-Z, a-z, 0-9, Underscore'
);
}
$partsCleaned[] = '`' . $partCleaned . '`';
}
return implode('.', $partsCleaned);
}
/**
* @return void
*/
public function __clone()
{
$this->connection = null;
$this->transactionActive = false;
$this->config = clone $this->config;
}
/**
* @return void
*/
public function __destruct()
{
$this->disconnect();
}
/**
* @return void
*/
public function __wakeup()
{
}
/**
* @return array
*/
public function __sleep()
{
return [];
}
/**
* @param string $statement
* @param array $values
*
* @return mysqli_stmt
*/
private function getMysqliStatement($statement, array $values = [])
{
list($statement, $values) = $this->replaceArrayValues($statement, $values);
list($rebuildStatement, $bindValues, $parameterNames) = $this->replaceNamedParameters($statement, $values);
$query = $this->connection->prepare($rebuildStatement);
if ($query === false && $this->connection->errno === 2006) {
// Code 2006 = MySQL server has gone away
// Falls Verbindung in einen Timeout gelaufen ist
// => Verbindung wiederherstellen und Prepare erneut probieren
$this->disconnect();
$this->connect();
$query = $this->connection->prepare($rebuildStatement);
}
if ($query === false || !is_object($query)) {
throw new QueryFailureException(
sprintf(
'Database prepare failed. Error code #%s. Error message: %s',
$this->connection->errno,
$this->connection->error
),
(int)$this->connection->errno
);
}
$this->bindParametersToMysqliStatement($query, $bindValues, $parameterNames);
$query->execute();
if ($query->errno > 0) {
throw new QueryFailureException(
sprintf('Database query failed. Error code #%s. Error message: %s', $query->errno, $query->error),
(int)$query->errno
);
}
return $query;
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
private function replaceArrayValues($statement, array $values = [])
{
$parser = new MysqliArrayValueParser();
$result = $parser->rebuild($statement, $values);
return [$result['statement'], $result['values']];
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
private function replaceNamedParameters($statement, array $values = [])
{
$parser = new MysqliNamedParameterParser();
$result = $parser->rebuild($statement, $values);
return [$result['statement'], $result['values'], $result['params']];
}
/**
* @param mysqli_stmt $statement
* @param array $bindValues Values for binding
* @param array $parameterNames Original parameter names (for debugging only)
*
* @return void
*/
private function bindParametersToMysqliStatement($statement, $bindValues, $parameterNames)
{
if (empty($bindValues)) {
return;
}
$bindTypes = '';
foreach ($bindValues as $index => &$bindValue) {
if (is_bool($bindValue)) {
$bindValue = (int)$bindValue;
$bindTypes .= 'i'; // integer
continue;
}
if (is_float($bindValue)) {
$bindTypes .= 'd'; // double
continue;
}
if (is_array($bindValue)) {
throw new BindParameterException(sprintf(
'Can not bind parameter of type "array" to placeholder "%s".',
$parameterNames[$index]
));
}
if (is_object($bindValue)) {
throw new BindParameterException(sprintf(
'Can not bind parameter of type "object" to placeholder "%s".',
$parameterNames[$index]
));
}
$bindTypes .= 's'; // string
}
unset($bindValue);
$statement->bind_param($bindTypes, ...$bindValues);
}
/**
* @param $statement
* @param array $values
*
* @return mysqli_result
*/
private function getMysqliResult($statement, array $values = [])
{
$query = $this->getMysqliStatement($statement, $values);
$result = $query->get_result();
$query->close();
if ($result === false) {
throw new QueryFailureException(
sprintf('Database query failed. Error code #%s. Error message: %s', $query->errno, $query->error),
(int)$query->errno
);
}
return $result;
}
/**
* @param string $methodName
*
* @return void
*/
private function startProfiler($methodName)
{
if ($this->profiler === null) {
return;
}
$this->profiler->start(__CLASS__, $methodName);
}
/**
* @param string|null $statement
* @param array $values
*
* @return void
*/
private function finishProfiler($statement = null, array $values = [])
{
if ($this->profiler === null) {
return;
}
$this->profiler->finish($statement, $values);
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace Xentral\Components\Database;
use Xentral\Components\Database\Adapter\MysqliAdapter;
use Xentral\Components\Database\Exception\ConfigException;
use Xentral\Components\Database\Profiler\Profiler;
use Xentral\Components\Database\SqlQuery\QueryFactory;
use Xentral\Components\Logger\Context\ContextHelper;
use Xentral\Components\Logger\MemoryLogger;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Core\DependencyInjection\ServiceContainer;
use Xentral\Core\LegacyConfig\ConfigLoader;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'Database' => 'onInitDatabase',
'DatabaseProfiler' => 'onGetDatabaseProfiler',
'MysqliAdapter' => 'onInitMysqliAdapter',
'QueryFactory' => 'onInitQueryFactory',
];
}
/**
* @param ServiceContainer $container
*
* @return Database
*/
public static function onInitDatabase(ServiceContainer $container)
{
return new Database($container->get('MysqliAdapter'), $container->get('QueryFactory'));
}
/**
* @return QueryFactory
*/
public static function onInitQueryFactory()
{
return new QueryFactory('mysql');
}
/**
* @param ServiceContainer $container
*
* @return Profiler
*/
public static function onGetDatabaseProfiler(ServiceContainer $container)
{
$request = $container->get('Request');
return new Profiler(new MemoryLogger(new ContextHelper($request)));
}
/**
* @param ContainerInterface $container
*
* @return MysqliAdapter
*/
public static function onInitMysqliAdapter(ContainerInterface $container)
{
$conf = ConfigLoader::load();
$dbHost = property_exists($conf, 'WFdbhost') ? $conf->WFdbhost : 'localhost';
$dbPort = property_exists($conf, 'WFdbport') ? $conf->WFdbport : 3306;
$dbName = property_exists($conf, 'WFdbname') ? $conf->WFdbname : null;
$dbUser = property_exists($conf, 'WFdbuser') ? $conf->WFdbuser : null;
$dbPass = property_exists($conf, 'WFdbpass') ? $conf->WFdbpass : null;
if (empty($dbName)) {
throw new ConfigException('Could not connect to database. Database name is missing or empty.');
}
if (empty($dbUser)) {
throw new ConfigException('Could not connect to database. Database user is missing or empty.');
}
if (empty($dbPass)) {
throw new ConfigException('Could not connect to database. Database password is missing or empty.');
}
$startupQueries = [
"SET NAMES 'utf8', " .
"CHARACTER SET 'utf8', " .
"lc_time_names = 'de_DE', " .
"SESSION sql_mode = '', " .
"SESSION sql_big_selects = 1;",
];
$config = new DatabaseConfig($dbHost, $dbUser, $dbPass, $dbName, 'utf8', $dbPort, $startupQueries);
// Profiler aktivieren
// Kann mit $container->get('DatabaseProfiler')->getContexts() abgefragt werden
$profiler = $container->get('DatabaseProfiler');
if (defined('DEVELOPMENT_MODE') && DEVELOPMENT_MODE === true) {
$profiler->setActive(true);
}
return new MysqliAdapter($config, $profiler);
}
}
+356
View File
@@ -0,0 +1,356 @@
<?php /** @noinspection PhpInconsistentReturnPointsInspection */
namespace Xentral\Components\Database;
use Generator;
use Xentral\Components\Database\Adapter\AdapterInterface;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Components\Database\Exception\TransactionException;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\QueryFactory;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
final class Database
{
/** @var AdapterInterface $adapter */
private $adapter;
/** @var QueryFactory $queryFactory */
private $queryFactory;
/**
* @param AdapterInterface $adapter
* @param QueryFactory $queryFactory
*/
public function __construct(AdapterInterface $adapter, QueryFactory $queryFactory)
{
$this->adapter = $adapter;
$this->queryFactory = $queryFactory;
}
/**
* Aura.SqlQuery
*/
/**
* @return SelectQuery
*/
public function select()
{
return $this->queryFactory->newSelect();
}
/**
* @return InsertQuery
*/
public function insert()
{
return $this->queryFactory->newInsert();
}
/**
* @return UpdateQuery
*/
public function update()
{
return $this->queryFactory->newUpdate();
}
/**
* @return DeleteQuery
*/
public function delete()
{
return $this->queryFactory->newDelete();
}
/**
* ENDE: Aura.SqlQuery
*/
/**
* Close database connection
*
* @return void
*/
public function close()
{
$this->adapter->disconnect();
}
/**
* Executes simple queries without named parameters
*
* Use self::perform() for queries with named parameters.
*
* @param string $query
*
* @return void
*/
public function exec($query)
{
$this->adapter->perform($query, []);
}
/**
* @return int
*/
public function lastInsertId()
{
return (int)$this->adapter->lastInsertId();
}
/**
* @throws TransactionException If transaction is already started
*
* @return void
*/
public function beginTransaction()
{
$this->adapter->beginTransaction();
}
/**
* @return void
*/
public function commit()
{
$this->adapter->commit();
}
/**
* @return void
*/
public function rollBack()
{
$this->adapter->rollBack();
}
/**
* @return bool
*/
public function inTransaction()
{
return $this->adapter->inTransaction();
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchAll($query, array $values = [])
{
return $this->adapter->fetchAll($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchAssoc($query, array $values = [])
{
return $this->adapter->fetchAssoc($query, $values);
}
/**
* @param string $query
* @param array $values
* @param bool $includeGroupColumn
*
* @return array Empty array on empty result
*/
public function fetchGroup($query, array $values = [], $includeGroupColumn = false)
{
return $this->adapter->fetchGroup($query, $values, (bool)$includeGroupColumn);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchRow($query, array $values = [])
{
return $this->adapter->fetchRow($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchPairs($query, array $values = [])
{
return $this->adapter->fetchPairs($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return int|float|string|false false on empty result
*/
public function fetchValue($query, array $values = [])
{
return $this->adapter->fetchValue($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return int
*/
public function fetchAffected($query, array $values = [])
{
return $this->adapter->fetchAffected($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchCol($query, array $values = [])
{
return $this->adapter->fetchCol($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldAll($query, array $values = [])
{
return $this->adapter->yieldAll($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldAssoc($query, array $values = [])
{
return $this->adapter->yieldAssoc($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldPairs($query, array $values = [])
{
return $this->adapter->yieldPairs($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldCol($query, array $values = [])
{
return $this->adapter->yieldCol($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return void
*/
public function perform($query, array $values = [])
{
$this->adapter->perform($query, $values);
}
/**
* Escapes values for "BOOLEAN" and (TINY)INT columns
*
* @param bool|null $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeBool($value, $isNullable = false)
{
return $this->adapter->escapeBool($value, $isNullable);
}
/**
* Escapes values for INT columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeInt($value, $isNullable = false)
{
return $this->adapter->escapeInt($value, $isNullable);
}
/**
* Escapes values for FLOAT, DOUBLE and REAL columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeDecimal($value, $isNullable = false)
{
return $this->adapter->escapeDecimal($value, $isNullable);
}
/**
* Escapes values for CHAR, VARCHAR, TEXT and BLOB columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeString($value, $isNullable = false)
{
return $this->adapter->escapeString($value, $isNullable);
}
/**
* Escapes and quotes an identifier (column or table name)
*
* @param string $value
*
* @throws EscapingException
*
* @return string
*/
public function escapeIdentifier($value)
{
return $this->adapter->escapeIdentifier($value);
}
}
@@ -0,0 +1,146 @@
<?php
namespace Xentral\Components\Database;
final class DatabaseConfig
{
/** @var string $hostname */
private $hostname;
/** @var string $username */
private $username;
/** @var string $password */
private $password;
/** @var string $database */
private $database;
/** @var string $charset */
private $charset;
/** @var int $port */
private $port;
/** @var array $queries */
private $queries;
/**
* @param string $hostname
* @param string $username
* @param string $password
* @param string $database
* @param string|null $charset
* @param int|null $port
* @param array $queries
*/
public function __construct(
$hostname,
$username,
$password,
$database,
$charset = null,
$port = null,
array $queries = []
) {
$this->hostname = (string)$hostname;
$this->username = (string)$username;
$this->password = (string)$password;
$this->database = (string)$database;
$this->charset = $charset !== null ? (string)$charset : 'utf8';
$this->port = $port !== null ? (int)$port : 3306;
$this->queries = $queries;
}
/**
* @param array $config
*
* @return DatabaseConfig
*/
public static function fromArray(array $config)
{
return new DatabaseConfig(
$config['hostname'],
$config['username'],
$config['password'],
$config['database'],
isset($config['charset']) ? $config['charset'] : null,
isset($config['port']) ? $config['port'] : null,
isset($config['queries']) ? $config['queries'] : []
);
}
/**
* @return string
*/
public function getHostname()
{
return $this->hostname;
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return string
*/
public function getDatabase()
{
return $this->database;
}
/**
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* @return int
*/
public function getPort()
{
return $this->port;
}
/**
* @return array
*/
public function getQueries()
{
return $this->queries;
}
/**
* @return array
*/
public function __debugInfo()
{
return [
'args' => [
'hostname' => $this->hostname,
'username' => '****',
'password' => '****',
'database' => $this->database,
'charset' => $this->charset,
'port' => $this->port,
'queries' => $this->queries,
],
];
}
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class BindParameterException extends \RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class ConfigException extends \InvalidArgumentException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class ConnectionException extends \RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
namespace Xentral\Components\Database\Exception;
use RuntimeException;
/**
* @deprecated Will be removed in 19.4
*/
class DatabaseException extends RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface DatabaseExceptionInterface extends ComponentExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class EscapingException extends \RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class MissingParameterException extends \RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class NotConnectedException extends \LogicException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class QueryFailureException extends \RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class TransactionException extends \RuntimeException implements DatabaseExceptionInterface
{
}
@@ -0,0 +1,66 @@
<?php
namespace Xentral\Components\Database\Parser;
/**
* @example
* self::rebuild('SELECT * FROM foo WHERE id IN (:ids)', ['ids' => [1, 2, 3]])
* Erzeugt:
* [
* 'statement' => 'SELECT * FROM foo WHERE id IN (:ids_expl_0_, :ids_expl_1_, :ids_expl_2_)',
* 'values' => [
* '_ids_expl_0_' => 1,
* '_ids_expl_1_' => 2,
* '_ids_expl_2_' => 3,
* ]
* ]
*/
final class MysqliArrayValueParser implements ParserInterface
{
/**
* @param string $statement
* @param array $values
*
* @return array
* - Array key 'statement' contains the rebuild statement
* - Array key 'values' contains the rebuild bind parameters
*/
public function rebuild($statement, array $values = [])
{
return $this->replaceArrayValues($statement, $values);
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
private function replaceArrayValues($statement, array $values = [])
{
foreach ($values as $paramName => $paramValue) {
if (is_array($paramValue)) {
$counter = 0;
$additionalParams = [];
foreach ($paramValue as $arrayValue) {
$additionalParamName = '_' . $paramName . '_expl_' . $counter . '_';
$additionalParams[] = ':' . $additionalParamName;
$values[$additionalParamName] = $arrayValue;
$counter++;
}
// Replace original named parameter by exploded parameters in statement
$replaceString = implode(', ', $additionalParams);
$statement = str_replace(':' . $paramName, $replaceString, $statement);
// Remove original parameter value
unset($values[$paramName]);
}
}
return [
'statement' => $statement,
'values' => $values,
];
}
}
@@ -0,0 +1,79 @@
<?php
namespace Xentral\Components\Database\Parser;
use Xentral\Components\Database\Exception\MissingParameterException;
/**
* Responsibility of this class is to make sql statement and bind values compatible with mysqli
*
* (Mysqli does not support named parameters)
*
* It does this by:
* - Replacing named parameters (:param) by ?-Placeholder (in statement)
* - Rearranging bind values in order of appearance of named parameters
*/
final class MysqliNamedParameterParser implements ParserInterface
{
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function rebuild($statement, array $values = [])
{
return $this->replaceNamedParameters($statement, $values);
}
/**
* @param string $statement
* @param array $values
*
* @throws MissingParameterException
*
* @return array
*/
private function replaceNamedParameters($statement, array $values = [])
{
$result = [
'statement' => $statement,
'values' => [],
'params' => [],
];
if (empty($values)) {
return $result;
}
// Split statement on named parameters
$parts = preg_split('/(:[a-zA-Z0-9_]+)/um', $statement, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach ($parts as &$part) {
if (strpos($part, ':') !== 0) {
continue; // SQL part does not contain named parameter
}
$parameterName = substr_replace($part, '', 0, 1);
if (!array_key_exists($parameterName, $values)) {
throw new MissingParameterException(sprintf(
'Parameter "%s" is missing from the bound values',
$parameterName
));
}
// Push values in same order of parameters for binding
$result['values'][] = $values[$parameterName];
$result['params'][] = $parameterName; // For debugging only
// Replace named parameter by ?-Placeholder
$part = '?';
}
unset($part);
// Rebuild statement from (changed) parts
$result['statement'] = implode('', $parts);
return $result;
}
}
@@ -0,0 +1,16 @@
<?php
namespace Xentral\Components\Database\Parser;
interface ParserInterface
{
/**
* @param string $statement
* @param array $values
*
* @return array
* - Array key 'statement' contains the corrected statement
* - Array key 'values' contains the corrected bind values
*/
public function rebuild($statement, array $values = []);
}
@@ -0,0 +1,135 @@
<?php
namespace Xentral\Components\Database\Profiler;
use Exception;
use Xentral\Components\Logger\LoggerInterface;
final class Profiler implements ProfilerInterface
{
/** @var LoggerInterface $logger */
private $logger;
/** @var bool $active */
private $active = false;
/** @var string $logLevel */
private $logLevel = 'debug';
/** @var string $logFormat */
private $logFormat = "{method} ({duration}): {statement} \n{backtrace}";
/** @var array $context */
private $context = [];
/** @var array $contexts */
private $contexts = [];
/**
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* @param string $className
* @param string $methodName
*
* @return void
*/
public function start($className, $methodName)
{
if (!$this->active) {
return;
}
$this->context = [
'class' => $className,
'method' => $methodName,
'start' => microtime(true),
];
}
/**
* @param string|null $statement
* @param array $values
*
* @return void
*/
public function finish($statement = null, array $values = [])
{
if (!$this->active) {
return;
}
$finish = microtime(true);
$exception = new Exception();
$this->context['finish'] = $finish;
$this->context['duration_real'] = $finish - $this->context['start'];
$this->context['duration'] = sprintf('%.6f', $this->context['duration_real']) . ' seconds';
$this->context['statement'] = $statement;
$this->context['bindings'] = $values;
$this->context['backtrace'] = $exception->getTraceAsString();
$this->logger->log($this->logLevel, $this->logFormat, $this->context);
$this->contexts[] = $this->context;
$this->context = [];
}
/**
* @return bool
*/
public function isActive()
{
return $this->active;
}
/**
* @param bool $active
*
* @return void
*/
public function setActive($active)
{
$this->active = (bool)$active;
}
/**
* /**
* @return string
*/
public function getLogLevel()
{
return $this->logLevel;
}
/**
* @param string $logLevel
*
* @return void
*/
public function setLogLevel($logLevel)
{
$this->logLevel = (string)$logLevel;
}
/**
* @return LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* @return array
*/
public function getContexts()
{
return $this->contexts;
}
}
@@ -0,0 +1,54 @@
<?php
namespace Xentral\Components\Database\Profiler;
use Xentral\Components\Logger\LoggerInterface;
interface ProfilerInterface
{
/**
* @param string $className
* @param string $methodName
*
* @return void
*/
public function start($className, $methodName);
/**
* @param string|null $statement
* @param array $values
*
* @return void
*/
public function finish($statement = null, array $values = []);
/**
* @return bool
*/
public function isActive();
/**
* @param bool $active
*
* @return void
*/
public function setActive($active);
/**
/**
* @return string
*/
public function getLogLevel();
/**
* @param string $logLevel
*
* @return void
*/
public function setLogLevel($logLevel);
/**
* @return LoggerInterface
*/
public function getLogger();
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Delete;
final class DeleteQuery extends Delete
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Insert;
final class InsertQuery extends Insert
{
}
@@ -0,0 +1,59 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\AbstractQuery;
use Aura\SqlQuery\QueryFactory as AuraQueryFactory;
final class QueryFactory extends AuraQueryFactory
{
/**
* @return SelectQuery
*/
public function newSelect()
{
return $this->newInstance('Select');
}
/**
* @return InsertQuery
*/
public function newInsert()
{
$insert = $this->newInstance('Insert');
$insert->setLastInsertIdNames($this->last_insert_id_names);
return $insert;
}
/**
* @return UpdateQuery
*/
public function newUpdate()
{
return $this->newInstance('Update');
}
/**
* @return DeleteQuery
*/
public function newDelete()
{
return $this->newInstance('Delete');
}
/**
* @param string $query The query object type.
*
* @return AbstractQuery
*/
protected function newInstance($query)
{
$class = "Xentral\\Components\\Database\\SqlQuery\\{$query}Query";
return new $class(
$this->getQuoter(),
$this->newSeqBindPrefix()
);
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Select;
use Closure;
final class SelectQuery extends Select
{
/**
* @return bool
*/
public function hasOrderBy()
{
return !empty($this->order_by);
}
/**
* @param string $andor
* @param array|callable $args
*
* @return Select
*/
protected function addWhere($andor, $args)
{
if ($args[0] instanceof Closure) {
$this->addClauseCondClosure('where', $andor, $args[0]);
return $this;
}
return parent::addWhere($andor, $args);
}
/**
* Aura.SqlQuery 2.x unterstützt keine Klammersetzung in WHERE-Bedingungen
*
* @see https://github.com/auraphp/Aura.SqlQuery/issues/97
*
* Feature aus Version 3 importiert: https://github.com/auraphp/Aura.SqlQuery/pull/136/files
*
* @copyright Paul M. Jones
* @license MIT
*
* @param string $clause
* @param string $andor
* @param callable $closure
*/
protected function addClauseCondClosure($clause, $andor, $closure)
{
// retain the prior set of conditions, and temporarily reset the clause
// for the closure to work with (otherwise there will be an extraneous
// opening AND/OR keyword)
$set = $this->$clause;
$this->$clause = [];
// invoke the closure, which will re-populate the $this->$clause
$closure($this);
// are there new clause elements?
if (!$this->$clause) {
// no: restore the old ones, and done
$this->$clause = $set;
return;
}
// append an opening parenthesis to the prior set of conditions,
// with AND/OR as needed ...
if ($set) {
$set[] = "{$andor} (";
} else {
$set[] = "(";
}
// append the new conditions to the set, with indenting
foreach ($this->$clause as $cond) {
$set[] = " {$cond}";
}
$set[] = ")";
// ... then put the full set of conditions back into $this->$clause
$this->$clause = $set;
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Update;
final class UpdateQuery extends Update
{
}
@@ -0,0 +1,77 @@
# Data Manipulation
## `perform()`
Für alle SQL Anweisung außer `SELECT`. Die Methode gibt nichts zurück. Sollte die Ausführung
fehlschlagen, so wird eine Exception geworfen.
```php
$sql = 'UPDATE foo SET bar = :bar WHERE id = :id';
$values = [
'bar' => 'baz',
'id' => 123,
];
$db->perform($sql, $values);
```
## `fetchAffected()`
Für `INSERT`, `REPLACE`, `UPDATE` und `DELETE` Anweisungen. Gibt die Anzahl der betroffenen Datensätze zurück.
```php
$sql = 'UPDATE foo SET bar = :bar WHERE 1';
$values = [
'bar' => 'baz',
];
echo $db->fetchAffected($sql, $values);
```
###### Ausgabe
```
42
```
## `lastInsertId()`
Gibt den zuletzt erzeugten Auto-Increment-Wert zurück.
```php
$sql = 'INSERT INTO foo (id, bar) VALUES (NULL, :bar)';
$values = [
'bar' => 'baz',
];
$db->perform($sql, $values);
// $db->fetchAffected($sql, $values); // Alternative
echo $db->lastInsertId();
```
###### Ausgabe
```
123
```
## Multiple-Row-Insert
```php
$sql = 'INSERT INTO foo (id, bar) VALUES (NULL, :bar1, :baz1), (NULL, :bar2, :baz2), (NULL, :bar3, :baz3)';
$values = [
'bar1' => 'bar',
'baz1' => 'baz',
'bar2' => 'barbar',
'baz2' => 'bazbaz',
'bar3' => 'barbarbar',
'baz3' => 'bazbazbaz',
];
$db->perform($sql, $values);
// $db->fetchAffected($sql, $values); // Alternative; Rückgabe wäre `3`
```
@@ -0,0 +1,158 @@
# Datensätze abrufen
## `fetchAll()`
```php
$data = $db->fetchAll('SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.id > 5');
```
```
array (
0 =>
array (
'id' => 6,
'typ' => 'produkt',
'name_de' => 'LED Anzeige RLED 24-8',
),
1 =>
array (
'id' => 7,
'typ' => 'produkt',
'name_de' => 'Schalter S3 24V 5A',
),
...
)
```
## `fetchAssoc()`
Wie `fetchAll()` allerdings wird das Ergebnis der ersten Spalte als Index verwendet.
```php
$data = $db->fetchAssoc('SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.id > 5');
```
```
array (
6 =>
array (
'id' => 6,
'typ' => 'produkt',
'name_de' => 'LED Anzeige RLED 24-8',
),
7 =>
array (
'id' => 7,
'typ' => 'produkt',
'name_de' => 'Schalter S3 24V 5A',
),
...
)
```
## `fetchRow()`
Liefert die Ergebnisse der ersten Zeile als assoziatives Array. Die Spaltennamen werden als Index verwendet.
```php
$data = $db->fetchRow('SELECT a.id, a.name_de, a.name_en, a.logdatei FROM artikel AS a WHERE a.id > 5');
```
```
array (
'id' => 6,
'name_de' => 'LED Anzeige RLED 24-8',
'name_en' => '',
'logdatei' => '2015-10-26 17:26:27',
)
```
## `fetchValue()`
Liefert nur das Ergebnis der ersten Zeile und Spalte zurück.
```php
$data = $db->fetchValue('SELECT a.name_de, name_en, a.logdatei FROM artikel AS a WHERE a.id > 5');
```
```
'LED Anzeige RLED 24-8'
```
## `fetchCol()`
Liefert nur die Ergebnisse der ersten Spalte zurück.
```php
$data = $db->fetchCol('SELECT a.name_de, a.name_en, a.logdatei FROM artikel AS a WHERE a.id > 5');
```
```
array (
0 => 'LED Anzeige RLED 24-8',
1 => 'Schalter S3 24V 5A',
2 => 'Gehäuse GHK5 20x30x10',
...
)
```
## `fetchPairs()`
Gibt ein (eindimentionales) assoziatives Array zurück. Der Wert der ersten Spalte wird als Index verwendet und die zweite Spalte als Wert.
**Erwartet werden genau zwei Spalten, andernfalls wird eine Exception geworfen.**
```php
$data = $db->fetchPairs('SELECT a.nummer, a.name_de FROM artikel AS a WHERE a.id > 5');
```
```
array (
700006 => 'LED Anzeige RLED 24-8',
700007 => 'Schalter S3 24V 5A',
700008 => 'Gehäuse GHK5 20x30x10',
...
)
```
## `fetchGroup()`
Verhält sich wie `fetchAssoc()`; mit der Ausnahme dass die Werte der ersten Spalte gruppiert werden.
```php
$data = $db->fetchGroup(
'SELECT a.typ, a.nummer, a.name_de, a.logdatei FROM artikel AS a WHERE a.id > 5'
);
```
```
array (
'produkt' =>
array (
0 =>
array (
'typ' => 'produkt',
'nummer' => '700006',
'name_de' => 'LED Anzeige RLED 24-8',
'logdatei' => '2015-10-26 17:26:27',
),
1 =>
array (
'typ' => 'produkt',
'nummer' => '700007',
'name_de' => 'Schalter S3 24V 5A',
'logdatei' => '2015-10-26 17:26:59',
),
2 =>
array (
'typ' => 'produkt',
'nummer' => '700008',
'name_de' => 'Gehäuse GHK5 20x30x10',
'logdatei' => '2018-05-23 05:18:52',
),
),
'gebuehr' =>
array (
0 =>
array (
'typ' => 'gebuehr',
'nummer' => '100001',
'name_de' => 'Versandkosten',
'logdatei' => '2018-06-17 10:09:36',
),
),
)
```
+51
View File
@@ -0,0 +1,51 @@
# Database-Komponente
###### Verwendete Libraries
* **Aura.SqlQuery**:
* Composer: `aura/sqlquery:2.7.*`
* Packagist: https://packagist.org/packages/aura/sqlquery
* GitHub: https://github.com/auraphp/Aura.SqlQuery
* Docs: https://github.com/auraphp/Aura.SqlQuery/tree/2.x
* **~~Aura.Sql~~** Wird in 19.4 entfernt
* Composer: `aura/sql:3.*`
* Packagist: https://packagist.org/packages/aura/sql
* GitHub: https://github.com/auraphp/Aura.Sql
* Docs: https://github.com/auraphp/Aura.Sql/blob/3.x/docs/index.md
##### Database-Komponente aus Container holen
```php
$db = $container->get('Database');
```
Im alten Bereich:
```php
$db = $this->app->Container->get('Database');
```
## Themen
* [Datensätze abrufen (fetch)](fetch_results.md)
* [Datensätze abrufen mit Generatoren (yield)](yield_results.md)
* [Datensätze ändern](data_manipulation.md)
* [Transaktionen](transactions.md)
* [Named Parameter / Prepared Statements](named_parameter.md)
* [SQL Query Builder](query_builder.md)
## Exceptions
Die Database-Komponente verwendet intern `mysqli`. Im Unterschied zu `mysqli` werden in Fehlerfällen aber
Exceptions geworfen; z.B.:
* Wenn die Verbindung zur Datenbank fehlschlägt => `ConnectionException`
* Wenn ein SQL-Statement fehlerhaft ist oder aus anderen Gründen nicht erfolgreich ausgeführt
werden kann => `QueryFailureException`
* Wenn `Named Parameter` fehlen => `MissingParameterException`
##### ExceptionInterface
Alle Exceptions die von der Database-Komponente geworfen werden implementieren das
`\Xentral\Components\Database\Exception\DatabaseExceptionInterface` Interface.
@@ -0,0 +1,44 @@
# Named Parameter
`perform()`, `fetch*()` und `yield*()`-Methoden nehmen als zweiten Parameter ein assoziatives Array entgegen.
Mit diesem Array werden lassen sich Werte als *Named Parameter* 'binden'.
```php
$sql =
'SELECT a.id, a.nummer, a.name_de
FROM artikel AS a
WHERE a.typ IN (:types)
AND a.nummer LIKE :nummers
LIMIT :start, :length';
$values = [
'types' => ['produkt', 'gebuehr'],
'nummers' => '7000%',
'start' => 0,
'length' => 3,
];
$data = $db->fetchAll($sql, $values);
```
```
array (
0 =>
array (
'id' => 1,
'nummer' => '700001',
'name_de' => 'Schraube M10x20',
),
1 =>
array (
'id' => 2,
'nummer' => '700002',
'name_de' => 'Sechskant-Mutter M10',
),
2 =>
array (
'id' => 3,
'nummer' => '700003',
'name_de' => 'Schalthebel 20x10',
),
)
```
@@ -0,0 +1,13 @@
# Profiler
```php
<?php
/** @var \Xentral\Components\Database\Profiler\Profiler $profiler */
$profiler = $container->get('DatabaseProfiler');
$profiler->setActive(true);
$database->fetchAll($sql);
var_dump($profiler->getContexts());
```
@@ -0,0 +1,97 @@
# SQL Query Builder
###### Verwendete Libraries
* **Aura.SqlQuery**:
* Composer: `aura/sqlquery:2.7.*`
* Packagist: https://packagist.org/packages/aura/sqlquery
* GitHub: https://github.com/auraphp/Aura.SqlQuery
* Docs: https://github.com/auraphp/Aura.SqlQuery/tree/2.x
## Query Builder erzeugen
```php
$select = $db->select();
$update = $db->update();
$insert = $db->insert();
$delete = $db->delete();
```
## SELECT-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#select
###### Beispiel mit Named-Placeholder
```php
$select = $db->select();
$select
->cols(['u.id', 'u.description'])
->from('user AS u')
->where('u.id = :user_id')
->bindValue('user_id', 1);
$result = $db->fetchAll(
$select->getStatement(),
$select->getBindValues()
);
var_export($result);
// array(
// 0 => array(
// 'id' => 1,
// 'description' => 'Administrator',
// ),
// )
```
###### Alternative mit ?-Placeholder
```php
$select = $db->select();
$select
->cols(['u.id', 'u.description'])
->from('user AS u')
->where('u.id = ?', 1);
```
###### Beispiel mit Verschachtelung im WHERE
```php
$select = $db->select();
$select
->cols(['u.id', 'u.description'])
->from('user AS u')
->where('u.activ = ?', 1)
->where(function (SelectQuery $query) {
$query->where('u.type = ?', 'admin')
->orWhere('u.type = ?', 'standard');
});
echo $select->getStatement();
```
```sql
SELECT
`u`.`id`,
`u`.`description`
FROM
`user` AS `u`
WHERE
`u`.`activ` = :_1_1_
AND (
`u`.`type` = :_1_2_
OR `u`.`type` = :_1_3_
)
```
## INSERT-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#insert
## UPDATE-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#update
## DELETE-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#delete
@@ -0,0 +1,25 @@
# Transaktionnen
## Transaktion starten
```php
$db->beginTransaction();
```
## Transaktion übernehmen / Commit
```php
$db->commit();
```
## Transaktion zurücknehmen / Rollback
```php
$db->rollback();
```
## Prüfen ob Transaktion gestartet ist
```php
$db->inTransaction();
```
@@ -0,0 +1,107 @@
# Datensätze abrufen mit Generatoren
Um den Arbeitsspeicherverbrauch gering zu halten bieten sich zum Iterieren von großen Datenmengen
Generatoren an: https://www.php.net/manual/de/language.generators.overview.php
Die Database-Komponente stellt für diesen Zweck `yield`-Methoden zur Verfügung.
## `yieldAll()`
Wie `fetchAll()`; jede Zeile ist ein assoziatives Array.
```php
$statement = 'SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldAll($statement, $bindValues) as $row) {
var_dump($row);
}
```
```
array (size=3)
'id' => int 2
'typ' => string 'produkt' (length=7)
'name_de' => string 'Sechskant-Mutter M10' (length=20)
array (size=3)
'id' => int 3
'typ' => string 'produkt' (length=7)
'name_de' => string 'Schalthebel 20x10' (length=17)
```
## `yieldAssoc()`
Wie `fetchAssoc()`; jede Zeile ist ein assoziatives Array; der Key beinhaltet den Wert der ersten Spalte
```php
$statement = 'SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldAssoc($statement, $bindValues) as $key => $row) {
var_dump($key);
var_dump($row);
}
```
```
int 2
array (size=3)
'id' => int 2
'typ' => string 'produkt' (length=7)
'name_de' => string 'Sechskant-Mutter M10' (length=20)
int 3
array (size=3)
'id' => int 3
'typ' => string 'produkt' (length=7)
'name_de' => string 'Schalthebel 20x10' (length=17)
```
## `yieldPairs()`
Wie `fetchPairs()`; jede Zeile besteht aus Key-Value-Paaren; der Key beinhaltet den Inhalt der ersten Spalte;
der Wert den Inhalt der zweiten Spalte.
**Erwartet werden genau zwei Spalten, andernfalls wird eine Exception geworfen.**
```php
$statement = 'SELECT a.id, a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldPairs($statement, $bindValues) as $key => $value) {
var_dump($key);
var_dump($value);
}
```
```
int 2
string 'Sechskant-Mutter M10' (length=20)
int 3
string 'Schalthebel 20x10' (length=17)
```
## `yieldCol()`
Wie `fetchCol()`; jede Zeile beinhaltet nur den Wert der ersten Spalte.
```php
$statement = 'SELECT a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldCol($statement, $bindValues) as $key => $value) {
var_dump($key);
var_dump($value);
}
```
```
int 0
string 'Sechskant-Mutter M10' (length=20)
int 1
string 'Schalthebel 20x10' (length=17)
```