diff --git a/src/FuzeWorks/Database.php b/src/FuzeWorks/Database.php index e89bb04..1dca932 100644 --- a/src/FuzeWorks/Database.php +++ b/src/FuzeWorks/Database.php @@ -185,17 +185,17 @@ class Database /** * @param string $tableName - * @param string $tableModelName * @param string $connectionName + * @param string $engineName * @param array $parameters * @return iDatabaseTableModel * @throws DatabaseException */ - public function getTableModel(string $tableName, string $tableModelName, string $connectionName = 'default', array $parameters = []): iDatabaseTableModel + public function getTableModel(string $tableName, string $connectionName = 'default', string $engineName = '', array $parameters = []): iDatabaseTableModel { try { /** @var DatabaseLoadTableModelEvent $event */ - $event = Events::fireEvent('databaseLoadTableModelEvent', strtolower($tableModelName), $parameters, $connectionName, $tableName); + $event = Events::fireEvent('databaseLoadTableModelEvent', strtolower($engineName), $parameters, $connectionName, $tableName); } catch (EventException $e) { throw new DatabaseException("Could not get TableModel. databaseLoadTableModelEvent threw exception: '" . $e->getMessage() . "'"); } @@ -208,7 +208,7 @@ class Database if (is_object($event->tableModel) && $event->tableModel instanceof iDatabaseTableModel) { $tableModel = $this->tables[$event->connectionName . "|" . $event->tableName] = $event->tableModel; - if (!$tableModel->setup()) + if (!$tableModel->isSetup()) $tableModel->setUp($this->get($event->connectionName, $tableModel->getEngineName(), $event->parameters), $event->tableName); } // If the connection already exists, use that @@ -219,9 +219,13 @@ class Database // Otherwise use the provided configuration else { - $tableModelClass = get_class($this->fetchTableModel($event->tableModelName)); + // 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($this->get($event->connectionName, $tableModel->getEngineName(), $event->parameters), $event->tableName); + $tableModel->setUp($engine, $event->tableName); } // And return the tableModel diff --git a/src/FuzeWorks/DatabaseEngine/DatabaseDriver.php b/src/FuzeWorks/DatabaseEngine/DatabaseDriver.php index 689f5e5..223e468 100644 --- a/src/FuzeWorks/DatabaseEngine/DatabaseDriver.php +++ b/src/FuzeWorks/DatabaseEngine/DatabaseDriver.php @@ -39,7 +39,7 @@ namespace FuzeWorks\DatabaseEngine; /** * Class DatabaseDriver */ -abstract class DatabaseDriver +abstract class DatabaseDriver implements iDatabaseEngine { // --- Query Logging -------------------------------------------------- @@ -82,5 +82,4 @@ abstract class DatabaseDriver // -------------------------------------------------------------------- - } diff --git a/src/FuzeWorks/DatabaseEngine/MongoCommandSubscriber.php b/src/FuzeWorks/DatabaseEngine/MongoCommandSubscriber.php index 92f578d..471210a 100644 --- a/src/FuzeWorks/DatabaseEngine/MongoCommandSubscriber.php +++ b/src/FuzeWorks/DatabaseEngine/MongoCommandSubscriber.php @@ -184,17 +184,12 @@ class MongoCommandSubscriber implements CommandSubscriber $queryData = count($event->getReply()->cursor->firstBatch); break; + case 'update': + case 'delete': case 'insert': $queryData = $event->getReply()->n; break; - case 'update': - $queryData = $event->getReply()->n; - break; - - case 'delete': - $queryData = $event->getReply()->n; - break; } // And log query diff --git a/src/FuzeWorks/DatabaseEngine/MongoEngine.php b/src/FuzeWorks/DatabaseEngine/MongoEngine.php index ea088e1..281d78d 100644 --- a/src/FuzeWorks/DatabaseEngine/MongoEngine.php +++ b/src/FuzeWorks/DatabaseEngine/MongoEngine.php @@ -66,7 +66,7 @@ use MongoDB\Model\DatabaseInfoIterator; * @method Session startSession(array $options = []) * @method ChangeStream watch(array $pipeline = [], array $options = []) */ -class MongoEngine extends DatabaseDriver implements iDatabaseEngine +class MongoEngine extends DatabaseDriver { /** diff --git a/src/FuzeWorks/DatabaseEngine/PDOEngine.php b/src/FuzeWorks/DatabaseEngine/PDOEngine.php index c902af5..932c5d5 100644 --- a/src/FuzeWorks/DatabaseEngine/PDOEngine.php +++ b/src/FuzeWorks/DatabaseEngine/PDOEngine.php @@ -38,6 +38,7 @@ namespace FuzeWorks\DatabaseEngine; use FuzeWorks\Exception\DatabaseException; use FuzeWorks\Exception\TransactionException; use FuzeWorks\Logger; +use FuzeWorks\Model\iDatabaseTableModel; use PDO; use PDOException; use PDOStatement; @@ -52,7 +53,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 { /** @@ -164,7 +165,7 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine // Commit or rollback all changes to the database $this->transactionEnd(); - // + // And close the connection $this->pdoConnection = null; return true; } diff --git a/src/FuzeWorks/DatabaseEngine/PDOStatementWrapper.php b/src/FuzeWorks/DatabaseEngine/PDOStatementWrapper.php index fe1b76a..ecd05da 100644 --- a/src/FuzeWorks/DatabaseEngine/PDOStatementWrapper.php +++ b/src/FuzeWorks/DatabaseEngine/PDOStatementWrapper.php @@ -118,6 +118,16 @@ class PDOStatementWrapper return $result; } + /** + * Retrieves the statement last used + * + * @return PDOStatement + */ + public function getStatement(): PDOStatement + { + return $this->statement; + } + /** * Generates an error message for the last failure in PDO * @@ -146,5 +156,4 @@ class PDOStatementWrapper { return $this->statement->$name; } - } \ No newline at end of file diff --git a/src/FuzeWorks/DatabaseEngine/iDatabaseEngine.php b/src/FuzeWorks/DatabaseEngine/iDatabaseEngine.php index ffe8181..e5be1c3 100644 --- a/src/FuzeWorks/DatabaseEngine/iDatabaseEngine.php +++ b/src/FuzeWorks/DatabaseEngine/iDatabaseEngine.php @@ -36,7 +36,6 @@ namespace FuzeWorks\DatabaseEngine; - interface iDatabaseEngine { public function getName(): string; diff --git a/src/FuzeWorks/Event/DatabaseLoadTableModelEvent.php b/src/FuzeWorks/Event/DatabaseLoadTableModelEvent.php index 0f32831..4aeb595 100644 --- a/src/FuzeWorks/Event/DatabaseLoadTableModelEvent.php +++ b/src/FuzeWorks/Event/DatabaseLoadTableModelEvent.php @@ -56,7 +56,7 @@ class DatabaseLoadTableModelEvent extends Event * * @var string */ - public $tableModelName; + public $engineName; /** * The name of the table this model manages @@ -80,9 +80,9 @@ class DatabaseLoadTableModelEvent extends Event public $connectionName; - public function init(string $tableModelName, array $parameters, string $connectionName, string $tableName) + public function init(string $engineName, array $parameters, string $connectionName, string $tableName) { - $this->tableModelName = $tableModelName; + $this->engineName = $engineName; $this->parameters = $parameters; $this->connectionName = $connectionName; $this->tableName = $tableName; diff --git a/src/FuzeWorks/Model/MongoTableModel.php b/src/FuzeWorks/Model/MongoTableModel.php index c0d2a6a..10b6f62 100644 --- a/src/FuzeWorks/Model/MongoTableModel.php +++ b/src/FuzeWorks/Model/MongoTableModel.php @@ -122,6 +122,18 @@ class MongoTableModel implements iDatabaseTableModel 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 @@ -158,7 +170,7 @@ class MongoTableModel implements iDatabaseTableModel * @return array * @throws DatabaseException */ - public function read(array $filter = [], array $options = [], string $table = 'default'): array + public function read(array $filter = [], array $options = [], string $table = 'default'): TableModelResult { // Select collection if ($table == 'default') diff --git a/src/FuzeWorks/Model/PDOTableModel.php b/src/FuzeWorks/Model/PDOTableModel.php index 14f60ac..bbe2f06 100644 --- a/src/FuzeWorks/Model/PDOTableModel.php +++ b/src/FuzeWorks/Model/PDOTableModel.php @@ -119,30 +119,37 @@ class PDOTableModel implements iDatabaseTableModel 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 - * @param string $table * @return int * @throws DatabaseException */ - public function create(array $data, array $options = [], string $table = 'default'): int + public function create(array $data, array $options = []): 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 {$table} ({$fields}) VALUES ({$values})"; + $sql = "INSERT INTO {$this->tableName} ({$fields}) VALUES ({$values})"; /** @var PDOStatement $statement */ $this->lastStatement = $this->dbEngine->prepare($sql); @@ -159,72 +166,38 @@ class PDOTableModel implements iDatabaseTableModel } /** - * @todo: WRITE ABOUT FETCHMODE - * * @param array $filter * @param array $options - * @param string $table - * @return array + * @return TableModelResult * @throws DatabaseException */ - public function read(array $filter = [], array $options = [], string $table = 'default'): array + public function read(array $filter = [], array $options = []): TableModelResult { - // 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 {$table} {$join} " . $where; + $sql = "SELECT " . $fields . " FROM {$this->tableName} " . $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); - // And return the result - $fetchMode = (isset($options['fetchMode']) ? $options['fetchMode'] : PDO::FETCH_ASSOC); - if (is_array($fetchMode)) - return $this->lastStatement->fetchAll(...$fetchMode); - - return $this->lastStatement->fetchAll($fetchMode); + // Fetch PDO Iterable + return new TableModelResult($this->lastStatement->getStatement()); } - public function update(array $data, array $filter, array $options = [], string $table = 'default'): int + public function update(array $data, array $filter, array $options = []): 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); @@ -235,7 +208,7 @@ class PDOTableModel implements iDatabaseTableModel $fields = implode(', ', $fields); // Generate the sql and create a PDOStatement - $sql = "UPDATE {$table} SET {$fields} {$where}"; + $sql = "UPDATE {$this->tableName} SET {$fields} {$where}"; /** @var PDOStatement $statement */ $this->lastStatement = $this->dbEngine->prepare($sql); @@ -250,17 +223,13 @@ class PDOTableModel implements iDatabaseTableModel return $this->lastStatement->rowCount(); } - public function delete(array $filter, array $options = [], string $table = 'default'): int + public function delete(array $filter, array $options = []): 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 {$table} " . $where; + $sql = "DELETE FROM {$this->tableName} " . $where; /** @var PDOStatement $statement */ $this->lastStatement = $this->dbEngine->prepare($sql); @@ -272,11 +241,6 @@ class PDOTableModel implements iDatabaseTableModel return $this->lastStatement->rowCount(); } - public function getLastStatement(): PDOStatementWrapper - { - return $this->lastStatement; - } - /** * @return bool * @throws TransactionException diff --git a/src/FuzeWorks/Model/TableModelResult.php b/src/FuzeWorks/Model/TableModelResult.php new file mode 100644 index 0000000..412bd1e --- /dev/null +++ b/src/FuzeWorks/Model/TableModelResult.php @@ -0,0 +1,166 @@ +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 Iterator or + * Traversable + * @since 5.0.0 + */ + public function getIterator() + { + return $this->traversable; + } + + private function allToArray(): bool + { + // If the input has already been fetched, ignore it + if ($this->fullyFetched) + return true; + + $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; + + return true; + } + +} \ No newline at end of file diff --git a/src/FuzeWorks/Model/iDatabaseTableModel.php b/src/FuzeWorks/Model/iDatabaseTableModel.php index f92df40..86939dc 100644 --- a/src/FuzeWorks/Model/iDatabaseTableModel.php +++ b/src/FuzeWorks/Model/iDatabaseTableModel.php @@ -35,80 +35,120 @@ */ namespace FuzeWorks\Model; - - use FuzeWorks\DatabaseEngine\iDatabaseEngine; use FuzeWorks\Exception\DatabaseException; interface iDatabaseTableModel { /** + * 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 mixed + */ public function setUp(iDatabaseEngine $engine, string $tableName); + /** + * 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 - * @param string $table * @return int * @throws DatabaseException */ - public function create(array $data, array $options = [], string $table = 'default'): int; + 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 - * @param string $table - * @return array + * @return TableModelResult * @throws DatabaseException + * @see TableModelResult */ - public function read(array $filter = [], array $options = [], string $table = 'default'): array; + public function read(array $filter = [], array $options = []): TableModelResult; /** + * Updates data in the model + * * @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; + public function update(array $data, array $filter, array $options = []): int; /** + * Deletes data from the model + * * @param array $filter * @param array $options - * @param string $table * @return int * @throws DatabaseException */ - public function delete(array $filter, array $options = [], string $table = 'default'): int; + 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;