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
This commit is contained in:
Abel Hoogeveen 2019-07-22 11:43:01 +02:00
parent 67e79ceba3
commit b8ad345365
11 changed files with 978 additions and 33 deletions

View File

@ -36,6 +36,7 @@
namespace FuzeWorks;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\MongoEngine;
use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\Event\DatabaseLoadDriverEvent;
use FuzeWorks\Exception\DatabaseException;
@ -111,13 +112,16 @@ 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.");
@ -229,6 +233,7 @@ class Database
// Load the engines provided by the DatabaseComponent
$this->registerEngine(new PDOEngine());
$this->registerEngine(new MongoEngine());
// And save results
$this->enginesLoaded = true;

View File

@ -0,0 +1,206 @@
<?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 MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use Tracy\Debugger;
class MongoCommandSubscriber implements CommandSubscriber
{
/**
* @var MongoEngine
*/
private $mongoEngine;
/**
* @var float
*/
private $commandTimings = 0.0;
/**
* @var string
*/
private $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)
{
// 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)
{
$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)
{
// 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 'insert':
$queryData = $event->getReply()->n;
break;
case 'update':
$queryData = $event->getReply()->n;
break;
case 'delete':
$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 implements iDatabaseEngine
{
/**
* Whether the Engine has been set up
*
* @var bool
*/
protected $setUp = false;
/**
* @var Client
*/
protected $mongoConnection;
/**
* Connection string with the database
*
* @var string
*/
protected $uri;
/**
* 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 = isset($parameters['uri']) ? $parameters['uri'] : null;
$uriOptions = isset($parameters['uriOptions']) ? $parameters['uriOptions'] : [];
$driverOptions = isset($parameters['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
{
// TODO: Implement transactionStart() method.
}
/**
* @return bool
*/
public function transactionEnd(): bool
{
// TODO: Implement transactionEnd() method.
}
/**
* @return bool
*/
public function transactionCommit(): bool
{
// TODO: Implement transactionCommit() method.
}
/**
* @return bool
*/
public function transactionRollback(): bool
{
// TODO: Implement transactionRollback() method.
}
}

View File

@ -36,6 +36,7 @@
namespace FuzeWorks\DatabaseEngine;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Exception\TransactionException;
use FuzeWorks\Logger;
use PDO;
use PDOException;
@ -156,6 +157,7 @@ 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
{
@ -209,8 +211,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 +239,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 +265,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 +293,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 +311,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 +337,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

@ -54,10 +54,16 @@ class PDOStatementWrapper
*/
private $logQueryCallable;
public function __construct(PDOStatement $statement, callable $logQueryCallable)
/**
* @var PDOEngine
*/
private $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 = [])
@ -72,6 +78,8 @@ 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']);
}

View File

@ -44,4 +44,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

@ -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,260 @@
<?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\MongoEngine;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Factory;
use MongoDB\Collection;
class MongoTableModel implements iDatabaseTableModel
{
/**
* Holds the FuzeWorks Database loader
*
* @var Database
*/
private $databases;
/**
* Holds the PDOEngine for this model
*
* @var MongoEngine
*/
protected $dbEngine;
/**
* Holds the collection that is being modified
*
* @var Collection
*/
protected $collection;
/**
* Initializes the model to connect with the database.
*
* @param string $connectionName
* @param array $parameters
* @param string $tableName
* @throws DatabaseException
* @see MongoEngine::setUp()
*/
public function __construct(string $connectionName = 'default', array $parameters = [], string $tableName = 'default.default')
{
if (is_null($this->databases))
$this->databases = Factory::getInstance()->databases;
// Load databaseEngine
$this->dbEngine = $this->databases->get($connectionName, 'mongo', $parameters);
// Determine the collection
$this->collection = $this->getCollection($tableName);
}
/**
* @param string $collectionString
* @return Collection
* @throws DatabaseException
*/
protected function getCollection(string $collectionString)
{
// 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';
}
/**
* @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 array
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = [], string $table = 'default'): array
{
// Select collection
if ($table == 'default')
$collection = $this->collection;
else
$collection = $this->getCollection($table);
// Execute the request
$results = $collection->find($filter, $options);
// Return the result
$return = [];
foreach ($results->toArray() as $result)
$return[] = iterator_to_array($result);
return $return;
}
/**
* @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
{
// TODO: Implement transactionStart() method.
}
/**
* @return bool
*/
public function transactionEnd(): bool
{
// TODO: Implement transactionEnd() method.
}
/**
* @return bool
*/
public function transactionCommit(): bool
{
// TODO: Implement transactionCommit() method.
}
/**
* @return bool
*/
public function transactionRollback(): bool
{
// TODO: Implement transactionRollback() method.
}
/**
* 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

@ -41,6 +41,7 @@ use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\DatabaseEngine\PDOStatementWrapper;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Exception\EventException;
use FuzeWorks\Exception\TransactionException;
use FuzeWorks\Factory;
use PDO;
use PDOStatement;
@ -53,10 +54,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)
@ -100,10 +97,9 @@ class PDOTableModel implements iDatabaseTableModel
* @param array $parameters
* @param string|null $tableName
* @throws DatabaseException
* @throws EventException
* @see PDOEngine::setUp()
*/
public function __construct(string $connectionName = 'default', array $parameters = [], string $tableName = null)
public function __construct(string $connectionName = 'default', array $parameters = [], string $tableName = 'default')
{
if (is_null($this->databases))
$this->databases = Factory::getInstance()->databases;
@ -112,19 +108,35 @@ class PDOTableModel implements iDatabaseTableModel
$this->tableName = $tableName;
}
public function create(array $data, array $options = []): bool
public function getName(): string
{
return 'pdo';
}
/**
* @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 no data is provided, stop now
if (empty($data))
throw new DatabaseException("Could not create data. No data provided.");
// Select table
if ($table == 'default')
$table = $this->tableName;
// Determine which fields will be inserted
$fieldsArr = $this->createFields($data);
$fields = $fieldsArr['fields'];
$values = $fieldsArr['values'];
// Generate the sql and create a PDOStatement
$sql = "INSERT INTO {$this->tableName} ({$fields}) VALUES ({$values})";
$sql = "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
/** @var PDOStatement $statement */
$this->lastStatement = $this->dbEngine->prepare($sql);
@ -137,7 +149,7 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($record);
// And return true for success
return true;
return $this->lastStatement->rowCount();
}
/**
@ -145,23 +157,47 @@ class PDOTableModel implements iDatabaseTableModel
*
* @param array $filter
* @param array $options
* @param string $table
* @return array
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = []): array
public function read(array $filter = [], array $options = [], string $table = 'default'): array
{
// Select table
if ($table == 'default')
$table = $this->tableName;
// Determine which fields to select. If none provided, select all
$fields = (isset($options['fields']) && is_array($options['fields']) ? implode(',', $options['fields']) : '*');
// Apply the filter. If none provided, don't condition it
$where = $this->filter($filter);
// If a JOIN is provided, create the statement
if (isset($options['join']))
{
$joinType = (isset($options['join']['joinType']) ? strtoupper($options['join']['joinType']) : 'LEFT');
$targetTable = (isset($options['join']['targetTable']) ? $options['join']['targetTable'] : null);
$targetField = (isset($options['join']['targetField']) ? $options['join']['targetField'] : null);
$sourceField = (isset($options['join']['sourceField']) ? $options['join']['sourceField'] : null);
if (is_null($targetTable) || is_null($targetField) || is_null($sourceField))
throw new DatabaseException("Could not read from '" . $table . "'. Missing fields in join options.");
$join = "{$joinType} JOIN {$targetTable} ON {$table}.{$sourceField} = {$targetTable}.{$targetField}";
}
else
$join = '';
// Generate the sql and create a PDOStatement
$sql = "SELECT " . $fields . " FROM {$this->tableName} " . $where;
$sql = "SELECT " . $fields . " FROM {$table} {$join} " . $where;
/** @var PDOStatement $statement */
$this->lastStatement = $this->dbEngine->prepare($sql);
// Return prepared statement, if requested to do so
if (isset($options['returnPreparedStatement']) && $options['returnPreparedStatement'] == true)
return [];
// And execute the query
$this->lastStatement->execute($filter);
@ -173,12 +209,16 @@ class PDOTableModel implements iDatabaseTableModel
return $this->lastStatement->fetchAll($fetchMode);
}
public function update(array $data, array $filter, array $options = []): bool
public function update(array $data, array $filter, array $options = [], string $table = 'default'): int
{
// If no data is provided, stop now
if (empty($data))
throw new DatabaseException("Could not update data. No data provided.");
// Select table
if ($table == 'default')
$table = $this->tableName;
// Apply the filter
$where = $this->filter($filter);
@ -189,7 +229,7 @@ class PDOTableModel implements iDatabaseTableModel
$fields = implode(', ', $fields);
// Generate the sql and create a PDOStatement
$sql = "UPDATE {$this->tableName} SET {$fields} {$where}";
$sql = "UPDATE {$table} SET {$fields} {$where}";
/** @var PDOStatement $statement */
$this->lastStatement = $this->dbEngine->prepare($sql);
@ -201,16 +241,20 @@ 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 = [], string $table = 'default'): int
{
// Select table
if ($table == 'default')
$table = $this->tableName;
// Apply the filter
$where = $this->filter($filter);
// Generate the sql and create a PDOStatement
$sql = "DELETE FROM {$this->tableName} " . $where;
$sql = "DELETE FROM {$table} " . $where;
/** @var PDOStatement $statement */
$this->lastStatement = $this->dbEngine->prepare($sql);
@ -219,7 +263,7 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($filter);
// And return true for success
return true;
return $this->lastStatement->rowCount();
}
public function getLastStatement(): PDOStatementWrapper
@ -227,6 +271,42 @@ class PDOTableModel implements iDatabaseTableModel
return $this->lastStatement;
}
/**
* @return bool
* @throws TransactionException
*/
public function transactionStart(): bool
{
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();
}
/**
* Call methods on the PDO Engine, which calls methods on the PDO Connection
*

View File

@ -37,10 +37,69 @@
namespace FuzeWorks\Model;
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;
/**
* @return string
*/
public function getName(): string;
/**
* @param array $data
* @param array $options
* @param string $table
* @return int
* @throws DatabaseException
*/
public function create(array $data, array $options = [], string $table = 'default'): int;
/**
* @param array $filter
* @param array $options
* @param string $table
* @return array
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = [], string $table = 'default'): array;
/**
* @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;
/**
* @param array $filter
* @param array $options
* @param string $table
* @return int
* @throws DatabaseException
*/
public function delete(array $filter, array $options = [], string $table = 'default'): int;
/**
* @return bool
*/
public function transactionStart(): bool;
/**
* @return bool
*/
public function transactionEnd(): bool;
/**
* @return bool
*/
public function transactionCommit(): bool;
/**
* @return bool
*/
public function transactionRollback(): bool;
}