Compare commits

...

17 Commits

Author SHA1 Message Date
Abel Hoogeveen 5abb6b63b6 Moved platform dependencies into suggestions. 2023-02-14 15:01:23 +01:00
Abel Hoogeveen 63cd0ec6a8 Fixed critical bug where PDOEngine could not properly dissolve at the end of execution. 2023-01-26 15:07:29 +01:00
Abel Hoogeveen 4b200dbd63 Upgraded database backend for PHP 8.1.
Changes implemented:
- Proper type definitions, for both return types and properties.
- Streamlined if statements, removing unnecessary statements.
- Upgraded Mongo backend to be compatible with driver 1.15.
2022-12-22 10:46:13 +01:00
Abel Hoogeveen 029511f0cd
Updated to latest FuzeWorks libraries. 2021-11-29 22:58:24 +01:00
Abel Hoogeveen 1938ebad55
Upgraded dependencies and tested compatibility. 2021-01-25 12:11:19 +01:00
Abel Hoogeveen bb8eb0300a Implemented changes requested by Wettennet 2020-07-12 11:58:07 +02:00
Abel Hoogeveen 6c53eb8cd4
Implemented changes requested by Wettennet.
PDOStatementWrapper now directly binds parameters. This allows for values such as 'NULL' to be properly utilized.
2020-06-13 12:07:06 +02:00
Abel Hoogeveen 72b404ecc9
Release 1.2.0 2019-09-21 20:13:30 +02:00
Abel Hoogeveen 3a9e63fcdf
Merge remote-tracking branch 'upstream/master' 2019-09-18 00:18:31 +02:00
Abel Hoogeveen a83947d0f4
Release 1.2.0-RC6 2019-09-18 00:16:37 +02:00
Abel Hoogeveen 8260038310 Merge branch 'holiday-branch' into 'master'
Implemented changes requested by FuzeWorks\Application.

- TableModels now return a TableModelResult. This provides a standardized return variable for all types of TableModels.
- TableModelResults can be iterated over, or turned into an array with toArray()
- TableModels are now requested using the Database::getTableModel() method. Arguments are put in a different order.
- PDOTableModel no longer supports joins
- PDOTableModel no longer supports fetchMode. Use TableModelResult::group instead for certain cases.
- Optimized DatabaseEngine

See merge request fuzeworks/Database!2
2019-09-08 20:56:24 +00:00
Abel Hoogeveen 88135275d9
Implemented changes requested by FuzeWorks\Application.
- TableModels now return a TableModelResult. This provides a standardized return variable for all types of TableModels.
- TableModelResults can be iterated over, or turned into an array with toArray()
- TableModels are now requested using the Database::getTableModel() method. Arguments are put in a different order.
- PDOTableModel no longer supports joins
- PDOTableModel no longer supports fetchMode. Use TableModelResult::group instead for certain cases.
- Optimized DatabaseEngine
2019-09-08 20:32:42 +02:00
Abel Hoogeveen 1c4029d094 Merge branch 'holiday-branch' into 'master'
Release 1.2.0-RC4

See merge request fuzeworks/Database!1
2019-08-21 17:38:48 +00:00
Abel Hoogeveen 2be0b7b158
Release 1.2.0-RC4 2019-08-21 19:35:31 +02:00
Abel Hoogeveen 18f2a6bb40
Moved retrieval of TableModels into the \FuzeWorks\Database class.
- BREAKING: tableModels can no longer be fetched using the iDatabaseTableModel::__construct() method. From now on users must implement the Database::getTableModel() method using the Factory.
- TableModels now have a few more required methods from the iDatabaseTableModel interface. This change shouldn't impact much
2019-08-21 16:42:47 +02:00
Abel Hoogeveen ae8da38cd9
Implemented comments on PDOStatementWrapper.
- Now has hints on what methods it extends
2019-08-16 17:17:17 +02:00
Abel Hoogeveen b8ad345365 Implemented MongoDB engine into `FuzeWorks\Database` and other improvements
- A standard MongoDB engine has been implemented
- Implemented transactions into the iDatabaseEngine and PDOEngine.
- Added a TransactionException when transactions fail
2019-07-22 11:43:01 +02:00
24 changed files with 1585 additions and 179 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2013-2018 TechFuze
Copyright (c) 2013-2021 TechFuze
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,27 +1,30 @@
{
"name": "fuzeworks/database",
"description": "FuzeWorks Framework Database Component",
"homepage": "https://techfuze.net/fuzeworks",
"homepage": "https://i15.nl/fuzeworks",
"license": ["MIT"],
"authors": [
{
"name": "TechFuze",
"homepage": "https://techfuze.net"
},
{
"name": "FuzeWorks Community",
"homepage": "https://techfuze.net/fuzeworks/contributors"
"name": "Abel Hoogeveen",
"homepage": "https://i15.nl"
}
],
"require": {
"php": ">=7.1.0",
"fuzeworks/core": "1.2.0-RC3",
"fuzeworks/mvcr": "1.2.0-RC3",
"ext-pdo": "*"
"php": ">=8.1.0",
"fuzeworks/core": "~1.3.0",
"fuzeworks/mvcr": "~1.3.0"
},
"require-dev": {
"phpunit/phpunit": "^7",
"fuzeworks/tracycomponent": "1.2.0-RC3"
"phpunit/phpunit": "^9",
"fuzeworks/tracycomponent": "~1.3.0",
"mongodb/mongodb": "~1.15.0",
"ext-pdo": "*",
"ext-mongodb": "*"
},
"suggest": {
"mongodb/mongodb": "For using MongoDB databases with this component",
"ext-pdo": "For using PDO endpoints with this component",
"ext-mongodb": "For using MongoDB endpoints with this component"
},
"autoload": {
"psr-4": {

View File

@ -36,10 +36,15 @@
namespace FuzeWorks;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\MongoEngine;
use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\Event\DatabaseLoadDriverEvent;
use FuzeWorks\Event\DatabaseLoadTableModelEvent;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Exception\EventException;
use FuzeWorks\Model\iDatabaseTableModel;
use FuzeWorks\Model\MongoTableModel;
use FuzeWorks\Model\PDOTableModel;
/**
* Database loading class
@ -58,37 +63,51 @@ class Database
*
* @var array
*/
protected $dbConfig;
protected array $dbConfig;
/**
* All engines that can be used for databases
*
* @var iDatabaseEngine[]
*/
protected $engines = [];
protected array $engines = [];
/**
* All tableModels that can be used for connections
*
* @var iDatabaseTableModel[]
*/
protected array $tableModels = [];
/**
* Whether all DatabaseEngines have been loaded yet
*
* @var bool
*/
protected $enginesLoaded = false;
protected bool $enginesLoaded = false;
/**
* Array of all the non-default databases
* Array of all the database engines
*
* @var iDatabaseEngine[]
*/
protected $connections = [];
protected array $connections = [];
/**
* Array of all the tableModels
*
* @var iDatabaseTableModel[]
*/
protected array $tables;
/**
* Register with the TracyBridge upon startup
*/
public function init()
public function init(): void
{
$this->dbConfig = Factory::getInstance()->config->get('database')->toArray();
if (class_exists('Tracy\Debugger', true))
if (class_exists('Tracy\Debugger'))
DatabaseTracyBridge::register();
}
@ -111,19 +130,22 @@ class Database
* @param array $parameters
* @return iDatabaseEngine
* @throws DatabaseException
* @throws EventException
*/
public function get(string $connectionName = 'default', string $engineName = '', array $parameters = []): iDatabaseEngine
{
// Fire the event to allow settings to be changed
/** @var DatabaseLoadDriverEvent $event */
$event = Events::fireEvent('databaseLoadDriverEvent', strtolower($engineName), $parameters, $connectionName);
try {
$event = Events::fireEvent('databaseLoadDriverEvent', strtolower($engineName), $parameters, $connectionName);
} catch (EventException $e) {
throw new DatabaseException("Could not get database. databaseLoadDriverEvent threw exception: '".$e->getMessage()."'");
}
if ($event->isCancelled())
throw new DatabaseException("Could not get database. Cancelled by databaseLoadDriverEvent.");
/** @var iDatabaseEngine $engine */
// If a databaseEngine is provided by the event, use that. Otherwise search in the list of engines
if (is_object($event->databaseEngine) && $event->databaseEngine instanceof iDatabaseEngine)
if ($event->databaseEngine instanceof iDatabaseEngine)
{
// Do intervention first
$engine = $this->connections[$event->connectionName] = $event->databaseEngine;
@ -138,7 +160,7 @@ class Database
elseif (!empty($event->engineName) && !empty($event->parameters))
{
// Do provided config third
$engineClass = get_class($this->getEngine($event->engineName));
$engineClass = get_class($this->fetchEngine($event->engineName));
$engine = $this->connections[$event->connectionName] = new $engineClass();
$engine->setUp($event->parameters);
}
@ -149,18 +171,67 @@ class Database
throw new DatabaseException("Could not get database. Database not found in config.");
$engineName = $this->dbConfig['connections'][$event->connectionName]['engineName'];
$engineClass = get_class($this->getEngine($engineName));
$engineClass = get_class($this->fetchEngine($engineName));
$engine = $this->connections[$event->connectionName] = new $engineClass();
$engine->setUp($this->dbConfig['connections'][$event->connectionName]);
}
// Tie it into the Tracy Bar if available
if (class_exists('\Tracy\Debugger', true))
if (class_exists('\Tracy\Debugger'))
DatabaseTracyBridge::registerDatabase($engine);
return $engine;
}
/**
* @param string $tableName
* @param string $connectionName
* @param string $engineName
* @param array $parameters
* @return iDatabaseTableModel
* @throws DatabaseException
*/
public function getTableModel(string $tableName, string $connectionName = 'default', string $engineName = '', array $parameters = []): iDatabaseTableModel
{
try {
/** @var DatabaseLoadTableModelEvent $event */
$event = Events::fireEvent('databaseLoadTableModelEvent', strtolower($engineName), $parameters, $connectionName, $tableName);
} catch (EventException $e) {
throw new DatabaseException("Could not get TableModel. databaseLoadTableModelEvent threw exception: '" . $e->getMessage() . "'");
}
if ($event->isCancelled())
throw new DatabaseException("Could not get TableModel. Cancelled by databaseLoadTableModelEvent.");
/** @var iDatabaseTableModel $tableModel */
// If a TableModel is provided by the event, use that. Otherwise search in the list of tableModels
if ($event->tableModel instanceof iDatabaseTableModel)
{
$tableModel = $this->tables[$event->connectionName . "|" . $event->tableName] = $event->tableModel;
if (!$tableModel->isSetup())
$tableModel->setUp($this->get($event->connectionName, $tableModel->getEngineName(), $event->parameters), $event->tableName);
}
// If the connection already exists, use that
elseif (isset($this->tables[$event->connectionName . "|" . $event->tableName]))
{
$tableModel = $this->tables[$event->connectionName . "|" . $event->tableName];
}
// Otherwise use the provided configuration
else
{
// First the engine shall be fetched, so the name of the tableModel is known
$engine = $this->get($event->connectionName, $event->engineName, $event->parameters);
$tableModelClass = get_class($this->fetchTableModel($engine->getName()));
// Load the tableModel and add the engine
$tableModel = $this->tables[$event->connectionName . "|" . $event->tableName] = new $tableModelClass();
$tableModel->setUp($engine, $event->tableName);
}
// And return the tableModel
return $tableModel;
}
/**
* Get a loaded database engine.
*
@ -168,13 +239,13 @@ class Database
* @return iDatabaseEngine
* @throws DatabaseException
*/
public function getEngine(string $engineName): iDatabaseEngine
public function fetchEngine(string $engineName): iDatabaseEngine
{
// First retrieve the name
$engineName = strtolower($engineName);
// Then load all engines
$this->loadDatabaseEngines();
$this->loadDatabaseComponents();
// If the engine exists, return it
if (isset($this->engines[$engineName]))
@ -184,6 +255,29 @@ class Database
throw new DatabaseException("Could not get engine. Engine does not exist.");
}
/**
* Fetch a loaded TableModel
*
* @param string $tableModelName
* @return iDatabaseTableModel
* @throws DatabaseException
*/
public function fetchTableModel(string $tableModelName): iDatabaseTableModel
{
// First retrieve the name
$tableModelName = strtolower($tableModelName);
// Then load all the tableModels
$this->loadDatabaseComponents();
// If the tableModel exists, return it
if (isset($this->tableModels[$tableModelName]))
return $this->tableModels[$tableModelName];
// Otherwise throw an exception
throw new DatabaseException("Could not get tableModel. TableModel does not exist.");
}
/**
* Register a new database engine
*
@ -208,13 +302,36 @@ class Database
return true;
}
/**
* Register a new database tableModel
*
* @param iDatabaseTableModel $tableModel
* @return bool
* @throws DatabaseException
*/
public function registerTableModel(iDatabaseTableModel $tableModel): bool
{
// First retrieve the name
$tableModelName = strtolower($tableModel->getName());
// Check if the tableModel is already set
if (isset($this->tableModels[$tableModelName]))
throw new DatabaseException("Could not register tableModel. TableModel '" . $tableModelName . "' already registered.");
// Install it
$this->tableModels[$tableModelName] = $tableModel;
Logger::log("Registered TableModel type: '" . $tableModelName . "'");
return true;
}
/**
* Load all databaseEngines by firing a databaseLoadEngineEvent and by loading all the default engines
*
* @return bool
* @throws DatabaseException
*/
protected function loadDatabaseEngines(): bool
protected function loadDatabaseComponents(): bool
{
// If already loaded, skip
if ($this->enginesLoaded)
@ -229,6 +346,11 @@ class Database
// Load the engines provided by the DatabaseComponent
$this->registerEngine(new PDOEngine());
$this->registerEngine(new MongoEngine());
// Load the tableModels provided by the DatabaseComponent
$this->registerTableModel(new PDOTableModel());
$this->registerTableModel(new MongoTableModel());
// And save results
$this->enginesLoaded = true;

View File

@ -39,7 +39,7 @@ namespace FuzeWorks\DatabaseEngine;
/**
* Class DatabaseDriver
*/
abstract class DatabaseDriver
abstract class DatabaseDriver implements iDatabaseEngine
{
// --- Query Logging --------------------------------------------------
@ -49,7 +49,7 @@ abstract class DatabaseDriver
*
* @var array
*/
private $queries = [];
private array $queries = [];
/**
* Log information about a query. Used for debugging issues
@ -74,7 +74,7 @@ abstract class DatabaseDriver
*
* @return array
*/
public function getQueries()
public function getQueries(): array
{
return $this->queries;
}
@ -82,5 +82,4 @@ abstract class DatabaseDriver
// --------------------------------------------------------------------
}

View File

@ -0,0 +1,201 @@
<?php
/**
* FuzeWorks Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\DatabaseEngine;
use InvalidArgumentException;
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
class MongoCommandSubscriber implements CommandSubscriber
{
/**
* @var MongoEngine
*/
private MongoEngine $mongoEngine;
/**
* @var float
*/
private float $commandTimings = 0.0;
/**
* @var string
*/
private string $queryString;
public function __construct(MongoEngine $engine)
{
$this->mongoEngine = $engine;
}
/**
* Notification method for a failed command.
* If the subscriber has been registered with MongoDB\Driver\Monitoring\addSubscriber(), the driver will call this method when a command has failed.
* @link https://secure.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandfailed.php
* @param CommandFailedEvent $event An event object encapsulating information about the failed command.
* @return void
* @throws InvalidArgumentException on argument parsing errors.
* @since 1.3.0
*/
public function commandFailed(CommandFailedEvent $event): void
{
// TODO: Implement commandFailed() method.
}
/**
* Notification method for a started command.
* If the subscriber has been registered with MongoDB\Driver\Monitoring\addSubscriber(), the driver will call this method when a command has started.
* @link https://secure.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandstarted.php
* @param CommandStartedEvent $event An event object encapsulating information about the started command.
* @return void
* @throws InvalidArgumentException on argument parsing errors.
* @since 1.3.0
*/
public function commandStarted(CommandStartedEvent $event): void
{
$this->commandTimings = microtime(true);
$this->queryString = strtoupper($event->getCommandName());
// Determine query string
$command = $event->getCommand();
$this->queryString .= ' \'' . $event->getDatabaseName() . '.' . $command->{$event->getCommandName()} . '\'';
// If a projection is provided, print it
if (isset($command->projection))
{
$projection = $command->projection;
$projectionStrings = [];
foreach ($projection as $projectionKey => $projectionVal)
$projectionStrings[] = $projectionKey;
$this->queryString .= " PROJECT[" . implode(',', $projectionStrings) . ']';
}
// If a filter is provided, print it
if (isset($command->filter) && !empty((array) $command->filter))
{
$filter = $command->filter;
$filterStrings = [];
foreach ($filter as $filterKey => $filterVal)
$filterStrings[] = $filterKey;
$this->queryString .= " FILTER[" . implode(',', $filterStrings) . ']';
}
// If a sort is provided, print it
if (isset($command->sort))
{
$sort = $command->sort;
$sortStrings = [];
foreach ($sort as $sortKey => $sortVal)
$sortStrings[] = $sortKey . ($sortVal == 1 ? ' ASC' : ' DESC');
$this->queryString .= " SORT[" . implode(',', $sortStrings) . ']';
}
// If documents are provided, print it
if (isset($command->documents))
{
$documents = $command->documents;
$documentKeys = [];
foreach ($documents as $document)
$documentKeys = array_merge($documentKeys, array_keys((array) $document));
$this->queryString .= " VALUES[" . implode(',', $documentKeys) . ']';
}
// If a deletes is provided, print it
if (isset($command->deletes))
{
$deletes = $command->deletes;
$deleteKeys = [];
foreach ($deletes as $delete)
{
if (!isset($delete->q))
continue;
$deleteKeys = array_merge($deleteKeys, array_keys((array) $delete->q));
}
$this->queryString .= " FILTER[" . implode(',', $deleteKeys) . ']';
}
// If a limit is provided, print it
if (isset($command->limit))
$this->queryString .= " LIMIT(".$command->limit.")";
}
/**
* Notification method for a successful command.
* If the subscriber has been registered with MongoDB\Driver\Monitoring\addSubscriber(), the driver will call this method when a command has succeeded.
* @link https://secure.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandsucceeded.php
* @param CommandSucceededEvent $event An event object encapsulating information about the successful command.
* @return void
* @throws InvalidArgumentException on argument parsing errors.
* @since 1.3.0
*/
public function commandSucceeded(CommandSucceededEvent $event): void
{
// Get variables
$queryTimings = microtime(true) - $this->commandTimings;
$queryString = $this->queryString;
$queryData = 0;
switch ($event->getCommandName())
{
case 'find':
$queryData = count($event->getReply()->cursor->firstBatch);
break;
case 'update':
case 'delete':
case 'insert':
$queryData = $event->getReply()->n;
break;
}
// And log query
$this->mongoEngine->logMongoQuery($queryString, $queryData, $queryTimings, []);
// And reset timings
$this->commandTimings = 0.0;
}
}

View File

@ -0,0 +1,234 @@
<?php
/**
* FuzeWorks Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\DatabaseEngine;
use FuzeWorks\Exception\DatabaseException;
use MongoDB\ChangeStream;
use MongoDB\Client;
use MongoDB\Collection;
use MongoDB\Database;
use MongoDB\Driver\Exception\InvalidArgumentException;
use MongoDB\Driver\Manager;
use function MongoDB\Driver\Monitoring\addSubscriber;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\RuntimeException;
use MongoDB\Model\DatabaseInfoIterator;
/**
* Class MongoEngine
*
* The following additional methods can be accessed through the __call method
* @method array|object dropDatabase(string $databaseName, array $options = [])
* @method Manager getManager()
* @method ReadConcern getReadConcern()
* @method ReadPreference getReadPreference()
* @method array getTypeMap()
* @method WriteConcern getWriteConcern()
* @method DatabaseInfoIterator listDatabases(array $options = [])
* @method Collection selectCollection(string $databaseName, string $collectionName, array $options = [])
* @method Database selectDatabase(string $databaseName, array $options = [])
* @method Session startSession(array $options = [])
* @method ChangeStream watch(array $pipeline = [], array $options = [])
*/
class MongoEngine extends DatabaseDriver
{
/**
* Whether the Engine has been set up
*
* @var bool
*/
protected bool $setUp = false;
/**
* @var Client|null
*/
protected ?Client $mongoConnection = null;
/**
* Connection string with the database
*
* @var string|null
*/
protected ?string $uri = null;
/**
* Returns the name of this engine
*
* @return string
*/
public function getName(): string
{
return 'mongo';
}
public function getConnectionDescription(): string
{
if (is_null($this->mongoConnection))
return 'none';
return $this->uri;
}
/**
* Whether the database connection has been set up yet
*
* @return bool
*/
public function isSetup(): bool
{
return $this->setUp;
}
/**
* Method called by \FuzeWorks\Database to setUp the database connection
*
* @param array $parameters
* @return bool
* @throws DatabaseException
*/
public function setUp(array $parameters): bool
{
// Prepare variables for connection
$this->uri = $parameters['uri'] ?? null;
$uriOptions = $parameters['uriOptions'] ?? [];
$driverOptions = $parameters['driverOptions'] ?? [];
// Don't attempt and connect without a URI
if (is_null($this->uri))
throw new DatabaseException("Could not setUp MongoEngine. No URI provided");
// Import username and password
if (isset($parameters['username']) && isset($parameters['password']))
{
$uriOptions['username'] = $parameters['username'];
$uriOptions['password'] = $parameters['password'];
}
// And set FuzeWorks app name
$uriOptions['appname'] = 'FuzeWorks';
try {
$this->mongoConnection = new Client($this->uri, $uriOptions, $driverOptions);
} catch (InvalidArgumentException | RuntimeException $e) {
throw new DatabaseException("Could not setUp MongoEngine. MongoDB threw exception: '" . $e->getMessage() . "'");
}
// Set this engine as set up
$this->setUp = true;
// Register subscriber
$subscriber = new MongoCommandSubscriber($this);
addSubscriber($subscriber);
// And return true upon success
return true;
}
public function logMongoQuery(string $queryString, int $queryData, float $queryTimings, array $errorInfo = [])
{
$this->logQuery($queryString, $queryData, $queryTimings, $errorInfo);
}
/**
* Method called by \FuzeWorks\Database to tear down the database connection upon shutdown
*
* @return bool
*/
public function tearDown(): bool
{
// MongoDB does not require any action. Always return true
return true;
}
/**
* Call methods on the Mongo Connection
*
* @param $name
* @param $arguments
* @return mixed
*/
public function __call($name, $arguments)
{
return $this->mongoConnection->{$name}(...$arguments);
}
/**
* Get properties from the Mongo Connection
*
* @param $name
* @return Database
*/
public function __get($name): Database
{
return $this->mongoConnection->$name;
}
/**
* @return bool
*/
public function transactionStart(): bool
{
return false;
}
/**
* @return bool
*/
public function transactionEnd(): bool
{
return false;
}
/**
* @return bool
*/
public function transactionCommit(): bool
{
return false;
}
/**
* @return bool
*/
public function transactionRollback(): bool
{
return false;
}
}

View File

@ -36,6 +36,7 @@
namespace FuzeWorks\DatabaseEngine;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Exception\TransactionException;
use FuzeWorks\Logger;
use PDO;
use PDOException;
@ -51,7 +52,7 @@ use PDOStatement;
* @method string quote(string $string, int $parameter_type = PDO::PARAM_STR)
* @method bool setAttribute(int $attribute, mixed $value)
*/
class PDOEngine extends DatabaseDriver implements iDatabaseEngine
class PDOEngine extends DatabaseDriver
{
/**
@ -59,28 +60,28 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
*
* @var bool
*/
protected $setUp = false;
protected bool $setUp = false;
/**
* The PDO object connected with the database
*
* @var PDO
*/
protected $pdoConnection;
protected PDO $pdoConnection;
/**
* Connection string with the database
*
* @var string
* @var string|null
*/
protected $dsn;
protected ?string $dsn = null;
/**
* Whether a transaction has failed and should be reverted in the future
*
* @var bool
*/
protected $transactionFailed = false;
protected bool $transactionFailed = false;
/**
* Whether a transaction should be automatically committed if not manually aborted by the user.
@ -90,7 +91,7 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
*
* @var bool
*/
protected $transactionAutocommit = false;
protected bool $transactionAutocommit = false;
/**
* Returns the name of this engine
@ -127,9 +128,9 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
public function setUp(array $parameters): bool
{
// Prepare variables for connection
$this->dsn = isset($parameters['dsn']) ? $parameters['dsn'] : null;
$username = isset($parameters['username']) ? $parameters['username'] : '';
$password = isset($parameters['password']) ? $parameters['password'] : '';
$this->dsn = $parameters['dsn'] ?? null;
$username = $parameters['username'] ?? '';
$password = $parameters['password'] ?? '';
// Don't attempt connection without DSN
if (is_null($this->dsn))
@ -156,14 +157,12 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
* Method called by \FuzeWorks\Database to tear down the database connection upon shutdown
*
* @return bool
* @throws TransactionException
*/
public function tearDown(): bool
{
// Commit or rollback all changes to the database
$this->transactionEnd();
//
$this->pdoConnection = null;
return true;
}
@ -209,8 +208,9 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
* @param string $queryString
* @param int $queryData
* @param float $queryTimings
* @param array $errorInfo
*/
public function logPDOQuery(string $queryString, int $queryData, float $queryTimings, $errorInfo = [])
public function logPDOQuery(string $queryString, int $queryData, float $queryTimings, array $errorInfo = [])
{
$errorInfo = empty($errorInfo) ? $this->error() : $errorInfo;
$this->logQuery($queryString, $queryData, $queryTimings, $errorInfo);
@ -236,7 +236,7 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
$benchmarkEnd = microtime(true) - $benchmarkStart;
// Log the query
$this->logPDOQuery($sql, [], $benchmarkEnd);
$this->logPDOQuery($sql, 0, $benchmarkEnd);
// If the query failed, handle the error
if ($result === false)
@ -262,7 +262,8 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
{
return new PDOStatementWrapper(
$this->pdoConnection->prepare($statement, $driver_options),
array($this, 'logPDOQuery')
array($this, 'logPDOQuery'),
$this
);
}
@ -289,10 +290,15 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
* Start a transaction
*
* @return bool
* @throws TransactionException
*/
public function transactionStart(): bool
{
return $this->pdoConnection->beginTransaction();
try {
return $this->pdoConnection->beginTransaction();
} catch (PDOException $e) {
throw new TransactionException("Could not start transaction. PDO threw PDOException: '" . $e->getMessage() . "'");
}
}
/**
@ -302,6 +308,7 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
* Automatically rolls back changes if an error occurs with a query
*
* @return bool
* @throws TransactionException
*/
public function transactionEnd(): bool
{
@ -327,19 +334,37 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
* Commit a transaction
*
* @return bool
* @throws TransactionException
*/
public function transactionCommit(): bool
{
return $this->pdoConnection->commit();
try {
return $this->pdoConnection->commit();
} catch (PDOException $e) {
throw new TransactionException("Could not commit transaction. PDO threw PDOException: '" . $e->getMessage() . "'");
}
}
/**
* Roll back a transaction
*
* @return bool
* @throws TransactionException
*/
public function transactionRollback(): bool
{
return $this->pdoConnection->rollBack();
try {
return $this->pdoConnection->rollBack();
} catch (PDOException $e) {
throw new TransactionException("Could not rollback transaction. PDO threw PDOException: '" . $e->getMessage() . "'");
}
}
/**
* @internal
*/
public function transactionFail()
{
$this->transactionFailed = true;
}
}

View File

@ -38,14 +38,40 @@ namespace FuzeWorks\DatabaseEngine;
use FuzeWorks\Exception\DatabaseException;
use PDOStatement;
use PDO;
/**
* Class PDOStatementWrapper
*
* Provides a wrapper for PDOStatement objects so that these can be logged
*
* The following additional methods can be accessed through the __call method:
* @method bool bindColumn(mixed $column, mixed &$param, int $type = null, int $maxlen = null, mixed $driverdata = null)
* @method bool bindParam(mixed $parameter, mixed &$variable, int $data_type = PDO::PARAM_STR, int $length = null, mixed $driver_options = null)
* @method bool bindValue(mixed $parameter, mixed $value, int $data_type = PDO::PARAM_STR)
* @method bool closeCursor()
* @method int columnCount()
* @method void debugDumpParams()
* @method string errorCode()
* @method array errorInfo()
* @method mixed fetch(int $fetch_style = null, int $cursor_orientation = PDO::FETCH_ORI_NEXT, int $cursor_offset = 0)
* @method array fetchAll(int $fetch_style = null, mixed $fetch_argument = null, array $ctor_args = array())
* @method mixed fetchColumn(int $column_number = 0)
* @method mixed fetchObject(string $class_name = "stdClass", array $ctor_args = array())
* @method mixed getAttribute(int $attribute)
* @method array getColumnMeta(int $column)
* @method bool nextRowset()
* @method int rowCount()
* @method bool setAttribute(int $attribute, mixed $value)
* @method bool setFetchMode(int $mode)
*/
class PDOStatementWrapper
{
/**
* @var PDOStatement
*/
private $statement;
private PDOStatement $statement;
/**
* Callable for logging queries and errors
@ -54,17 +80,31 @@ class PDOStatementWrapper
*/
private $logQueryCallable;
public function __construct(PDOStatement $statement, callable $logQueryCallable)
/**
* @var PDOEngine
*/
private PDOEngine $engine;
public function __construct(PDOStatement $statement, callable $logQueryCallable, PDOEngine $engine)
{
$this->statement = $statement;
$this->logQueryCallable = $logQueryCallable;
$this->engine = $engine;
}
public function execute(array $input_parameters = [])
/**
* @param array $input_parameters
* @return bool
* @throws DatabaseException
*/
public function execute(array $input_parameters = []): bool
{
// Run the query and benchmark the time
$benchmarkStart = microtime(true);
$result = $this->statement->execute($input_parameters);
if (empty($input_parameters))
$result = $this->statement->execute();
else
$result = $this->statement->execute($input_parameters);
$benchmarkEnd = microtime(true) - $benchmarkStart;
$errInfo = $this->error();
call_user_func_array($this->logQueryCallable, [$this->statement->queryString, $this->statement->rowCount(), $benchmarkEnd, $errInfo]);
@ -72,11 +112,33 @@ class PDOStatementWrapper
// If the query failed, throw an error
if ($result === false)
{
$this->engine->transactionFail();
// And throw an exception
throw new DatabaseException("Could not run query. Database returned an error. Error code: " . $errInfo['code']);
}
return $result;
return true;
}
/**
* Retrieves the statement last used
*
* @return PDOStatement
*/
public function getStatement(): PDOStatement
{
return $this->statement;
}
/**
* Returns the query string
*
* @return string
*/
public function getQuery(): string
{
return $this->statement->queryString;
}
/**
@ -107,5 +169,4 @@ class PDOStatementWrapper
{
return $this->statement->$name;
}
}

View File

@ -36,7 +36,6 @@
namespace FuzeWorks\DatabaseEngine;
interface iDatabaseEngine
{
public function getName(): string;
@ -44,4 +43,24 @@ interface iDatabaseEngine
public function isSetup(): bool;
public function setUp(array $parameters): bool;
public function tearDown(): bool;
/**
* @return bool
*/
public function transactionStart(): bool;
/**
* @return bool
*/
public function transactionEnd(): bool;
/**
* @return bool
*/
public function transactionCommit(): bool;
/**
* @return bool
*/
public function transactionRollback(): bool;
}

View File

@ -38,7 +38,6 @@ namespace FuzeWorks;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use Tracy\IBarPanel;
use Tracy\Debugger;
use Tracy\Dumper;
/**
* DatabaseTracyBridge Class.
@ -57,9 +56,9 @@ class DatabaseTracyBridge implements IBarPanel
/**
* @var iDatabaseEngine[]
*/
public static $databases = array();
public static array $databases = array();
protected $results = array();
protected array $results = array();
public static function register()
{
@ -133,8 +132,6 @@ class DatabaseTracyBridge implements IBarPanel
$results = array_slice($results, -10);
}
//dump($results['queries']['mysql:host=localhost;dbname=hello']);
return $this->results = $results;
}

View File

@ -54,28 +54,28 @@ class DatabaseLoadDriverEvent extends Event
*
* @var iDatabaseEngine|null
*/
public $databaseEngine = null;
public ?iDatabaseEngine $databaseEngine = null;
/**
* The name of the engine to be loaded
*
* @var string
*/
public $engineName;
public string $engineName;
/**
* Parameters of the database to be loaded
*
* @var array
*/
public $parameters;
public array $parameters;
/**
* Database group to load
*
* @var bool
* @var string
*/
public $connectionName;
public string $connectionName;
public function init(string $engineName, array $parameters, string $connectionName)
{

View File

@ -0,0 +1,90 @@
<?php
/**
* FuzeWorks Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Event;
use FuzeWorks\Event;
use FuzeWorks\Model\iDatabaseTableModel;
class DatabaseLoadTableModelEvent extends Event
{
/**
* A possible database that can be loaded.
*
* Provide a database in this variable and it will be loaded. It shall be identified as default if
* the parameters variable is empty. If there is a string in parameters this database shall be identified as
* such.
*
* @var iDatabaseTableModel|null
*/
public ?iDatabaseTableModel $tableModel = null;
/**
* The name of the engine to be loaded
*
* @var string
*/
public string $engineName;
/**
* The name of the table this model manages
*
* @var string
*/
public string $tableName;
/**
* Parameters of the database to be loaded
*
* @var array
*/
public array $parameters;
/**
* Database group to load
*
* @var string
*/
public string $connectionName;
public function init(string $engineName, array $parameters, string $connectionName, string $tableName)
{
$this->engineName = $engineName;
$this->parameters = $parameters;
$this->connectionName = $connectionName;
$this->tableName = $tableName;
}
}

View File

@ -44,6 +44,4 @@ namespace FuzeWorks\Exception;
*/
class DatabaseException extends Exception
{
}
?>
}

View File

@ -0,0 +1,47 @@
<?php
/**
* FuzeWorks Framework Database Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Exception;
/**
* Class DatabaseException.
*
* @author Abel Hoogeveen <abel@techfuze.net>
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
*/
class TransactionException extends Exception
{
}

View File

@ -0,0 +1,284 @@
<?php
/**
* FuzeWorks Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Model;
use FuzeWorks\Database;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\MongoEngine;
use FuzeWorks\Exception\DatabaseException;
use MongoDB\Collection;
class MongoTableModel implements iDatabaseTableModel
{
/**
* Holds the FuzeWorks Database loader
*
* @var Database
*/
private Database $databases;
/**
* Whether the tableModel has been properly setup
*
* @var bool
*/
protected bool $setup = false;
/**
* Holds the PDOEngine for this model
*
* @var MongoEngine
*/
protected MongoEngine $dbEngine;
/**
* Holds the collection that is being modified
*
* @var Collection
*/
protected Collection $collection;
/**
* Initializes the model
*
* @param iDatabaseEngine $engine
* @param string $tableName
* @throws DatabaseException
*/
public function setUp(iDatabaseEngine $engine, string $tableName): void
{
if (!$engine instanceof MongoEngine)
throw new DatabaseException('MongoTableModel can only be used with a MongoEngine');
$this->dbEngine = $engine;
$this->collection = $this->getCollection($tableName);
$this->setup = true;
}
public function isSetup(): bool
{
return $this->setup;
}
/**
* @param string $collectionString
* @return Collection
* @throws DatabaseException
*/
protected function getCollection(string $collectionString): Collection
{
// Determine collection
$coll = explode('.', $collectionString);
if (count($coll) != 2)
throw new DatabaseException("Could not load MongoTableModel. Provided tableName is not a valid collection string.");
return $this->dbEngine->{$coll[0]}->{$coll[1]};
}
/**
* @return string
*/
public function getName(): string
{
return 'mongo';
}
/**
* @return string
*/
public function getEngineName(): string
{
return 'mongo';
}
/**
* @return iDatabaseEngine
* @throws DatabaseException
*/
public function getEngine(): iDatabaseEngine
{
if (!$this->setup)
throw new DatabaseException("Could not return Engine. Engine not setup yet.");
return $this->dbEngine;
}
/**
* @param array $data
* @param array $options
* @param string $table
* @return int
* @throws DatabaseException
*/
public function create(array $data, array $options = [], string $table = 'default'): int
{
// If not data is provided, stop now
if (empty($data))
throw new DatabaseException("Could not create data. No data provided.");
// Select collection
if ($table == 'default')
$collection = $this->collection;
else
$collection = $this->getCollection($table);
// And execute the request
if ($this->arrIsAssoc($data))
$res = $collection->insertOne($data, $options);
else
$res = $collection->insertMany($data, $options);
// And return the count of inserted documents
return $res->getInsertedCount();
}
/**
* @param array $filter
* @param array $options
* @param string $table
* @return TableModelResult
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = [], string $table = 'default'): TableModelResult
{
// Select collection
if ($table == 'default')
$collection = $this->collection;
else
$collection = $this->getCollection($table);
// Execute the request
$results = $collection->find($filter, $options);
// Convert the result into a TableModelResult
return new TableModelResult($results);
}
/**
* @param array $data
* @param array $filter
* @param array $options
* @param string $table
* @return int
* @throws DatabaseException
*/
public function update(array $data, array $filter, array $options = [], string $table = 'default'): int
{
// If not data is provided, stop now
if (empty($data))
throw new DatabaseException("Could not create data. No data provided.");
// Select collection
if ($table == 'default')
$collection = $this->collection;
else
$collection = $this->getCollection($table);
// And execute the request
$data = ['$set' => $data];
$res = $collection->updateMany($filter, $data, $options);
// Return the result
return $res->getModifiedCount();
}
/**
* @param array $filter
* @param array $options
* @param string $table
* @return int
* @throws DatabaseException
*/
public function delete(array $filter, array $options = [], string $table = 'default'): int
{
// Select collection
if ($table == 'default')
$collection = $this->collection;
else
$collection = $this->getCollection($table);
// Execute the request
$res = $collection->deleteMany($filter, $options);
// Return the result
return $res->getDeletedCount();
}
/**
* @return bool
*/
public function transactionStart(): bool
{
return $this->dbEngine->transactionStart();
}
/**
* @return bool
*/
public function transactionEnd(): bool
{
return $this->dbEngine->transactionEnd();
}
/**
* @return bool
*/
public function transactionCommit(): bool
{
return $this->dbEngine->transactionCommit();
}
/**
* @return bool
*/
public function transactionRollback(): bool
{
return $this->dbEngine->transactionRollback();
}
/**
* Determines whether an array is associative or numeric
*
* @param array $arr
* @return bool
*/
private function arrIsAssoc(array $arr): bool
{
if (array() === $arr) return false;
return array_keys($arr) !== range(0, count($arr) - 1);
}
}

View File

@ -36,12 +36,11 @@
namespace FuzeWorks\Model;
use FuzeWorks\Database;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\DatabaseEngine\PDOStatementWrapper;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Exception\EventException;
use FuzeWorks\Factory;
use FuzeWorks\Exception\TransactionException;
use PDO;
use PDOStatement;
@ -53,10 +52,6 @@ use PDOStatement;
* The following additional methods can be accessed through the __call method
* @method PDOStatement query(string $sql)
* @method PDOStatementWrapper prepare(string $statement, array $driver_options = [])
* @method bool transactionStart()
* @method bool transactionEnd()
* @method bool transactionCommit()
* @method bool transactionRollback()
* @method bool exec(string $statement)
* @method mixed getAttribute(int $attribute)
* @method string lastInsertId(string $name = null)
@ -65,54 +60,88 @@ use PDOStatement;
*/
class PDOTableModel implements iDatabaseTableModel
{
/**
* Holds the FuzeWorks Database loader
*
* @var Database
*/
private $databases;
/**
* Holds the PDOEngine for this model
*
* @var PDOEngine
*/
protected $dbEngine;
protected PDOEngine $dbEngine;
/**
* Whether the tableModel has been properly setup
*
* @var bool
*/
protected bool $setup = false;
/**
* The table this model manages on the database
*
* @var string
*/
protected $tableName;
protected string $tableName;
/**
* The last statement used by PDO
*
* @var PDOStatementWrapper
*/
protected $lastStatement;
protected PDOStatementWrapper $lastStatement;
/**
* Initializes the model to connect with the database.
* Initializes the model
*
* @param string $connectionName
* @param array $parameters
* @param string|null $tableName
* @param iDatabaseEngine $engine
* @param string $tableName
* @throws DatabaseException
* @throws EventException
* @see PDOEngine::setUp()
*/
public function __construct(string $connectionName = 'default', array $parameters = [], string $tableName = null)
public function setUp(iDatabaseEngine $engine, string $tableName): void
{
if (is_null($this->databases))
$this->databases = Factory::getInstance()->databases;
if (!$engine instanceof PDOEngine)
throw new DatabaseException('PDOTableModel can only be used with PDOEngine');
$this->dbEngine = $this->databases->get($connectionName, 'pdo', $parameters);
$this->dbEngine = $engine;
$this->tableName = $tableName;
$this->setup = true;
}
public function create(array $data, array $options = []): bool
public function isSetup(): bool
{
return $this->setup;
}
public function getName(): string
{
return 'pdo';
}
/**
* @return string
*/
public function getEngineName(): string
{
return 'pdo';
}
/**
* @return iDatabaseEngine
* @throws DatabaseException
*/
public function getEngine(): iDatabaseEngine
{
if (!$this->setup)
throw new DatabaseException("Could not return Engine. Engine not setup yet.");
return $this->dbEngine;
}
/**
* @param array $data
* @param array $options
* @return int
* @throws DatabaseException
*/
public function create(array $data, array $options = []): int
{
// If no data is provided, stop now
if (empty($data))
@ -137,43 +166,46 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($record);
// And return true for success
return true;
return $this->lastStatement->rowCount();
}
/**
* @todo: WRITE ABOUT FETCHMODE
*
* @param array $filter
* @param array $options
* @return array
* @return TableModelResult
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = []): array
public function read(array $filter = [], array $options = []): TableModelResult
{
// Determine which fields to select. If none provided, select all
$fields = (isset($options['fields']) && is_array($options['fields']) ? implode(',', $options['fields']) : '*');
$limit = isset($options['limit']) ? 'LIMIT ' . intval($options['limit']) : '';
// Apply the filter. If none provided, don't condition it
$where = $this->filter($filter);
// Generate the sql and create a PDOStatement
$sql = "SELECT " . $fields . " FROM {$this->tableName} " . $where;
$sql = "SELECT " . $fields . " FROM {$this->tableName} " . $where . " " . $limit;
/** @var PDOStatement $statement */
$this->lastStatement = $this->dbEngine->prepare($sql);
// And execute the query
$this->lastStatement->execute($filter);
foreach ($filter as $key => $val)
{
if (is_null($val))
$this->lastStatement->bindValue(':' . $key, $val, PDO::PARAM_NULL);
else
$this->lastStatement->bindValue(':' . $key, $val);
}
// And return the result
$fetchMode = (isset($options['fetchMode']) ? $options['fetchMode'] : PDO::FETCH_ASSOC);
if (is_array($fetchMode))
return $this->lastStatement->fetchAll(...$fetchMode);
$this->lastStatement->execute();
return $this->lastStatement->fetchAll($fetchMode);
// Fetch PDO Iterable
return new TableModelResult($this->lastStatement->getStatement());
}
public function update(array $data, array $filter, array $options = []): bool
public function update(array $data, array $filter, array $options = []): int
{
// If no data is provided, stop now
if (empty($data))
@ -183,6 +215,7 @@ class PDOTableModel implements iDatabaseTableModel
$where = $this->filter($filter);
// Determine fields and values
$fields = [];
foreach ($data as $key => $val)
$fields[] = $key."=:".$key;
@ -201,16 +234,17 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($parameters);
// And return true for success
return true;
return $this->lastStatement->rowCount();
}
public function delete(array $filter, array $options = []): bool
public function delete(array $filter, array $options = []): int
{
// Apply the filter
$where = $this->filter($filter);
$limit = isset($options['limit']) ? 'LIMIT ' . intval($options['limit']) : '';
// Generate the sql and create a PDOStatement
$sql = "DELETE FROM {$this->tableName} " . $where;
$sql = "DELETE FROM {$this->tableName} " . $where . " " . $limit;
/** @var PDOStatement $statement */
$this->lastStatement = $this->dbEngine->prepare($sql);
@ -219,12 +253,43 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($filter);
// And return true for success
return true;
return $this->lastStatement->rowCount();
}
public function getLastStatement(): PDOStatementWrapper
/**
* @return bool
* @throws TransactionException
*/
public function transactionStart(): bool
{
return $this->lastStatement;
return $this->dbEngine->transactionStart();
}
/**
* @return bool
* @throws TransactionException
*/
public function transactionEnd(): bool
{
return $this->dbEngine->transactionEnd();
}
/**
* @return bool
* @throws TransactionException
*/
public function transactionCommit(): bool
{
return $this->dbEngine->transactionCommit();
}
/**
* @return bool
* @throws TransactionException
*/
public function transactionRollback(): bool
{
return $this->dbEngine->transactionRollback();
}
/**
@ -281,6 +346,8 @@ class PDOTableModel implements iDatabaseTableModel
$record = $record[0];
// Determine the fields and values
$fields = [];
$values = [];
foreach ($record as $key => $val)
{
$fields[] = $key;

View File

@ -0,0 +1,164 @@
<?php
/**
* FuzeWorks Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Model;
use ArrayObject;
use IteratorAggregate;
use Traversable;
class TableModelResult implements IteratorAggregate
{
/**
* Raw result from the TableModel
*
* @var Traversable
*/
private iterable $raw;
/**
* Result from the TableModel, possibly altered by TableModelResult methods.
*
* @var ArrayObject
*/
private ArrayObject $result;
/**
* @var Traversable
*/
private iterable $traversable;
/**
* Whether the raw input has already been fully fetched
*
* @var bool
*/
private bool $fullyFetched = false;
public function __construct(iterable $results)
{
$this->raw = $results;
$this->traversable = $results;
}
/**
* Group the results by a certain field.
*
* @param string $field
* @return TableModelResult
*/
public function group(string $field): self
{
// First make sure all data is fetched
$this->allToArray();
// Afterwards build a grouped array
$grouped = [];
foreach ($this->result->getIterator() as $key => $val)
{
// Check if this group exists within the results
if (isset($val[$field]))
{
// Name of the group
$fieldSelector = $val[$field];
// If the group has never been found before, add the array
if (!isset($grouped[$fieldSelector]))
$grouped[$fieldSelector] = [];
unset($val[$field]);
$grouped[$fieldSelector][] = $val;
}
}
$this->result->exchangeArray($grouped);
return $this;
}
/**
* Convert the result into an array
*
* @return array
*/
public function toArray(): array
{
// First make sure all data is fetched
$this->allToArray();
// And return a copy
return $this->result->getArrayCopy();
}
/**
* Retrieve an external iterator
* @link https://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator(): Traversable
{
return $this->traversable;
}
private function allToArray(): void
{
// If the input has already been fetched, ignore it
if ($this->fullyFetched)
return;
$result = [];
foreach ($this->raw as $key => $val)
{
// Clear out all numeric keys
foreach ($val as $recKey => $recVal)
if (is_numeric($recKey))
unset($val[$recKey]);
$result[$key] = $val;
}
// Set the variable
$this->result = new ArrayObject($result);
// Afterwards modify the traversable
$this->traversable = $this->result->getIterator();
// Set fullyFetched to true so it doesn't get fetched again
$this->fullyFetched = true;
}
}

View File

@ -35,12 +35,122 @@
*/
namespace FuzeWorks\Model;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\Exception\DatabaseException;
interface iDatabaseTableModel
{
public function create(array $data, array $options = []): bool;
public function read(array $filter = [], array $options = []): array;
public function update(array $data, array $filter, array $options = []): bool;
public function delete(array $filter, array $options = []): bool;
/**
* Returns the name of the TableModel.
*
* Usually 'pdo' or 'mongo'.
*
* @return string
*/
public function getName(): string;
/**
* Return the name of the engine used by this TableModel.
*
* Usually 'pdo' or 'mongo'
*
* @return string
*/
public function getEngineName(): string;
/**
* Return the engine used by this TableModel
*
* @return iDatabaseEngine
*/
public function getEngine(): iDatabaseEngine;
/**
* Method invoked by FuzeWorks\Database to setup this tableModel.
*
* Provides the TableModel with the appropriate iDatabaseEngine and the name of the table.
*
* @param iDatabaseEngine $engine
* @param string $tableName
* @return void
* @throws DatabaseException
*/
public function setUp(iDatabaseEngine $engine, string $tableName): void;
/**
* Returns whether the TableModel has been setup yet
*
* @return bool
*/
public function isSetup(): bool;
/**
* Creates data in the model.
*
* @param array $data
* @param array $options
* @return int
* @throws DatabaseException
*/
public function create(array $data, array $options = []): int;
/**
* Returns data from the model in the form of a TableModelResult
*
* @param array $filter
* @param array $options
* @return TableModelResult
* @throws DatabaseException
* @see TableModelResult
*/
public function read(array $filter = [], array $options = []): TableModelResult;
/**
* Updates data in the model
*
* @param array $data
* @param array $filter
* @param array $options
* @return int
* @throws DatabaseException
*/
public function update(array $data, array $filter, array $options = []): int;
/**
* Deletes data from the model
*
* @param array $filter
* @param array $options
* @return int
* @throws DatabaseException
*/
public function delete(array $filter, array $options = []): int;
/**
* Starts a transaction in the model when supported
*
* @return bool
*/
public function transactionStart(): bool;
/**
* Ends a transaction in the model when supported
*
* @return bool
*/
public function transactionEnd(): bool;
/**
* Commits changes in the model when supported
*
* @return bool
*/
public function transactionCommit(): bool;
/**
* Rolls back changes in the modle when supported
*
* @return bool
*/
public function transactionRollback(): bool;
}

View File

@ -24,7 +24,7 @@
<tr>
<td>
<?php if (!empty($query['errors'])): ?>
<span title="<?= htmlSpecialChars((isset($query['errors']['message']) ? $query['errors']['message'] : ''), ENT_IGNORE | ENT_QUOTES, 'UTF-8') ?>">ERROR</span>
<span title="<?= htmlSpecialChars(($query['errors']['message'] ?? ''), ENT_IGNORE | ENT_QUOTES, 'UTF-8') ?>">ERROR</span>
<br /><a class="tracy-toggle tracy-collapsed" data-tracy-ref="^tr .nette-DbConnectionPanel-explain">explain</a>
<?php elseif ($query['timings'] !== 0): echo sprintf('%0.3f', $query['timings'] * 1000); endif ?>
</td>
@ -37,7 +37,7 @@
</tr>
<tr>
<td><?= htmlSpecialChars($query['errors']['code'], ENT_NOQUOTES, 'UTF-8') ?></td>
<td><?= htmlSpecialChars((isset($query['errors']['message']) ? $query['errors']['message'] : ''), ENT_NOQUOTES, 'UTF-8') ?></td>
<td><?= htmlSpecialChars(($query['errors']['message'] ?? ''), ENT_NOQUOTES, 'UTF-8') ?></td>
</tr>
</table>
<?php endif ?>

View File

@ -31,7 +31,7 @@
* @link http://techfuze.net/fuzeworks
* @since Version 1.1.4
*
* @version Version 1.1.4
* @version Version 1.3.0
*/
use FuzeWorks\Logger;
@ -41,18 +41,11 @@ chdir(dirname(__DIR__));
// Load the FuzeWorks container
$container = require('bootstrap.php');
Logger::disableHandlers();
// Load the test abstract
require_once 'database/DatabaseTestAbstract.php';
// Reset error and exception handlers
ob_start();
restore_error_handler();
restore_exception_handler();
// Set logger template for output in CLI
Logger::setLoggerTemplate('logger_cli');
// Display all errors
ini_set('display_errors', 1);
error_reporting(E_ALL | E_STRICT);
@ -60,4 +53,5 @@ error_reporting(E_ALL | E_STRICT);
// Set localhost "remote" IP
isset($_SERVER['REMOTE_ADDR']) OR $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
// Set logger template for output in CLI
Logger::setLoggerTemplate('logger_cli');

View File

@ -34,6 +34,9 @@
* @version Version 1.1.4
*/
use FuzeWorks\DatabaseComponent;
use FuzeWorks\TracyComponent;
require_once(dirname(__DIR__) . '/vendor/autoload.php');
$configurator = new FuzeWorks\Configurator();
@ -44,15 +47,12 @@ $configurator->setLogDirectory(dirname(__FILE__) . '/temp');
// Other values
$configurator->setTimeZone('Europe/Amsterdam');
$configurator->enableDebugMode(true)->setDebugAddress('ALL');
$configurator->enableDebugMode()->setDebugAddress('ALL');
// Implement the Layout Component
$configurator->addComponent(new \FuzeWorks\DatabaseComponent());
$configurator->addComponent(new \FuzeWorks\TracyComponent());
$configurator->addComponent(new DatabaseComponent());
$configurator->addComponent(new TracyComponent());
// Create container
$container = $configurator->createContainer();
// And return the result
return $container;
return $configurator->createContainer();

View File

@ -36,7 +36,6 @@
use FuzeWorks\Factory;
use FuzeWorks\Database;
use FuzeWorks\Exception\DatabaseException;
/**
* Class databaseTest.
@ -49,9 +48,9 @@ class DatabaseTest extends DatabaseTestAbstract
/**
* @var Database
*/
protected $database;
protected Database $database;
public function setUp()
public function setUp(): void
{
$this->database = Factory::getInstance()->database;
}

View File

@ -34,6 +34,7 @@
* @version Version 1.1.4
*/
use FuzeWorks\Config;
use PHPUnit\Framework\TestCase;
use FuzeWorks\Events;
use FuzeWorks\Factory;
@ -51,15 +52,21 @@ abstract class DatabaseTestAbstract extends TestCase
*
* Reset the layout manager
*/
public function tearDown()
public function tearDown(): void
{
// Clear all events created by tests
Events::$listeners = array();
Events::$listeners = [];
// Reset all config files
Factory::getInstance()->config->discardConfigFiles();
Factory::getInstance('config')->discardConfigFiles();
// Re-enable events, in case they have been disabled
Events::enable();
// Remove Config overrides
Config::$configOverrides = [];
// Remove autoloader
Core::clearAutoloader();
}
}

View File

@ -1,34 +1,19 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
bootstrap="autoload.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
colors="false">
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="autoload.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" colors="false">
<coverage processUncoveredFiles="false">
<include>
<directory suffix=".php">../</directory>
</include>
<exclude>
<directory suffix=".php">../vendor/</directory>
<directory suffix=".php">../test/</directory>
<directory suffix=".php">../src/Layout/</directory>
<directory suffix=".php">../src/Config/</directory>
</exclude>
</coverage>
<testsuites>
<testsuite name="FuzeWorks Database Suite">
<testsuite name="Core Suite">
<directory>./</directory>
</testsuite>
</testsuites>
<logging>
<log type="json" target="../build/phpunit/logfile.json"/>
<log type="junit" target="../build/phpunit/logfile.xml"/>
<log type="testdox-html" target="../build/phpunit/testdox.html"/>
<log type="testdox-text" target="../build/phpunit/testdox.txt"/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="false">
<directory suffix=".php">../</directory>
<exclude>
<directory suffix=".php">../vendor/</directory>
<directory suffix=".php">../tests/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>