From 6f14145bca383b6eb564db889fd22ce664ab331c Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 27 Apr 2016 17:41:57 -0500 Subject: [PATCH 1/4] Prepared documentation for publication - Renamed files to remove `zend.db.` prefixes - Edited all files for content and formating. - Converted from bookdown to mkdocs. --- README.md | 3 +- doc/book/adapter.md | 459 +++++++++++ doc/book/index.html | 10 + doc/book/index.md | 1 + doc/book/{zend.db.metadata.md => metadata.md} | 79 +- doc/book/result-set.md | 157 ++++ doc/book/row-gateway.md | 98 +++ doc/book/{zend.db.sql.ddl.md => sql-ddl.md} | 94 +-- doc/book/sql.md | 715 ++++++++++++++++++ doc/book/table-gateway.md | 217 ++++++ doc/book/zend.db.adapter.md | 433 ----------- doc/book/zend.db.result-set.md | 142 ---- doc/book/zend.db.row-gateway.md | 89 --- doc/book/zend.db.sql.md | 665 ---------------- doc/book/zend.db.table-gateway.md | 186 ----- doc/bookdown.json | 13 - mkdocs.yml | 16 + 17 files changed, 1765 insertions(+), 1612 deletions(-) create mode 100644 doc/book/adapter.md create mode 100644 doc/book/index.html create mode 120000 doc/book/index.md rename doc/book/{zend.db.metadata.md => metadata.md} (71%) create mode 100644 doc/book/result-set.md create mode 100644 doc/book/row-gateway.md rename doc/book/{zend.db.sql.ddl.md => sql-ddl.md} (58%) create mode 100644 doc/book/sql.md create mode 100644 doc/book/table-gateway.md delete mode 100644 doc/book/zend.db.adapter.md delete mode 100644 doc/book/zend.db.result-set.md delete mode 100644 doc/book/zend.db.row-gateway.md delete mode 100644 doc/book/zend.db.sql.md delete mode 100644 doc/book/zend.db.table-gateway.md delete mode 100644 doc/bookdown.json create mode 100644 mkdocs.yml diff --git a/README.md b/README.md index e7116491c1..f2753c8201 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,5 @@ oriented API to build the queries. `Zend\Db` consumes different storage adapters to access different database vendors such as MySQL, PostgreSQL, Oracle, IBM DB2, Microsoft Sql Server, PDO, etc. - - File issues at https://github.com/zendframework/zend-db/issues -- Documentation is at http://framework.zend.com/manual/current/en/index.html#zend-db +- Documentation is at https://zendframework.github.io/zend-db/ diff --git a/doc/book/adapter.md b/doc/book/adapter.md new file mode 100644 index 0000000000..bec98b63d2 --- /dev/null +++ b/doc/book/adapter.md @@ -0,0 +1,459 @@ +# Adapters + +`Zend\Db\Adapter\Adapter` is the central object of the zend-db component. It is +responsible for adapting any code written in or for zend-db to the targeted PHP +extensions and vendor databases. In doing this, it creates an abstraction layer +for the PHP extensions in the `Driver` subnamespace of `Zend\Db\Adapter`. It +also creates a lightweight "Platform" abstraction layer, for the various +idiosyncrasies that each vendor-specific platform might have in its SQL/RDBMS +implementation, separate from the driver implementations. + +## Creating an adapter using configuration + +Create an adapter by instantiating the `Zend\Db\Adapter\Adapter` class. The most +common use case, while not the most explicit, is to pass an array of +configuration to the `Adapter`: + +```php +use Zend\Db\Adapter\Adapter; + +$adapter = new Adapter($configArray); +``` + +This driver array is an abstraction for the extension level required parameters. +Here is a table for the key-value pairs that should be in configuration array. + +Key | Is Required? | Value +---------- | ---------------------- | ----- +`driver` | required | `Mysqli`, `Sqlsrv`, `Pdo_Sqlite`, `Pdo_Mysql`, `Pdo`(= Other PDO Driver) +`database` | generally required | the name of the database (schema) +`username` | generally required | the connection username +`password` | generally required | the connection password +`hostname` | not generally required | the IP address or hostname to connect to +`port` | not generally required | the port to connect to (if applicable) +`charset` | not generally required | the character set to use + +> ### Options are adapter-dependent +> +> Other names will work as well. Effectively, if the PHP manual uses a +> particular naming, this naming will be supported by the associated driver. For +> example, `dbname` in most cases will also work for 'database'. Another +> example is that in the case of `Sqlsrv`, `UID` will work in place of +> `username`. Which format you choose is up to you, but the above table +> represents the official abstraction names. + +For example, a MySQL connection using ext/mysqli: + +```php +$adapter = new Zend\Db\Adapter\Adapter([ + 'driver' => 'Mysqli', + 'database' => 'zend_db_example', + 'username' => 'developer', + 'password' => 'developer-password', +]); +``` + +Another example, of a Sqlite connection via PDO: + +```php +$adapter = new Zend\Db\Adapter\Adapter([ + 'driver' => 'Pdo_Sqlite', + 'database' => 'path/to/sqlite.db', +]); +``` + +It is important to know that by using this style of adapter creation, the +`Adapter` will attempt to create any dependencies that were not explicitly +provided. A `Driver` object will be created from the configuration array +provided in the constructor. A `Platform` object will be created based off the +type of `Driver` class that was instantiated. And lastly, a default `ResultSet` +object is created and utilized. Any of these objects can be injected, to do +this, see the next section. + +The list of officially supported drivers: + +- `IbmDb2`: The ext/ibm_db2 deriver +- `Mysqli`: The ext/mysqli driver +- `Oci8`: The ext/oci8 driver +- `Pgsql`: The ext/pgsql driver +- `Sqlsrv`: The ext/sqlsrv driver (from Microsoft) +- `Pdo_Mysql`: MySQL via the PDO extension +- `Pdo_Sqlite`: SQLite via the PDO extension +- `Pdo_Pgsql`: PostgreSQL via the PDO extension + +## Creating an adapter using dependency injection + +The more expressive and explicit way of creating an adapter is by injecting all +your dependencies up front. `Zend\Db\Adapter\Adapter` uses constructor +injection, and all required dependencies are injected through the constructor, +which has the following signature (in pseudo-code): + +```php +use Zend\Db\Adapter\Platform\PlatformInterface; +use Zend\Db\ResultSet\ResultSet; + +class Zend\Db\Adapter\Adapter +{ + public function __construct( + $driver, + PlatformInterface $platform = null, + ResultSet $queryResultSetPrototype = null + ); +} +``` + +What can be injected: + +- `$driver`: an array of connection parameters (see above) or an instance of + `Zend\Db\Adapter\Driver\DriverInterface`. +- `$platform` (optional): an instance of `Zend\Db\Platform\PlatformInterface`; + the default will be created based off the driver implementation. +- `$queryResultSetPrototype` (optional): an instance of + `Zend\Db\ResultSet\ResultSet`; to understand this object's role, see the + section below on querying. + +## Query Preparation + +By default, `Zend\Db\Adapter\Adapter::query()` prefers that you use +"preparation" as a means for processing SQL statements. This generally means +that you will supply a SQL statement containing placeholders for the values, and +separately provide substitutions for those placeholders. As an example: + +```php +$adapter->query('SELECT * FROM `artist` WHERE `id` = ?', [5]); +``` + +The above example will go through the following steps: + +- create a new `Statement` object. +- prepare the array `[5]` into a `ParameterContainer` if necessary. +- inject the `ParameterContainer` into the `Statement` object. +- execute the `Statement` object, producing a `Result` object. +- check the `Result` object to check if the supplied SQL was a result set + producing statement: + - if the query produced a result set, clone the `ResultSet` prototype, + inject the `Result` as its datasource, and return the new `ResultSet` + instance. + - otherwise, return the `Result`. + +## Query Execution + +In some cases, you have to execute statements directly without preparation. One +possible reason for doing so would be to execute a DDL statement, as most +extensions and RDBMS systems are incapable of preparing such statements. + +To execute a query without the preparation step, you will need to pass a flag as +the second argument indicating execution is required: + +```php +$adapter->query( + 'ALTER TABLE ADD INDEX(`foo_index`) ON (`foo_column`)', + Adapter::QUERY_MODE_EXECUTE +); +``` + +The primary difference to notice is that you must provide the +`Adapter::QUERY_MODE_EXECUTE` (execute) flag as the second parameter. + +## Creating Statements + +While `query()` is highly useful for one-off and quick querying of a database +via the `Adapter`, it generally makes more sense to create a statement and +interact with it directly, so that you have greater control over the +prepare-then-execute workflow. To do this, `Adapter` gives you a routine called +`createStatement()` that allows you to create a `Driver` specific `Statement` to +use so you can manage your own prepare-then-execute workflow. + +```php +// with optional parameters to bind up-front: +$statement = $adapter->createStatement($sql, $optionalParameters); +$result = $statement->execute(); +``` + +## Using the Driver Object + +The `Driver` object is the primary place where `Zend\Db\Adapter\Adapter` +implements the connection level abstraction specific to a given extension. To +make this possible, each driver is composed of 3 objects: + +- A connection: `Zend\Db\Adapter\Driver\ConnectionInterface` +- A statement: `Zend\Db\Adapter\Driver\StatementInterface` +- A result: `Zend\Db\Adapter\Driver\ResultInterface` + +Each of the built-in drivers practice "prototyping" as a means of creating +objects when new instances are requested. The workflow looks like this: + +- An adapter is created with a set of connection parameters. +- The adapter chooses the proper driver to instantiate (for example, + `Zend\Db\Adapter\Driver\Mysqli`) +- That driver class is instantiated. +- If no connection, statement, or result objects are injected, defaults are + instantiated. + +This driver is now ready to be called on when particular workflows are +requested. Here is what the `Driver` API looks like: + +```php +namespace Zend\Db\Adapter\Driver; + +interface DriverInterface +{ + const PARAMETERIZATION_POSITIONAL = 'positional'; + const PARAMETERIZATION_NAMED = 'named'; + const NAME_FORMAT_CAMELCASE = 'camelCase'; + const NAME_FORMAT_NATURAL = 'natural'; + + public function getDatabasePlatformName(string $nameFormat = self::NAME_FORMAT_CAMELCASE) : string; + public function checkEnvironment() : bool; + public function getConnection() : ConnectionInterface; + public function createStatement(string|resource $sqlOrResource = null) : StatementInterface; + public function createResult(resource $resource) : ResultInterface; + public function getPrepareType() :string; + public function formatParameterName(string $name, $type = null) : string; + public function getLastGeneratedValue() : mixed; +} +``` + +From this `DriverInterface`, you can + +- Determine the name of the platform this driver supports (useful for choosing + the proper platform object). +- Check that the environment can support this driver. +- Return the `Connection` instance. +- Create a `Statement` instance which is optionally seeded by an SQL statement + (this will generally be a clone of a prototypical statement object). +- Create a `Result` object which is optionally seeded by a statement resource + (this will generally be a clone of a prototypical result object) +- Format parameter names; this is important to distinguish the difference + between the various ways parameters are named between extensions +- Retrieve the overall last generated value (such as an auto-increment value). + +Now let's turn to the `Statement` API: + +```php +namespace Zend\Db\Adapter\Driver; + +interface StatementInterface extends StatementContainerInterface +{ + public function getResource() : resource; + public function prepare($sql = null) : void; + public function isPrepared() : bool; + public function execute(null|array|ParameterContainer $parameters = null) : ResultInterface; + + /** Inherited from StatementContainerInterface */ + public function setSql(string $sql) : void; + public function getSql() : string; + public function setParameterContainer(ParameterContainer $parameterContainer) : void; + public function getParameterContainer() : ParameterContainer; +} +``` + +And finally, the `Result` API: + +```php +namespace Zend\Db\Adapter\Driver; + +use Countable; +use Iterator; + +interface ResultInterface extends Countable, Iterator +{ + public function buffer() : void; + public function isQueryResult() : bool; + public function getAffectedRows() : int; + public function getGeneratedValue() : mixed; + public function getResource() : resource; + public function getFieldCount() : int; +} +``` + +## Using The Platform Object + +The `Platform` object provides an API to assist in crafting queries in a way +that is specific to the SQL implementation of a particular vendor. The object +handles nuances such as how identifiers or values are quoted, or what the +identifier separator character is. To get an idea of the capabilities, the +interface for a platform object looks like this: + +```php +namespace Zend\Db\Adapter\Platform; + +interface PlatformInterface +{ + public function getName() : string; + public function getQuoteIdentifierSymbol() : string; + public function quoteIdentifier(string $identifier) : string; + public function quoteIdentifierChain(string|string[] $identiferChain) : string; + public function getQuoteValueSymbol() : string; + public function quoteValue(string $value) : string; + public function quoteTrustedValue(string $value) : string; + public function quoteValueList(string|string[] $valueList) : string; + public function getIdentifierSeparator() : string; + public function quoteIdentifierInFragment(string $identifier, array $additionalSafeWords = []) : string; +} +``` + +While you can directly instantiate a `Platform` object, generally speaking, it +is easier to get the proper `Platform` instance from the configured adapter (by +default the `Platform` type will match the underlying driver implementation): + +```php +$platform = $adapter->getPlatform(); + +// or +$platform = $adapter->platform; // magic property access +``` + +The following are examples of `Platform` usage: + +```php +// $adapter is a Zend\Db\Adapter\Adapter instance; +// $platform is a Zend\Db\Adapter\Platform\Sql92 instance. +$platform = $adapter->getPlatform(); + +// "first_name" +echo $platform->quoteIdentifier('first_name'); + +// " +echo $platform->getQuoteIdentifierSymbol(); + +// "schema"."mytable" +echo $platform->quoteIdentifierChain(['schema', 'mytable']); + +// ' +echo $platform->getQuoteValueSymbol(); + +// 'myvalue' +echo $platform->quoteValue('myvalue'); + +// 'value', 'Foo O\\'Bar' +echo $platform->quoteValueList(['value', "Foo O'Bar"]); + +// . +echo $platform->getIdentifierSeparator(); + +// "foo" as "bar" +echo $platform->quoteIdentifierInFragment('foo as bar'); + +// additionally, with some safe words: +// ("foo"."bar" = "boo"."baz") +echo $platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']); +``` + +## Using The Parameter Container + +The `ParameterContainer` object is a container for the various parameters that +need to be passed into a `Statement` object to fulfill all the various +parameterized parts of the SQL statement. This object implements the +`ArrayAccess` interface. Below is the `ParameterContainer` API: + +```php +namespace Zend\Db\Adapter; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use Iterator; + +class ParameterContainer implements Iterator, ArrayAccess, Countable +{ + public function __construct(array $data = []) + + /** methods to interact with values */ + public function offsetExists(string|int $name) : bool; + public function offsetGet(string|int $name) : mixed; + public function offsetSetReference(string|int $name, string|int $from) : void; + public function offsetSet(string|int $name, mixed $value, mixed $errata = null, int $maxLength = null) : void; + public function offsetUnset(string|int $name) : void; + + /** set values from array (will reset first) */ + public function setFromArray(array $data) : ParameterContainer; + + /** methods to interact with value errata */ + public function offsetSetErrata(string|int $name, mixed $errata) : void; + public function offsetGetErrata(string|int $name) : mixed; + public function offsetHasErrata(string|int $name) : bool; + public function offsetUnsetErrata(string|int $name) : void; + + /** errata only iterator */ + public function getErrataIterator() : ArrayIterator; + + /** get array with named keys */ + public function getNamedArray() : array; + + /** get array with int keys, ordered by position */ + public function getPositionalArray() : array; + + /** iterator: */ + public function count() : int; + public function current() : mixed; + public function next() : mixed; + public function key() : string|int; + public function valid() : bool; + public function rewind() : void; + + /** merge existing array of parameters with existing parameters */ + public function merge(array $parameters) : ParameterContainer; +} +``` + +In addition to handling parameter names and values, the container will assist in +tracking parameter types for PHP type to SQL type handling. For example, it +might be important that: + +```php +$container->offsetSet('limit', 5); +``` + +be bound as an integer. To achieve this, pass in the +`ParameterContainer::TYPE_INTEGER` constant as the 3rd parameter: + +```php +$container->offsetSet('limit', 5, $container::TYPE_INTEGER); +``` + +This will ensure that if the underlying driver supports typing of bound +parameters, that this translated information will also be passed along to the +actual php database driver. + +## Examples + +Creating a `Driver`, a vendor-portable query, and preparing and iterating the +result: + +```php +$adapter = new Zend\Db\Adapter\Adapter($driverConfig); + +$qi = function ($name) use ($adapter) { + return $adapter->platform->quoteIdentifier($name); +}; +$fp = function ($name) use ($adapter) { + return $adapter->driver->formatParameterName($name); +}; + +$sql = 'UPDATE ' . $qi('artist') + . ' SET ' . $qi('name') . ' = ' . $fp('name') + . ' WHERE ' . $qi('id') . ' = ' . $fp('id'); + +$statement = $adapter->query($sql); + +$parameters = [ + 'name' => 'Updated Artist', + 'id' => 1, +]; + +$statement->execute($parameters); + +// DATA INSERTED, NOW CHECK + +$statement = $adapter->query( + 'SELECT * FROM ' + . $qi('artist') + . ' WHERE id = ' . $fp('id') +); + +$results = $statement->execute(['id' => 1]); + +$row = $results->current(); +$name = $row['name']; +``` diff --git a/doc/book/index.html b/doc/book/index.html new file mode 100644 index 0000000000..b2ef8a4e93 --- /dev/null +++ b/doc/book/index.html @@ -0,0 +1,10 @@ +
+
+

zend-db

+ +

Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations.

+ +
$ composer require zendframework/zend-stdlib
+
+
+ diff --git a/doc/book/index.md b/doc/book/index.md new file mode 120000 index 0000000000..fe84005413 --- /dev/null +++ b/doc/book/index.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/doc/book/zend.db.metadata.md b/doc/book/metadata.md similarity index 71% rename from doc/book/zend.db.metadata.md rename to doc/book/metadata.md index 5f07983003..e1f5b31cba 100644 --- a/doc/book/zend.db.metadata.md +++ b/doc/book/metadata.md @@ -1,47 +1,50 @@ -# Zend\\Db\\Metadata +# RDBMS Metadata -`Zend\Db\Metadata` is as sub-component of Zend\\Db that makes it possible to get metadata -information about tables, columns, constraints, triggers, and other information from a database in a -standardized way. The primary interface for the Metadata objects is: +`Zend\Db\Metadata` is as sub-component of zend-db that makes it possible to get +metadata information about tables, columns, constraints, triggers, and other +information from a database in a standardized way. The primary interface for +`Metadata` is: ```php +namespace Zend\Db\Metadata; + interface MetadataInterface { public function getSchemas(); - public function getTableNames($schema = null, $includeViews = false); - public function getTables($schema = null, $includeViews = false); - public function getTable($tableName, $schema = null); + public function getTableNames(string $schema = null, bool $includeViews = false) : string[]; + public function getTables(string $schema = null, bool $includeViews = false) : Object\TableObject[]; + public function getTable(string $tableName, string $schema = null) : Object\TableObject; - public function getViewNames($schema = null); - public function getViews($schema = null); - public function getView($viewName, $schema = null); + public function getViewNames(string $schema = null) : string[]; + public function getViews(string $schema = null) : Object\ViewObject[]; + public function getView(string $viewName, string $schema = null) : Object\ViewObject; - public function getColumnNames($table, $schema = null); - public function getColumns($table, $schema = null); - public function getColumn($columnName, $table, $schema = null); + public function getColumnNames(string string $table, $schema = null) : string[]; + public function getColumns(string $table, string $schema = null) : Object\ColumnObject[]; + public function getColumn(string $columnName, string $table, string $schema = null) Object\ColumnObject; - public function getConstraints($table, $schema = null); - public function getConstraint($constraintName, $table, $schema = null); - public function getConstraintKeys($constraint, $table, $schema = null); + public function getConstraints(string $table, $string schema = null) : Object\ConstraintObject[]; + public function getConstraint(string $constraintName, string $table, string $schema = null) : Object\ConstraintObject; + public function getConstraintKeys(string $constraint, string $table, string $schema = null) : Object\ConstraintKeyObject[]; - public function getTriggerNames($schema = null); - public function getTriggers($schema = null); - public function getTrigger($triggerName, $schema = null); + public function getTriggerNames(string $schema = null) : string[]; + public function getTriggers(string $schema = null) : Object\TriggerObject[]; + public function getTrigger(string $triggerName, string $schema = null) : Object\TriggerObject; } ``` ## Basic Usage -Usage of `Zend\Db\Metadata` is very straight forward. The top level class -Zend\\Db\\Metadata\\Metadata will, given an adapter, choose the best strategy (based on the database -platform being used) for retrieving metadata. In most cases, information will come from querying the -INFORMATION\_SCHEMA tables generally accessible to all database connections about the currently -accessible schema. +Usage of `Zend\Db\Metadata` involves: + +- Constructing a `Zend\Db\Metadata\Metadata` instance with an `Adapter`. +- Choosing a strategy for retrieving metadata, based on the database platform + used. In most cases, information will come from querying the + `INFORMATION_SCHEMA` tables for the currently accessible schema. -Metadata::get\*Names() methods will return an array of strings, while the other methods will return -specific value objects with the containing information. This is best demonstrated by the script -below. +The `Metadata::get*Names()` methods will return arrays of strings, while the +other methods will return value objects specific to the type queried. ```php $metadata = new Zend\Db\Metadata\Metadata($adapter); @@ -54,7 +57,6 @@ foreach ($tableNames as $tableName) { $table = $metadata->getTable($tableName); - echo ' With columns: ' . PHP_EOL; foreach ($table->getColumns() as $column) { echo ' ' . $column->getName() @@ -66,13 +68,14 @@ foreach ($tableNames as $tableName) { echo ' With constraints: ' . PHP_EOL; foreach ($metadata->getConstraints($tableName) as $constraint) { - /** @var $constraint Zend\Db\Metadata\Object\ConstraintObject */ echo ' ' . $constraint->getName() . ' -> ' . $constraint->getType() . PHP_EOL; - if (!$constraint->hasColumns()) { + + if (! $constraint->hasColumns()) { continue; } + echo ' column: ' . implode(', ', $constraint->getColumns()); if ($constraint->isForeignKey()) { $fkCols = []; @@ -81,18 +84,20 @@ foreach ($tableNames as $tableName) { } echo ' => ' . implode(', ', $fkCols); } - echo PHP_EOL; + echo PHP_EOL; } echo '----' . PHP_EOL; } ``` -Metadata returns value objects that provide an interface to help developers better explore the -metadata. Below is the API for the various value objects: +## Metadata value objects + +Metadata returns value objects that provide an interface to help developers +better explore the metadata. Below is the API for the various value objects: -The TableObject: +### TableObject ```php class Zend\Db\Metadata\Object\TableObject @@ -107,7 +112,7 @@ class Zend\Db\Metadata\Object\TableObject } ``` -The ColumnObject: +### ColumnObject ```php class Zend\Db\Metadata\Object\ColumnObject @@ -146,7 +151,7 @@ class Zend\Db\Metadata\Object\ColumnObject } ``` -The ConstraintObject: +### ConstraintObject ```php class Zend\Db\Metadata\Object\ConstraintObject @@ -185,7 +190,7 @@ class Zend\Db\Metadata\Object\ConstraintObject } ``` -The TriggerObject: +### TriggerObject ```php class Zend\Db\Metadata\Object\TriggerObject diff --git a/doc/book/result-set.md b/doc/book/result-set.md new file mode 100644 index 0000000000..18bca80804 --- /dev/null +++ b/doc/book/result-set.md @@ -0,0 +1,157 @@ +# Result Sets + +`Zend\Db\ResultSet` is a sub-component of zend-db for abstracting the iteration +of results returned from queries producing rowsets. While data sources for this +can be anything that is iterable, generally these will be populated from +`Zend\Db\Adapter\Driver\ResultInterface` instances. + +Result sets must implement the `Zend\Db\ResultSet\ResultSetInterface`, and all +sub-components of zend-db that return a result set as part of their API will +assume an instance of a `ResultSetInterface` should be returned. In most casts, +the prototype pattern will be used by consuming object to clone a prototype of +a `ResultSet` and return a specialized `ResultSet` with a specific data source +injected. `ResultSetInterface` is defined as follows: + +```php +use Countable; +use Traversable; + +interface ResultSetInterface extends Traversable, Countable +{ + public function initialize(mixed $dataSource) : void; + public function getFieldCount() : int; +} +``` + +## Quick start + +`Zend\Db\ResultSet\ResultSet` is the most basic form of a `ResultSet` object +that will expose each row as either an `ArrayObject`-like object or an array of +row data. By default, `Zend\Db\Adapter\Adapter` will use a prototypical +`Zend\Db\ResultSet\ResultSet` object for iterating when using the +`Zend\Db\Adapter\Adapter::query()` method. + +The following is an example workflow similar to what one might find inside +`Zend\Db\Adapter\Adapter::query()`: + +```php +use Zend\Db\Adapter\Driver\ResultInterface; +use Zend\Db\ResultSet\ResultSet; + +$statement = $driver->createStatement('SELECT * FROM users'); +$statement->prepare(); +$result = $statement->execute($parameters); + +if ($result instanceof ResultInterface && $result->isQueryResult()) { + $resultSet = new ResultSet; + $resultSet->initialize($result); + + foreach ($resultSet as $row) { + echo $row->my_column . PHP_EOL; + } +} +``` + +## Zend\\Db\\ResultSet\\ResultSet and Zend\\Db\\ResultSet\\AbstractResultSet + +For most purposes, either an instance of `Zend\Db\ResultSet\ResultSet` or a +derivative of `Zend\Db\ResultSet\AbstractResultSet` will be used. The +implementation of the `AbstractResultSet` offers the following core +functionality: + +```php +namespace Zend\Db\ResultSet; + +use Iterator; + +abstract class AbstractResultSet implements Iterator, ResultSetInterface +{ + public function initialize(array|Iterator|IteratorAggregate|ResultInterface $dataSource) : self; + public function getDataSource() : Iterator|IteratorAggregate|ResultInterface; + public function getFieldCount() : int; + + /** Iterator */ + public function next() : mixed; + public function key() : string|int; + public function current() : mixed; + public function valid() : bool; + public function rewind() : void; + + /** countable */ + public function count() : int; + + /** get rows as array */ + public function toArray() : array; +} +``` + +## Zend\\Db\\ResultSet\\HydratingResultSet + +`Zend\Db\ResultSet\HydratingResultSet` is a more flexible `ResultSet` object +that allows the developer to choose an appropriate "hydration strategy" for +getting row data into a target object. While iterating over results, +`HydratingResultSet` will take a prototype of a target object and clone it once +for each row. The `HydratingResultSet` will then hydrate that clone with the +row data. + +The `HydratingResultSet` depends on +[zend-hydrator](https://zendframework.github.io/zend-hydrator), which you will +need to install: + +```bash +$ composer require zendframework/zend-hydrator +``` + +In the example below, rows from the database will be iterated, and during +iteration, `HydratingResultSet` will use the `Reflection` based hydrator to +inject the row data directly into the protected members of the cloned +`UserEntity` object: + +```php +use Zend\Db\Adapter\Driver\ResultInterface; +use Zend\Db\ResultSet\HydratingResultSet; +use Zend\Hydrator\Reflection as ReflectionHydrator; + +class UserEntity +{ + protected $first_name; + protected $last_name; + + public function getFirstName() + { + return $this->first_name; + } + + public function getLastName() + { + return $this->last_name; + } + + public function setFirstName($firstName) + { + $this->first_name = $firstName; + } + + public function setLastName($lastName) + { + $this->last_name = $lastName; + } +} + +$statement = $driver->createStatement($sql); +$statement->prepare($parameters); +$result = $statement->execute(); + +if ($result instanceof ResultInterface && $result->isQueryResult()) { + $resultSet = new HydratingResultSet(new ReflectionHydrator, new UserEntity); + $resultSet->initialize($result); + + foreach ($resultSet as $user) { + echo $user->getFirstName() . ' ' . $user->getLastName() . PHP_EOL; + } +} +``` + +For more information, see the [zend-hydrator](https://zendframework.github.io/zend-hydrator/) +documentation to get a better sense of the different strategies that can be +employed in order to populate a target object. diff --git a/doc/book/row-gateway.md b/doc/book/row-gateway.md new file mode 100644 index 0000000000..4b986003d6 --- /dev/null +++ b/doc/book/row-gateway.md @@ -0,0 +1,98 @@ +# Row Gateways + +`Zend\Db\RowGateway` is a sub-component of zend-db that implements the Row Data +Gateway pattern described in the book [Patterns of Enterprise Application +Architecture](http://www.martinfowler.com/books/eaa.html). Row Data Gateways +model individual rows of a database table, and provide methods such as `save()` +and `delete()` that persist the row to the database. Likewise, after a row from +the database is retrieved, it can then be manipulated and `save()`'d back to +the database in the same position (row), or it can be `delete()`'d from the +table. + +`RowGatewayInterface` defines the methods `save()` and `delete()`: + +```php +namespace Zend\Db\RowGateway; + +interface RowGatewayInterface +{ + public function save(); + public function delete(); +} +``` + +## Quick start + +`RowGateway` is generally used in conjunction with objects that produce +`Zend\Db\ResultSet`s, though it may also be used standalone. To use it +standalone, you need an `Adapter` instance and a set of data to work with. + +The following demonstrates a basic use case. + +```php +use Zend\Db\RowGateway\RowGateway; + +// Query the database: +$resultSet = $adapter->query('SELECT * FROM `user` WHERE `id` = ?', [2]); + +// Get array of data: +$rowData = $resultSet->current()->getArrayCopy(); + +// Create a row gateway: +$rowGateway = new RowGateway('id', 'my_table', $adapter); +$rowGateway->populate($rowData, true); + +// Manipulate the row and persist it: +$rowGateway->first_name = 'New Name'; +$rowGateway->save(); + +// Or delete this row: +$rowGateway->delete(); +``` + +The workflow described above is greatly simplified when `RowGateway` is used in +conjunction with the [TableGateway RowGatewayFeature](table-gateway.md#tablegateway-features). +In that paradigm, `select()` operations will produce a `ResultSet` that iterates +`RowGateway` instances. + +As an example: + +```php +use Zend\Db\TableGateway\Feature\RowGatewayFeature; +use Zend\Db\TableGateway\TableGateway; + +$table = new TableGateway('artist', $adapter, new RowGatewayFeature('id')); +$results = $table->select(['id' => 2]); + +$artistRow = $results->current(); +$artistRow->name = 'New Name'; +$artistRow->save(); +``` + +## ActiveRecord Style Objects + +If you wish to have custom behaviour in your `RowGateway` objects — +essentially making them behave similarly to the +[ActiveRecord](http://www.martinfowler.com/eaaCatalog/activeRecord.html) +pattern), pass a prototype object implementing the `RowGatewayInterface` to the +`RowGatewayFeature` constructor instead of a primary key: + +```php +use Zend\Db\TableGateway\Feature\RowGatewayFeature; +use Zend\Db\TableGateway\TableGateway; +use Zend\Db\RowGateway\RowGatewayInterface; + +class Artist implements RowGatewayInterface +{ + protected $adapter; + + public function __construct($adapter) + { + $this->adapter = $adapter; + } + + // ... save() and delete() implementations +} + +$table = new TableGateway('artist', $adapter, new RowGatewayFeature(new Artist($adapter))); +``` diff --git a/doc/book/zend.db.sql.ddl.md b/doc/book/sql-ddl.md similarity index 58% rename from doc/book/zend.db.sql.ddl.md rename to doc/book/sql-ddl.md index 4d08ea2c14..93440791a8 100644 --- a/doc/book/zend.db.sql.ddl.md +++ b/doc/book/sql-ddl.md @@ -1,33 +1,32 @@ -# Zend\\Db\\Sql\\Ddl +# DDL Abstraction -`Zend\Db\Sql\Ddl` is a sub-component of `Zend\Db\Sql` that allows consumers to create statement -objects that will produce DDL (Data Definition Language) SQL statements. When combined with a -platform specific `Zend\Db\Sql\Sql` object, these DDL objects are capable of producing -platform-specific `CREATE TABLE` statements, with specialized data types, constraints, and indexes -for a database/schema. +`Zend\Db\Sql\Ddl` is a sub-component of `Zend\Db\Sql` allowing creation of DDL +(Data Definition Language) SQL statements. When combined with a platform +specific `Zend\Db\Sql\Sql` object, DDL objects are capable of producing +platform-specific `CREATE TABLE` statements, with specialized data types, +constraints, and indexes for a database/schema. The following platforms have platform specializations for DDL: -* MySQL -* All databases compatible with ANSI SQL92 +- MySQL +- All databases compatible with ANSI SQL92 ## Creating Tables -Like `Zend\Db\Sql` objects, each statement type is represented by a class. For example, `CREATE -TABLE` is modeled by a `CreateTable` object; this is likewise the same for `ALTER TABLE` (as -`AlterTable`), and `DROP TABLE` (as `DropTable`). These classes exist in the `Zend\Db\Sql\Ddl` -namespace. To initiate the building of a DDL statement, such as `CreateTable`, one needs to -instantiate the object. There are a couple of valid patterns for this: +Like `Zend\Db\Sql` objects, each statement type is represented by a class. For +example, `CREATE TABLE` is modeled by the `CreateTable` class; this is likewise +the same for `ALTER TABLE` (as `AlterTable`), and `DROP TABLE` (as +`DropTable`). You can create instances using a number of approaches: ```php use Zend\Db\Sql\Ddl; $table = new Ddl\CreateTable(); -// or with table +// With a table name: $table = new Ddl\CreateTable('bar'); -// optionally, as a temporary table +// Optionally, as a temporary table: $table = new Ddl\CreateTable('bar', true); ``` @@ -37,19 +36,21 @@ You can also set the table after instantiation: $table->setTable('bar'); ``` -Currently, columns are added by creating a column object, described in the data type table in the -data type section below: +Currently, columns are added by creating a column object (described in the +[data type table below](#currently-supported-data-types)): ```php use Zend\Db\Sql\Ddl\Column; + $table->addColumn(new Column\Integer('id')); $table->addColumn(new Column\Varchar('name', 255)); ``` -Beyond adding columns to a table, constraints can also be added: +Beyond adding columns to a table, you may also add constraints: ```php use Zend\Db\Sql\Ddl\Constraint; + $table->addConstraint(new Constraint\PrimaryKey('id')); $table->addConstraint( new Constraint\UniqueKey(['name', 'foo'], 'my_unique_key') @@ -58,30 +59,32 @@ $table->addConstraint( ## Altering Tables -Similarly to `CreateTable`, you may also instantiate `AlterTable`: +Similar to `CreateTable`, you may also use `AlterTable` instances: ```php use Zend\Db\Sql\Ddl; $table = new Ddl\AlterTable(); -// or with table +// With a table name: $table = new Ddl\AlterTable('bar'); -// optionally, as a temporary table +// Optionally, as a temporary table: $table = new Ddl\AlterTable('bar', true); ``` -The primary difference between a `CreateTable` and `AlterTable` is that the `AlterTable` takes into -account that the table and its assets already exist. Therefore, while you still have `addColumn()` -and `addConstraint()`, you will also see the ability to change existing columns: +The primary difference between a `CreateTable` and `AlterTable` is that the +`AlterTable` takes into account that the table and its assets already exist. +Therefore, while you still have `addColumn()` and `addConstraint()`, you will +also have the ability to *alter* existing columns: ```php use Zend\Db\Sql\Ddl\Column; + $table->changeColumn('name', Column\Varchar('new_name', 50)); ``` -You may also drop existing columns or constraints: +You may also *drop* existing columns or constraints: ```php $table->dropColumn('foo'); @@ -90,7 +93,7 @@ $table->dropConstraint('my_index'); ## Dropping Tables -To drop a table, create a `DropTable` statement object: +To drop a table, create a `DropTable` instance: ```php $drop = new Ddl\DropTable('bar'); @@ -98,17 +101,17 @@ $drop = new Ddl\DropTable('bar'); ## Executing DDL Statements -After a DDL statement object has been created and configured, at some point you will want to execute -the statement. To do this, you will need two other objects: an `Adapter` instance, and a properly -seeded `Sql` instance. +After a DDL statement object has been created and configured, at some point you +will need to execute the statement. This requires an `Adapter` instance and a +properly seeded `Sql` instance. -The workflow looks something like this, with `$ddl` being a `CreateTable`, `AlterTable`, or -`DropTable` instance: +The workflow looks something like this, with `$ddl` being a `CreateTable`, +`AlterTable`, or `DropTable` instance: ```php use Zend\Db\Sql\Sql; -// existence of $adapter is assumed +// Existence of $adapter is assumed. $sql = new Sql($adapter); $adapter->query( @@ -117,18 +120,19 @@ $adapter->query( ); ``` -By passing the `$ddl` object through the `$sql` object's `getSqlStringForSqlObject()` method, we -ensure that any platform specific specializations/modifications are utilized to create a platform -specific SQL statement. +By passing the `$ddl` object through the `$sql` instance's +`getSqlStringForSqlObject()` method, we ensure that any platform specific +specializations/modifications are utilized to create a platform specific SQL +statement. -Next, using the constant `Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE` ensures that the SQL -statement is not prepared, as many DDL statements on a variety of platforms cannot be prepared, only -executed. +Next, using the constant `Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE` ensures +that the SQL statement is not prepared, as most DDL statements on most +platforms cannot be prepared, only executed. ## Currently Supported Data Types -These types exist in the `Zend\Db\Sql\Ddl\Column` namespace. Data types must implement -`Zend\Db\Sql\Ddl\Column\ColumnInterface`. +These types exist in the `Zend\Db\Sql\Ddl\Column` namespace. Data types must +implement `Zend\Db\Sql\Ddl\Column\ColumnInterface`. In alphabetical order: @@ -153,8 +157,8 @@ instance. Currently, this is primarily in `CreateTable::addColumn()` and `AlterT ## Currently Supported Constraint Types -These types exist in the `Zend\Db\Sql\Ddl\Constraint` namespace. Data types must implement -`Zend\Db\Sql\Ddl\Constraint\ConstraintInterface`. +These types exist in the `Zend\Db\Sql\Ddl\Constraint` namespace. Data types +must implement `Zend\Db\Sql\Ddl\Constraint\ConstraintInterface`. In alphabetical order: @@ -165,6 +169,6 @@ ForeignKey | `$name`, `$column`, `$referenceTable`, `$referenceColumn`, `$onDele PrimaryKey | `$columns` UniqueKey | `$column`, `$name = null` -Each of the above types can be utilized in any place that accepts a `Column\ConstraintInterface` -instance. Currently, this is primarily in `CreateTable::addConstraint()` and -`AlterTable::addConstraint()`. +Each of the above types can be utilized in any place that accepts a +`Column\ConstraintInterface` instance. Currently, this is primarily in +`CreateTable::addConstraint()` and `AlterTable::addConstraint()`. diff --git a/doc/book/sql.md b/doc/book/sql.md new file mode 100644 index 0000000000..9b6392b252 --- /dev/null +++ b/doc/book/sql.md @@ -0,0 +1,715 @@ +# SQL Abstraction + +`Zend\Db\Sql` is a SQL abstraction layer for building platform-specific SQL +queries via an object-oriented API. The end result of a `Zend\Db\Sql` object +will be to either produce a `Statement` and `ParameterContainer` that +represents the target query, or a full string that can be directly executed +against the database platform. To achieve this, `Zend\Db\Sql` objects require a +`Zend\Db\Adapter\Adapter` object in order to produce the desired results. + +## Quick start + +There are four primary tasks associated with interacting with a database +defined by Data Manipulation Language (DML): selecting, inserting, updating, +and deleting. As such, there are four primary classes that developers can +interact with in order to build queries in the `Zend\Db\Sql` namespace: +`Select`, `Insert`, `Update`, and `Delete`. + +Since these four tasks are so closely related and generally used together +within the same application, the `Zend\Db\Sql\Sql` class helps you create them +and produce the result you are attempting to achieve. + +```php +use Zend\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); // returns a Zend\Db\Sql\Select instance +$insert = $sql->insert(); // returns a Zend\Db\Sql\Insert instance +$update = $sql->update(); // returns a Zend\Db\Sql\Update instance +$delete = $sql->delete(); // returns a Zend\Db\Sql\Delete instance +``` + +As a developer, you can now interact with these objects, as described in the +sections below, to customize each query. Once they have been populated with +values, they are ready to either be prepared or executed. + +To prepare (using a Select object): + +```php +use Zend\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); +$select->from('foo'); +$select->where(['id' => 2]); + +$statement = $sql->prepareStatementForSqlObject($select); +$results = $statement->execute(); +``` + +To execute (using a Select object) + +```php +use Zend\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); +$select->from('foo'); +$select->where(['id' => 2]); + +$selectString = $sql->buildSqlString($select); +$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE); +``` + +`Zend\\Db\\Sql\\Sql` objects can also be bound to a particular table so that in +obtaining a `Select`, `Insert`, `Update`, or `Delete` instance, the object will be +seeded with the table: + +```php +use Zend\Db\Sql\Sql; + +$sql = new Sql($adapter, 'foo'); +$select = $sql->select(); +$select->where(['id' => 2]); // $select already has from('foo') applied +``` + +## Common interfaces for SQL implementations + +Each of these objects implements the following two interfaces: + +```php +interface PreparableSqlInterface +{ + public function prepareStatement(Adapter $adapter, StatementInterface $statement) : void; +} + +interface SqlInterface +{ + public function getSqlString(PlatformInterface $adapterPlatform = null) : string; +} +``` + +Use these functions to produce either (a) a prepared statement, or (b) a string +to execute. + +## Select + +`Zend\Db\Sql\Select` presents a unified API for building platform-specific SQL +SELECT queries. Instances may be created and consumed without +`Zend\Db\Sql\Sql`: + +```php +use Zend\Db\Sql\Select; + +$select = new Select(); +// or, to produce a $select bound to a specific table +$select = new Select('foo'); +``` + +If a table is provided to the `Select` object, then `from()` cannot be called +later to change the name of the table. + +Once you have a valid `Select` object, the following API can be used to further +specify various select statement parts: + +```php +class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface +{ + const JOIN_INNER = 'inner'; + const JOIN_OUTER = 'outer'; + const JOIN_LEFT = 'left'; + const JOIN_RIGHT = 'right'; + const SQL_STAR = '*'; + const ORDER_ASCENDING = 'ASC'; + const ORDER_DESCENDING = 'DESC'; + + public $where; // @param Where $where + + public function __construct(string|array|TableIdentifier $table = null); + public function from(string|array|TableIdentifier $table) : self; + public function columns(array $columns, bool $prefixColumnsWithTable = true) : self; + public function join(string|array $name, string $on, string|array $columns = self::SQL_STAR, string $type = self::JOIN_INNER) : self; + public function where(Where|callable|string|array|PredicateInterface $predicate, string $combination = Predicate\PredicateSet::OP_AND) : self; + public function group(string|array $group); + public function having(Having|callable|string|array $predicate, string $combination = Predicate\PredicateSet::OP_AND) : self; + public function order(string|array $order) : self; + public function limit(int $limit) : self; + public function offset(int $offset) : self; +} +``` + +### from() + +```php +// As a string: +$select->from('foo'); + +// As an array to specify an alias +// (produces SELECT "t".* FROM "table" AS "t") +$select->from(['t' => 'table']); + +// Using a Sql\TableIdentifier: +// (same output as above) +$select->from(new TableIdentifier(['t' => 'table'])); +``` + +### columns() + +```php +// As an array of names +$select->columns(['foo', 'bar']); + +// As an associative array with aliases as the keys +// (produces 'bar' AS 'foo', 'bax' AS 'baz') +$select->columns(['foo' => 'bar', 'baz' => 'bax']); +``` + +### join() + +```php +$select->join( + 'foo', // table name + 'id = bar.id', // expression to join on (will be quoted by platform object before insertion), + ['bar', 'baz'], // (optional) list of columns, same requirements as columns() above + $select::JOIN_OUTER // (optional), one of inner, outer, left, right also represented by constants in the API +); + +$select + ->from(['f' => 'foo']) // base table + ->join( + ['b' => 'bar'], // join table with alias + 'f.foo_id = b.foo_id' // join expression + ); +``` + +### where(), having() + +`Zend\Db\Sql\Select` provides bit of flexibility as it regards to what kind of +parameters are acceptable when calling `where()` or `having()`. The method +signature is listed as: + +```php +/** + * Create where clause + * + * @param Where|callable|string|array $predicate + * @param string $combination One of the OP_* constants from Predicate\PredicateSet + * @return Select + */ +public function where($predicate, $combination = Predicate\PredicateSet::OP_AND); +``` + +If you provide a `Zend\Db\Sql\Where` instance to `where()` or a +`Zend\Db\Sql\Having` instance to `having()`, any previous internal instances +will be replaced completely. When either instance is processed, this object will +be iterated to produce the WHERE or HAVING section of the SELECT statement. + +If you provide a PHP callable to `where()` or `having()`, this function will be +called with the `Select`'s `Where`/`Having` instance as the only parameter. +This enables code like the following: + +```php +$select->where(function (Where $where) { + $where->like('username', 'ralph%'); +}); +``` + +If you provide a *string*, this string will be used to create a +`Zend\Db\Sql\Predicate\Expression` instance, and its contents will be applied +as-is, with no quoting: + +```php +// SELECT "foo".* FROM "foo" WHERE x = 5 +$select->from('foo')->where('x = 5'); +``` + +If you provide an array with integer indices, the value can be one of: + +- a string; this will be used to build a `Predicate\Expression`. +- any object implementing `Predicate\PredicateInterface`. + +In either case, the instances are pushed onto the `Where` stack with the +`$combination` provided (defaulting to `AND`). + +As an example: + +```php +// SELECT "foo".* FROM "foo" WHERE x = 5 AND y = z +$select->from('foo')->where(['x = 5', 'y = z']); +``` + +If you provide an associative array with string keys, any value with a string +key will be cast as follows: + +PHP value | Predicate type +--------- | -------------- +`null` | `Predicate\IsNull` +`array` | `Predicate\In` +`string` | `Predicate\Operator`, where the key is the identifier. + +As an example: + +```php +// SELECT "foo".* FROM "foo" WHERE "c1" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL +$select->from('foo')->where([ + 'c1' => null, + 'c2' => [1, 2, 3], + new \Zend\Db\Sql\Predicate\IsNotNull('c3'), +]); +``` + +### order() + +```php +$select = new Select; +$select->order('id DESC'); // produces 'id' DESC + +$select = new Select; +$select + ->order('id DESC') + ->order('name ASC, age DESC'); // produces 'id' DESC, 'name' ASC, 'age' DESC + +$select = new Select; +$select->order(['name ASC', 'age DESC']); // produces 'name' ASC, 'age' DESC +``` + +### limit() and offset() + +```php +$select = new Select; +$select->limit(5); // always takes an integer/numeric +$select->offset(10); // similarly takes an integer/numeric +``` + +## Insert + +The Insert API: + +```php +class Insert implements SqlInterface, PreparableSqlInterface +{ + const VALUES_MERGE = 'merge'; + const VALUES_SET = 'set'; + + public function __construct(string|TableIdentifier $table = null); + public function into(string|TableIdentifier $table) : self; + public function columns(array $columns) : self; + public function values(array $values, string $flag = self::VALUES_SET) : self; +} +``` + +As with `Select`, the table may be provided during instantiation or via the +`into()` method. + +### columns() + +```php +$insert->columns(['foo', 'bar']); // set the valid columns +``` + +### values() + +```php +// The default behavior of values is to set the values; +// successive calls will not preserve values from previous calls. +$insert->values([ + 'col_1' => 'value1', + 'col_2' => 'value2', +]); +``` + +```php +// To merge values with previous calls, provide the appropriate flag: +$insert->values(['col_2' => 'value2'], $insert::VALUES_MERGE); +``` + +## Update + +```php +class Update +{ + const VALUES_MERGE = 'merge'; + const VALUES_SET = 'set'; + + public $where; // @param Where $where + public function __construct(string|TableIdentifier $table = null); + public function table(string|TableIdentifier $table) : self; + public function set(array $values, string $flag = self::VALUES_SET) : self; + public function where(Where|callable|string|array|PredicateInterface $predicate, string $combination = Predicate\PredicateSet::OP_AND) : self; +} +``` + +### set() + +```php +$update->set(['foo' => 'bar', 'baz' => 'bax']); +``` + +### where() + +See the [section on Where and Having](#where-and-having). + +## Delete + +```php +class Delete +{ + public $where; // @param Where $where + + public function __construct(string|TableIdentifier $table = null); + public function from(string|TableIdentifier $table); + public function where(Where|callable|string|array|PredicateInterface $predicate, string $combination = Predicate\PredicateSet::OP_AND) : self; +} +``` + +### where() + +See the [section on Where and Having](#where-and-having). + +## Where and Having + +In the following, we will talk about `Where`; note, however, that `Having` +utilizes the same API. + +Effectively, `Where` and `Having` extend from the same base object, a +`Predicate` (and `PredicateSet`). All of the parts that make up a WHERE or +HAVING clause that are AND'ed or OR'd together are called *predicates*. The +full set of predicates is called a `PredicateSet`. A `Predicate` generally +contains the values (and identifiers) separate from the fragment they belong to +until the last possible moment when the statement is either prepared +(parameteritized) or executed. In parameterization, the parameters will be +replaced with their proper placeholder (a named or positional parameter), and +the values stored inside an `Adapter\ParameterContainer`. When executed, the +values will be interpolated into the fragments they belong to and properly +quoted. + +In the `Where`/`Having` API, a distinction is made between what elements are +considered identifiers (`TYPE_IDENTIFIER`) and which are values (`TYPE_VALUE`). +There is also a special use case type for literal values (`TYPE_LITERAL`). All +element types are expressed via the `Zend\Db\Sql\ExpressionInterface` +interface. + +> ### Literals +> +> In ZF 2.1, an actual `Literal` type was added. `Zend\Db\Sql` now makes the +> distinction that literals will not have any parameters that need +> interpolating, while `Expression` objects *might* have parameters that need +> interpolating. In cases where there are parameters in an `Expression`, +> `Zend\Db\Sql\AbstractSql` will do its best to identify placeholders when the +> `Expression` is processed during statement creation. In short, if you don't +> have parameters, use `Literal` objects. + +The `Where` and `Having` API is that of `Predicate` and `PredicateSet`: + +```php +// Where & Having extend Predicate: +class Predicate extends PredicateSet +{ + public $and; + public $or; + public $AND; + public $OR; + public $NEST; + public $UNNEST; + + public function nest() : Predicate; + public function setUnnest(Predicate $predicate) : void; + public function unnest() : Predicate; + public function equalTo( + int|float|bool|string $left, + int|float|bool|string $right, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ) : self; + public function notEqualTo( + int|float|bool|string $left, + int|float|bool|string $right, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ) : self; + public function lessThan( + int|float|bool|string $left, + int|float|bool|string $right, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ) : self; + public function greaterThan( + int|float|bool|string $left, + int|float|bool|string $right, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ) : self; + public function lessThanOrEqualTo( + int|float|bool|string $left, + int|float|bool|string $right, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ) : self; + public function greaterThanOrEqualTo( + int|float|bool|string $left, + int|float|bool|string $right, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ) : self; + public function like(string $identifier, string $like) : self; + public function notLike(string $identifier, string $notLike) : self; + public function literal(string $literal) : self; + public function expression(string $expression, array $parameters = null) : self; + public function isNull(string $identifier) : self; + public function isNotNull(string $identifier) : self; + public function in(string $identifier, array $valueSet = []) : self; + public function notIn(string $identifier, array $valueSet = []) : self; + public function between( + string $identifier, + int|float|string $minValue, + int|float|string $maxValue + ) : self; + public function notBetween( + string $identifier, + int|float|string $minValue, + int|float|string $maxValue + ) : self; + public function predicate(PredicateInterface $predicate) : self; + + // Inherited From PredicateSet + + public function addPredicate(PredicateInterface $predicate, $combination = null) : self; + public function getPredicates() PredicateInterface[]; + public function orPredicate(PredicateInterface $predicate) : self; + public function andPredicate(PredicateInterface $predicate) : self; + public function getExpressionData() : array; + public function count() : int; +} +``` + +Each method in the API will produce a corresponding `Predicate` object of a similarly named +type, as described below. + +### equalTo(), lessThan(), greaterThan(), lessThanOrEqualTo(), greaterThanOrEqualTo() + +```php +$where->equalTo('id', 5); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Operator($left, Operator::OPERATOR_EQUAL_TO, $right, $leftType, $rightType) +); +``` + +Operators use the following API: + +```php +class Operator implements PredicateInterface +{ + const OPERATOR_EQUAL_TO = '='; + const OP_EQ = '='; + const OPERATOR_NOT_EQUAL_TO = '!='; + const OP_NE = '!='; + const OPERATOR_LESS_THAN = '<'; + const OP_LT = '<'; + const OPERATOR_LESS_THAN_OR_EQUAL_TO = '<='; + const OP_LTE = '<='; + const OPERATOR_GREATER_THAN = '>'; + const OP_GT = '>'; + const OPERATOR_GREATER_THAN_OR_EQUAL_TO = '>='; + const OP_GTE = '>='; + + public function __construct( + int|float|bool|string $left = null, + string $operator = self::OPERATOR_EQUAL_TO, + int|float|bool|string $right = null, + string $leftType = self::TYPE_IDENTIFIER, + string $rightType = self::TYPE_VALUE + ); + public function setLeft(int|float|bool|string $left); + public function getLeft() : int|float|bool|string; + public function setLeftType(string $type) : self; + public function getLeftType() : string; + public function setOperator(string $operator); + public function getOperator() : string; + public function setRight(int|float|bool|string $value) : self; + public function getRight() : int|float|bool|string; + public function setRightType(string $type) : self; + public function getRightType() : string; + public function getExpressionData() : array; +} +``` + +### like($identifier, $like), notLike($identifier, $notLike) + +```php +$where->like($identifier, $like): + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Like($identifier, $like) +); +``` + +The following is the `Like` API: + +```php +class Like implements PredicateInterface +{ + public function __construct(string $identifier = null, string $like = null); + public function setIdentifier(string $identifier) : self; + public function getIdentifier() : string; + public function setLike(string $like) : self; + public function getLike() : string; +} +``` + +### literal($literal) + +```php +$where->literal($literal); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Literal($literal) +); +``` + +The following is the `Literal` API: + +```php +class Literal implements ExpressionInterface, PredicateInterface +{ + const PLACEHOLDER = '?'; + public function __construct(string $literal = ''); + public function setLiteral(string $literal) : self; + public function getLiteral() : string; +} +``` + +### expression($expression, $parameter) + +```php +$where->expression($expression, $parameter); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Expression($expression, $parameter) +); +``` + +The following is the `Expression` API: + +```php +class Expression implements ExpressionInterface, PredicateInterface +{ + const PLACEHOLDER = '?'; + + public function __construct( + string $expression = null, + int|float|bool|string|array $valueParameter = null + /* [, $valueParameter, ... ] */ + ); + public function setExpression(string $expression) : self; + public function getExpression() : string; + public function setParameters(int|float|bool|string|array $parameters) : self; + public function getParameters() : array; +} +``` + +### isNull($identifier) + +```php +$where->isNull($identifier); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\IsNull($identifier) +); +``` + +The following is the `IsNull` API: + +```php +class IsNull implements PredicateInterface +{ + public function __construct(string $identifier = null); + public function setIdentifier(string $identifier) : self; + public function getIdentifier() : string; +} +``` + +### isNotNull($identifier) + +```php +$where->isNotNull($identifier); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\IsNotNull($identifier) +); +``` + +The following is the `IsNotNull` API: + +```php +class IsNotNull implements PredicateInterface +{ + public function __construct(string $identifier = null); + public function setIdentifier(string $identifier) : self; + public function getIdentifier() : string; +} +``` + +### in($identifier, $valueSet), notIn($identifier, $valueSet) + +```php +$where->in($identifier, $valueSet); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\In($identifier, $valueSet) +); +``` + +The following is the `In` API: + +```php +class In implements PredicateInterface +{ + public function __construct( + string|array $identifier = null, + array|Select $valueSet = null + ); + public function setIdentifier(string|array $identifier) : self; + public function getIdentifier() : string|array; + public function setValueSet(array|Select $valueSet) : self; + public function getValueSet() : array|Select; +} +``` + +### between($identifier, $minValue, $maxValue), notBetween($identifier, $minValue, $maxValue) + +```php +$where->between($identifier, $minValue, $maxValue); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Between($identifier, $minValue, $maxValue) +); +``` + +The following is the `Between` API: + +```php +class Between implements PredicateInterface +{ + public function __construct( + string $identifier = null, + int|float|string $minValue = null, + int|float|string $maxValue = null + ); + public function setIdentifier(string $identifier) : self; + public function getIdentifier() : string; + public function setMinValue(int|float|string $minValue) : self; + public function getMinValue() : int|float|string; + public function setMaxValue(int|float|string $maxValue) : self; + public function getMaxValue() : int|float|string; + public function setSpecification(string $specification); +} +``` diff --git a/doc/book/table-gateway.md b/doc/book/table-gateway.md new file mode 100644 index 0000000000..9bf745f810 --- /dev/null +++ b/doc/book/table-gateway.md @@ -0,0 +1,217 @@ +# Table Gateways + +The Table Gateway subcomponent provides an object-oriented representation of a +datbase table; its methods mirror the most common table operations. In code, +the interface resembles: + +```php +namespace Zend\Db\TableGateway; + +use Zend\Db\ResultSet\ResultSetInterface; +use Zend\Db\Sql\Where; + +interface TableGatewayInterface +{ + public function getTable() : string; + public function select(Where|callable|string|array $where = null) : ResultSetInterface; + public function insert(array $set) : int; + public function update( + array $set, + Where|callable|string|array $where = null, + array $joins = null + ) : int; + public function delete(Where|callable|string|array $where) : int; +} +``` + +There are two primary implementations of the `TableGatewayInterface`, +`AbstractTableGateway` and `TableGateway`. The `AbstractTableGateway` is an +abstract basic implementation that provides functionality for `select()`, +`insert()`, `update()`, `delete()`, as well as an additional API for doing +these same kinds of tasks with explicit `Zend\Db\Sql` objects: `selectWith()`, +`insertWith()`, `updateWith()`, and `deleteWith()`. In addition, +AbstractTableGateway also implements a "Feature" API, that allows for expanding +the behaviors of the base `TableGateway` implementation without having to +extend the class with this new functionality. The `TableGateway` concrete +implementation simply adds a sensible constructor to the `AbstractTableGateway` +class so that out-of-the-box, `TableGateway` does not need to be extended in +order to be consumed and utilized to its fullest. + +## Quick start + +The following example uses `Zend\Db\TableGateway\TableGateway`, which defines +the following API: + +```php +namespace Zend\Db\TableGateway; + +use Zend\Db\Adapter\AdapterInterface; +use Zend\Db\ResultSet\ResultSet; +use Zend\Db\ResultSet\ResultSetInterface; +use Zend\Db\Sql; +use Zend\Db\Sql\TableIdentifier; + +class TableGateway extends AbstractTableGateway +{ + public $lastInsertValue; + public $table; + public $adapter; + + public function __construct( + string|TableIdentifier $table, + AdapterInterface $adapter, + Feature\AbstractFeature|Feature\FeatureSet|Feature\AbstractFeature[] $features = null, + ResultSetInterface $resultSetPrototype = null, + Sql\Sql $sql = null + ); + + /** Inherited from AbstractTableGateway */ + + public function isInitialized() : bool; + public function initialize() : void; + public function getTable() : string; + public function getAdapter() : AdapterInterface; + public function getColumns() : array; + public function getFeatureSet() Feature\FeatureSet; + public function getResultSetPrototype() : ResultSetInterface; + public function getSql() | Sql\Sql; + public function select(Sql\Where|callable|string|array $where = null) : ResultSetInterface; + public function selectWith(Sql\Select $select) : ResultSetInterface; + public function insert(array $set) : int; + public function insertWith(Sql\Insert $insert) | int; + public function update( + array $set, + Sql\Where|callable|string|array $where = null, + array $joins = null + ) : int; + public function updateWith(Sql\Update $update) : int; + public function delete(Sql\Where|callable|string|array $where) : int; + public function deleteWith(Sql\Delete $delete) : int; + public function getLastInsertValue() : int; +} +``` + +The concrete `TableGateway` object uses constructor injection for getting +dependencies and options into the instance. The table name and an instance of +an `Adapter` are all that is required to create an instance. + +Out of the box, this implementation makes no assumptions about table structure +or metadata, and when `select()` is executed, a simple `ResultSet` object with +the populated `Adapter`'s `Result` (the datasource) will be returned and ready +for iteration. + +```php +use Zend\Db\TableGateway\TableGateway; + +$projectTable = new TableGateway('project', $adapter); +$rowset = $projectTable->select(['type' => 'PHP']); + +echo 'Projects of type PHP: ' . PHP_EOL; +foreach ($rowset as $projectRow) { + echo $projectRow['name'] . PHP_EOL; +} + +// Or, when expecting a single row: +$artistTable = new TableGateway('artist', $adapter); +$rowset = $artistTable->select(['id' => 2]); +$artistRow = $rowset->current(); + +var_dump($artistRow); +``` + +The `select()` method takes the same arguments as +`Zend\Db\Sql\Select::where()`; arguments will be passed to the `Select` +instance used to build the SELECT query. This means the following is possible: + +```php +use Zend\Db\TableGateway\TableGateway; +use Zend\Db\Sql\Select; + +$artistTable = new TableGateway('artist', $adapter); + +// Search for at most 2 artists who's name starts with Brit, ascending: +$rowset = $artistTable->select(function (Select $select) { + $select->where->like('name', 'Brit%'); + $select->order('name ASC')->limit(2); +}); +``` + +## TableGateway Features + +The Features API allows for extending the functionality of the base +`TableGateway` object without having to polymorphically extend the base class. +This allows for a wider array of possible mixing and matching of features to +achieve a particular behavior that needs to be attained to make the base +implementation of `TableGateway` useful for a particular problem. + +With the `TableGateway` object, features should be injected though the +constructor. The constructor can take features in 3 different forms: + +- as a single `Feature` instance +- as a `FeatureSet` instance +- as an array of `Feature` instances + +There are a number of features built-in and shipped with zend-db: + +- `GlobalAdapterFeature`: the ability to use a global/static adapter without + needing to inject it into a `TableGateway` instance. This is only useful when + you are extending the `AbstractTableGateway` implementation: + + ```php + use Zend\Db\TableGateway\AbstractTableGateway; + use Zend\Db\TableGateway\Feature; + + class MyTableGateway extends AbstractTableGateway + { + public function __construct() + { + $this->table = 'my_table'; + $this->featureSet = new Feature\FeatureSet(); + $this->featureSet->addFeature(new Feature\GlobalAdapterFeature()); + $this->initialize(); + } + } + + // elsewhere in code, in a bootstrap + Zend\Db\TableGateway\Feature\GlobalAdapterFeature::setStaticAdapter($adapter); + + // in a controller, or model somewhere + $table = new MyTableGateway(); // adapter is statically loaded + ``` + +- `MasterSlaveFeature`: the ability to use a master adapter for `insert()`, + `update()`, and `delete()`, but switch to a slave adapter for all `select()` + operations. + + ```php + $table = new TableGateway('artist', $adapter, new Feature\MasterSlaveFeature($slaveAdapter)); + ``` + +- `MetadataFeature`: the ability populate `TableGateway` with column + information from a `Metadata` object. It will also store the primary key + information in case the `RowGatewayFeature` needs to consume this information. + + ```php + $table = new TableGateway('artist', $adapter, new Feature\MetadataFeature()); + ``` + +- `EventFeature`: the ability to compose a + [zend-eventmanager](https://github.com/zendframework/zend-eventmanager) + `EventManager` instance within your `TableGateway` instance, and attach + listeners to the various events of its lifecycle. + + ```php + $table = new TableGateway('artist', $adapter, new Feature\EventFeature($eventManagerInstance)); + ``` + +- `RowGatewayFeature`: the ability for `select()` to return a `ResultSet` object that upon iteration + will return a `RowGateway` instance for each row. + + ```php + $table = new TableGateway('artist', $adapter, new Feature\RowGatewayFeature('id')); + $results = $table->select(['id' => 2]); + + $artistRow = $results->current(); + $artistRow->name = 'New Name'; + $artistRow->save(); + ``` diff --git a/doc/book/zend.db.adapter.md b/doc/book/zend.db.adapter.md deleted file mode 100644 index e99dcf9d5c..0000000000 --- a/doc/book/zend.db.adapter.md +++ /dev/null @@ -1,433 +0,0 @@ -# Zend\\Db\\Adapter - -The Adapter object is the most important sub-component of `Zend\Db`. It is responsible for adapting -any code written in or for Zend\\Db to the targeted php extensions and vendor databases. In doing -this, it creates an abstraction layer for the PHP extensions, which is called the "Driver" portion -of the `Zend\Db` adapter. It also creates a lightweight abstraction layer, called the "Platform" -portion of the adapter, for the various idiosyncrasies that each vendor-specific platform might have -in its SQL/RDBMS implementation. - -## Creating an Adapter - Quickstart - -Creating an adapter can simply be done by instantiating the `Zend\Db\Adapter\Adapter` class. The -most common use case, while not the most explicit, is to pass an array of configuration to the -`Adapter`. - -```php -$adapter = new Zend\Db\Adapter\Adapter($configArray); -``` - -This driver array is an abstraction for the extension level required parameters. Here is a table for -the key-value pairs that should be in configuration array. - -Key | Is Required? | Value ------------|------------------------|------ -`driver` | required | `Mysqli`, `Sqlsrv`, `Pdo_Sqlite`, `Pdo_Mysql`, `Pdo`(= Other PDO Driver) -`database` | generally required | the name of the database (schema) -`username` | generally required | the connection username -`password` | generally required | the connection password -`hostname` | not generally required | the IP address or hostname to connect to -`port` | not generally required | the port to connect to (if applicable) -`charset` | not generally required | the character set to use - -> ### Options are adapter-dependent -> -> Other names will work as well. Effectively, if the PHP manual uses a -> particular naming, this naming will be supported by our Driver. For example, -> dbname in most cases will also work for 'database'. Another example is that -> in the case of Sqlsrv, UID will work in place of username. Which format you -> choose is up to you, but the above table represents the official abstraction -> names. - -So, for example, a MySQL connection using ext/mysqli: - -```php -$adapter = new Zend\Db\Adapter\Adapter([ - 'driver' => 'Mysqli', - 'database' => 'zend_db_example', - 'username' => 'developer', - 'password' => 'developer-password', -]); -``` - -Another example, of a Sqlite connection via PDO: - -```php -$adapter = new Zend\Db\Adapter\Adapter([ - 'driver' => 'Pdo_Sqlite', - 'database' => 'path/to/sqlite.db', -]); -``` - -It is important to know that by using this style of adapter creation, the `Adapter` will attempt to -create any dependencies that were not explicitly provided. A Driver object will be created from the -configuration array provided in the constructor. A Platform object will be created based off the -type of Driver class that was instantiated. And lastly, a default ResultSet object is created and -utilized. Any of these objects can be injected, to do this, see the next section. - -The list of officially supported drivers: - -* `Mysqli`: The ext/mysqli driver -* `Pgsql`: The ext/pgsql driver -* `Sqlsrv`: The ext/sqlsrv driver (from Microsoft) -* `Pdo_Mysql`: MySQL through the PDO extension -* `Pdo_Sqlite`: SQLite though the PDO extension -* `Pdo_Pgsql`: PostgreSQL through the PDO extension - -## Creating an Adapter Using Dependency Injection - -The more expressive and explicit way of creating an adapter is by injecting all your dependencies up -front. `Zend\Db\Adapter\Adapter` uses constructor injection, and all required dependencies are -injected through the constructor, which has the following signature (in pseudo-code): - -```php -use Zend\Db\Adapter\Platform\PlatformInterface; -use Zend\Db\ResultSet\ResultSet; - -class Zend\Db\Adapter\Adapter -{ - public function __construct( - $driver, - PlatformInterface $platform = null, - ResultSet $queryResultSetPrototype = null - ) -} -``` - -What can be injected: - -- $driver - an array of connection parameters (see above) or an instance of - `Zend\Db\Adapter\Driver\DriverInterface` -- $platform - (optional) an instance of `Zend\Db\Platform\PlatformInterface`, the default will be - created based off the driver implementation -- $queryResultSetPrototype - (optional) an instance of `Zend\Db\ResultSet\ResultSet`, to understand - this object's role, see the section below on querying through the adapter - -## Query Preparation Through Zend\\Db\\Adapter\\Adapter::query() - -By default, query() prefers that you use "preparation" as a means for processing SQL statements. -This generally means that you will supply a SQL statement with the values substituted by -placeholders, and then the parameters for those placeholders are supplied separately. An example of -this workflow with `Zend\Db\Adapter\Adapter` is: - -```php -$adapter->query('SELECT * FROM `artist` WHERE `id` = ?', [5]); -``` - -The above example will go through the following steps: - -- create a new Statement object -- prepare an array into a ParameterContainer if necessary -- inject the ParameterContainer into the Statement object -- execute the Statement object, producing a Result object -- check the Result object to check if the supplied sql was a "query", or a result set producing - statement -- if it is a result set producing query, clone the ResultSet prototype, inject Result as datasource, - return it -- else, return the Result - -## Query Execution Through Zend\\Db\\Adapter\\Adapter::query() - -In some cases, you have to execute statements directly. The primary purpose for needing to execute -sql instead of prepare and execute a sql statement, might be because you are attempting to execute a -DDL statement (which in most extensions and vendor platforms), are un-preparable. An example of -executing: - -```php -$adapter->query( - 'ALTER TABLE ADD INDEX(`foo_index`) ON (`foo_column`)', - Adapter::QUERY_MODE_EXECUTE -); -``` - -The primary difference to notice is that you must provide the Adapter::QUERY\_MODE\_EXECUTE -(execute) as the second parameter. - -## Creating Statements - -While query() is highly useful for one-off and quick querying of a database through Adapter, it -generally makes more sense to create a statement and interact with it directly, so that you have -greater control over the prepare-then-execute workflow. To do this, Adapter gives you a routine -called createStatement() that allows you to create a Driver specific Statement to use so you can -manage your own prepare-then-execute workflow. - -```php -// with optional parameters to bind up-front -$statement = $adapter->createStatement($sql, $optionalParameters); -$result = $statement->execute(); -``` - -## Using the Driver Object - -The Driver object is the primary place where `Zend\Db\Adapter\Adapter` implements the connection -level abstraction making it possible to use all of ZendDb's interfaces via the various ext/mysqli, -ext/sqlsrv, PDO, and other PHP level drivers. To make this possible, each driver is composed of 3 -objects: - -* A connection: `Zend\Db\Adapter\Driver\ConnectionInterface` -* A statement: `Zend\Db\Adapter\Driver\StatementInterface` -* A result: `Zend\Db\Adapter\Driver\ResultInterface` - -Each of the built-in drivers practices "prototyping" as a means of creating objects when new -instances are requested. The workflow looks like this: - -* An adapter is created with a set of connection parameters -* The adapter chooses the proper driver to instantiate, for example `Zend\Db\Adapter\Driver\Mysqli` -* That driver class is instantiated -* If no connection, statement or result objects are injected, defaults are instantiated - -This driver is now ready to be called on when particular workflows are requested. Here is what the -Driver API looks like: - -```php -namespace Zend\Db\Adapter\Driver; - -interface DriverInterface -{ - const PARAMETERIZATION_POSITIONAL = 'positional'; - const PARAMETERIZATION_NAMED = 'named'; - const NAME_FORMAT_CAMELCASE = 'camelCase'; - const NAME_FORMAT_NATURAL = 'natural'; - public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE); - public function checkEnvironment(); - public function getConnection(); - public function createStatement($sqlOrResource = null); - public function createResult($resource); - public function getPrepareType(); - public function formatParameterName($name, $type = null); - public function getLastGeneratedValue(); -} -``` - -From this DriverInterface, you can - -- Determine the name of the platform this driver supports (useful for choosing the proper platform - object) -- Check that the environment can support this driver -- Return the Connection object -- Create a Statement object which is optionally seeded by an SQL statement (this will generally be a - clone of a prototypical statement object) -- Create a Result object which is optionally seeded by a statement resource (this will generally be - a clone of a prototypical result object) -- Format parameter names, important to distinguish the difference between the various ways - parameters are named between extensions -- Retrieve the overall last generated value (such as an auto-increment value) - -Statement objects generally look like this: - -```php -namespace Zend\Db\Adapter\Driver; - -interface StatementInterface extends StatementContainerInterface -{ - public function getResource(); - public function prepare($sql = null); - public function isPrepared(); - public function execute($parameters = null); - - /** Inherited from StatementContainerInterface */ - public function setSql($sql); - public function getSql(); - public function setParameterContainer(ParameterContainer $parameterContainer); - public function getParameterContainer(); -} -``` - -Result objects generally look like this: - -```php -namespace Zend\Db\Adapter\Driver; - -use Countable; -use Iterator; - -interface ResultInterface extends Countable, Iterator -{ - public function buffer(); - public function isQueryResult(); - public function getAffectedRows(); - public function getGeneratedValue(); - public function getResource(); - public function getFieldCount(); -} -``` - -## Using The Platform Object - -The Platform object provides an API to assist in crafting queries in a way that is specific to the -SQL implementation of a particular vendor. Nuances such as how identifiers or values are quoted, or -what the identifier separator character is are handled by this object. To get an idea of the -capabilities, the interface for a platform object looks like this: - -```php -namespace Zend\Db\Adapter\Platform; - -interface PlatformInterface -{ - public function getName(); - public function getQuoteIdentifierSymbol(); - public function quoteIdentifier($identifier); - public function quoteIdentifierChain($identiferChain) - public function getQuoteValueSymbol(); - public function quoteValue($value); - public function quoteValueList($valueList); - public function getIdentifierSeparator(); - public function quoteIdentifierInFragment($identifier, array $additionalSafeWords = []); -} -``` - -While one can instantiate your own Platform object, generally speaking, it is easier to get the -proper Platform instance from the configured adapter (by default the Platform type will match the -underlying driver implementation): - -```php -$platform = $adapter->getPlatform(); -// or -$platform = $adapter->platform; // magic property access -``` - -The following is a couple of example of Platform usage: - -```php -/** @var $adapter Zend\Db\Adapter\Adapter */ -/** @var $platform Zend\Db\Adapter\Platform\Sql92 */ -$platform = $adapter->getPlatform(); - -// "first_name" -echo $platform->quoteIdentifier('first_name'); - -// " -echo $platform->getQuoteIdentifierSymbol(); - -// "schema"."mytable" -echo $platform->quoteIdentifierChain(['schema', 'mytable']); - -// ' -echo $platform->getQuoteValueSymbol(); - -// 'myvalue' -echo $platform->quoteValue('myvalue'); - -// 'value', 'Foo O\\'Bar' -echo $platform->quoteValueList(['value', "Foo O'Bar"]); - -// . -echo $platform->getIdentifierSeparator(); - -// "foo" as "bar" -echo $platform->quoteIdentifierInFragment('foo as bar'); - -// additionally, with some safe words: -// ("foo"."bar" = "boo"."baz") -echo $platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']); -``` - -## Using The Parameter Container - -The ParameterContainer object is a container for the various parameters that need to be passed into -a Statement object to fulfill all the various parameterized parts of the SQL statement. This object -implements the ArrayAccess interface. Below is the ParameterContainer API: - -```php -namespace Zend\Db\Adapter; - -use ArrayAccess; -use Countable; -use Iterator; - -class ParameterContainer implements Iterator, ArrayAccess, Countable -{ - public function __construct(array $data = []) - - /** methods to interact with values */ - public function offsetExists($name) - public function offsetGet($name) - public function offsetSetReference($name, $from) - public function offsetSet($name, $value, $errata = null) - public function offsetUnset($name) - - /** set values from array (will reset first) */ - public function setFromArray(Array $data) - - /** methods to interact with value errata */ - public function offsetSetErrata($name, $errata) - public function offsetGetErrata($name) - public function offsetHasErrata($name) - public function offsetUnsetErrata($name) - - /** errata only iterator */ - public function getErrataIterator() - - /** get array with named keys */ - public function getNamedArray() - - /** get array with int keys, ordered by position */ - public function getPositionalArray() - - /** iterator: */ - public function count() - public function current() - public function next() - public function key() - public function valid() - public function rewind() - - /** merge existing array of parameters with existing parameters */ - public function merge($parameters) -} -``` - -In addition to handling parameter names and values, the container will assist in tracking parameter -types for PHP type to SQL type handling. For example, it might be important that: - -```php -$container->offsetSet('limit', 5); -``` - -be bound as an integer. To achieve this, pass in the ParameterContainer::TYPE\_INTEGER constant as -the 3rd parameter: - -```php -$container->offsetSet('limit', 5, $container::TYPE_INTEGER); -``` - -This will ensure that if the underlying driver supports typing of bound parameters, that this -translated information will also be passed along to the actual php database driver. - -## Examples - -Creating a Driver and Vendor portable Query, Preparing and Iterating Result - -```php -$adapter = new Zend\Db\Adapter\Adapter($driverConfig); - -$qi = function ($name) use ($adapter) { return $adapter->platform->quoteIdentifier($name); }; -$fp = function ($name) use ($adapter) { return $adapter->driver->formatParameterName($name); }; - -$sql = 'UPDATE ' . $qi('artist') - . ' SET ' . $qi('name') . ' = ' . $fp('name') - . ' WHERE ' . $qi('id') . ' = ' . $fp('id'); - -/** @var $statement Zend\Db\Adapter\Driver\StatementInterface */ -$statement = $adapter->query($sql); - -$parameters = [ - 'name' => 'Updated Artist', - 'id' => 1, -]; - -$statement->execute($parameters); - -// DATA INSERTED, NOW CHECK - -/** @var $statement Zend\Db\Adapter\DriverStatementInterface */ -$statement = $adapter->query('SELECT * FROM ' - . $qi('artist') - . ' WHERE id = ' . $fp('id')); - -/** @var $results Zend\Db\ResultSet\ResultSet */ -$results = $statement->execute(['id' => 1]); - -$row = $results->current(); -$name = $row['name']; -``` diff --git a/doc/book/zend.db.result-set.md b/doc/book/zend.db.result-set.md deleted file mode 100644 index ddaafca04e..0000000000 --- a/doc/book/zend.db.result-set.md +++ /dev/null @@ -1,142 +0,0 @@ -# Zend\\Db\\ResultSet - -`Zend\Db\ResultSet` is a sub-component of Zend\\Db for abstracting the iteration of rowset producing -queries. While data sources for this can be anything that is iterable, generally a -`Zend\Db\Adapter\Driver\ResultInterface` based object is the primary source for retrieving data. - -`Zend\Db\ResultSet`'s must implement the `Zend\Db\ResultSet\ResultSetInterface` and all -sub-components of Zend\\Db that return a ResultSet as part of their API will assume an instance of a -`ResultSetInterface` should be returned. In most casts, the Prototype pattern will be used by -consuming object to clone a prototype of a ResultSet and return a specialized ResultSet with a -specific data source injected. The interface of ResultSetInterface looks like this: - -```php -use Countable; -use Traversable; - -interface ResultSetInterface extends Traversable, Countable -{ - public function initialize($dataSource); - public function getFieldCount(); -} -``` - -## Quickstart - -`Zend\Db\ResultSet\ResultSet` is the most basic form of a ResultSet object that will expose each row -as either an ArrayObject-like object or an array of row data. By default, `Zend\Db\Adapter\Adapter` -will use a prototypical `Zend\Db\ResultSet\ResultSet` object for iterating when using the -`Zend\Db\Adapter\Adapter::query()` method. - -The following is an example workflow similar to what one might find inside -`Zend\Db\Adapter\Adapter::query()`: - -```php -use Zend\Db\Adapter\Driver\ResultInterface; -use Zend\Db\ResultSet\ResultSet; - -$stmt = $driver->createStatement('SELECT * FROM users'); -$stmt->prepare(); -$result = $stmt->execute($parameters); - -if ($result instanceof ResultInterface && $result->isQueryResult()) { - $resultSet = new ResultSet; - $resultSet->initialize($result); - - foreach ($resultSet as $row) { - echo $row->my_column . PHP_EOL; - } -} -``` - -## Zend\\Db\\ResultSet\\ResultSet and Zend\\Db\\ResultSet\\AbstractResultSet - -For most purposes, either a instance of `Zend\Db\ResultSet\ResultSet` or a derivative of -`Zend\Db\ResultSet\AbstractResultSet` will be being used. The implementation of the -`AbstractResultSet` offers the following core functionality: - -```php -namespace Zend\Db\ResultSet; - -use Iterator; - -abstract class AbstractResultSet implements Iterator, ResultSetInterface -{ - public function initialize($dataSource) - public function getDataSource() - public function getFieldCount() - - /** Iterator */ - public function next() - public function key() - public function current() - public function valid() - public function rewind() - - /** countable */ - public function count() - - /** get rows as array */ - public function toArray() -} -``` - -## Zend\\Db\\ResultSet\\HydratingResultSet - -`Zend\Db\ResultSet\HydratingResultSet` is a more flexible `ResultSet` object that allows the -developer to choose an appropriate "hydration strategy" for getting row data into a target object. -While iterating over results, `HydratingResultSet` will take a prototype of a target object and -clone it once for each row. The `HydratingResultSet` will then hydrate that clone with the row data. - -In the example below, rows from the database will be iterated, and during iteration, -`HydratingRowSet` will use the Reflection based hydrator to inject the row data directly into the -protected members of the cloned UserEntity object: - -```php -use Zend\Db\Adapter\Driver\ResultInterface; -use Zend\Db\ResultSet\HydratingResultSet; -use Zend\Stdlib\Hydrator\Reflection as ReflectionHydrator; - -class UserEntity -{ - protected $first_name; - protected $last_name; - - public function getFirstName() - { - return $this->first_name; - } - - public function getLastName() - { - return $this->last_name; - } - - public function setFirstName($first_name) - { - $this->first_name = $first_name; - } - - public function setLastName($last_name) - { - $this->last_name = $last_name; - } -} - -$stmt = $driver->createStatement($sql); -$stmt->prepare($parameters); -$result = $stmt->execute(); - -if ($result instanceof ResultInterface && $result->isQueryResult()) { - $resultSet = new HydratingResultSet(new ReflectionHydrator, new UserEntity); - $resultSet->initialize($result); - - foreach ($resultSet as $user) { - echo $user->getFirstName() . ' ' . $user->getLastName() . PHP_EOL; - } -} -``` - -For more information, see the [zend-hydrator](https://zendframework.github.io/zend-hydrator/) -documentation to get a better sense of the different strategies that can be -employed in order to populate a target object. diff --git a/doc/book/zend.db.row-gateway.md b/doc/book/zend.db.row-gateway.md deleted file mode 100644 index 398f036b84..0000000000 --- a/doc/book/zend.db.row-gateway.md +++ /dev/null @@ -1,89 +0,0 @@ -# Zend\\Db\\RowGateway - -`Zend\Db\RowGateway` is a sub-component of Zend\\Db that implements the Row Gateway pattern from -PoEAA. This effectively means that Row Gateway objects primarily model a row in a database, and have -methods such as save() and delete() that will help persist this row-as-an-object in the database -itself. Likewise, after a row from the database is retrieved, it can then be manipulated and -save()'d back to the database in the same position (row), or it can be delete()'d from the table. - -The interface for a Row Gateway object simply adds save() and delete() and this is the interface -that should be assumed when a component has a dependency that is expected to be an instance of a -RowGateway object: - -```php -interface RowGatewayInterface -{ - public function save(); - public function delete(); -} -``` - -## Quickstart - -While most of the time, RowGateway will be used in conjunction with other Zend\\Db\\ResultSet -producing objects, it is possible to use it standalone. To use it standalone, you simply need an -Adapter and a set of data to work with. The following use case demonstrates -Zend\\Db\\RowGateway\\RowGateway usage in its simplest form: - -```php -use Zend\Db\RowGateway\RowGateway; - -// query the database -$resultSet = $adapter->query('SELECT * FROM `user` WHERE `id` = ?', [2]); - -// get array of data -$rowData = $resultSet->current()->getArrayCopy(); - -// row gateway -$rowGateway = new RowGateway('id', 'my_table', $adapter); -$rowGateway->populate($rowData, true); - -$rowGateway->first_name = 'New Name'; -$rowGateway->save(); - -// or delete this row: -$rowGateway->delete(); -``` - -The workflow described above is greatly simplified when RowGateway is used in conjunction with the -TableGateway feature. What this achieves is a Table Gateway object that when select()'ing from a -table, will produce a ResultSet that is then capable of producing valid Row Gateway objects. Its -usage looks like this: - -```php -use Zend\Db\TableGateway\Feature\RowGatewayFeature; -use Zend\Db\TableGateway\TableGateway; - -$table = new TableGateway('artist', $adapter, new RowGatewayFeature('id')); -$results = $table->select(['id' => 2]); - -$artistRow = $results->current(); -$artistRow->name = 'New Name'; -$artistRow->save(); -``` - -## ActiveRecord Style Objects - -If you wish to have custom behaviour for your RowGateway objects (essentially making them behave -similarly to the ActiveRecord pattern), pass a prototype object implementing the -`RowGatewayInterface` to the `RowGatewayFeature` constructor instead of a primary key: - -```php -use Zend\Db\TableGateway\Feature\RowGatewayFeature; -use Zend\Db\TableGateway\TableGateway; -use Zend\Db\RowGateway\RowGatewayInterface; - -class Artist implements RowGatewayInterface -{ - protected $adapter; - - public function __construct($adapter) - { - $this->adapter = $adapter; - } - - // ... save() and delete() implementations -} - -$table = new TableGateway('artist', $adapter, new RowGatewayFeature(new Artist($adapter))); -``` diff --git a/doc/book/zend.db.sql.md b/doc/book/zend.db.sql.md deleted file mode 100644 index cee4bbbeb9..0000000000 --- a/doc/book/zend.db.sql.md +++ /dev/null @@ -1,665 +0,0 @@ -# Zend\\Db\\Sql - -`Zend\Db\Sql` is a SQL abstraction layer for building platform specific SQL queries via an -object-oriented API. The end result of an `Zend\Db\Sql` object will be to either produce a Statement -and Parameter container that represents the target query, or a full string that can be directly -executed against the database platform. To achieve this, `Zend\Db\Sql` objects require a -`Zend\Db\Adapter\Adapter` object in order to produce the desired results. - -## Zend\\Db\\Sql\\Sql (Quickstart) - -As there are four primary tasks associated with interacting with a database (from the DML, or Data -Manipulation Language): selecting, inserting, updating and deleting. As such, there are four primary -objects that developers can interact or building queries, `Zend\Db\Sql\Select`, `Insert`, `Update` -and `Delete`. - -Since these four tasks are so closely related, and generally used together within the same -application, `Zend\Db\Sql\Sql` objects help you create them and produce the result you are -attempting to achieve. - -```php -use Zend\Db\Sql\Sql; - -$sql = new Sql($adapter); -$select = $sql->select(); // @return Zend\Db\Sql\Select -$insert = $sql->insert(); // @return Zend\Db\Sql\Insert -$update = $sql->update(); // @return Zend\Db\Sql\Update -$delete = $sql->delete(); // @return Zend\Db\Sql\Delete -``` - -As a developer, you can now interact with these objects, as described in the sections below, to -specialize each query. Once they have been populated with values, they are ready to either be -prepared or executed. - -To prepare (using a Select object): - -```php -use Zend\Db\Sql\Sql; - -$sql = new Sql($adapter); -$select = $sql->select(); -$select->from('foo'); -$select->where(['id' => 2]); - -$statement = $sql->prepareStatementForSqlObject($select); -$results = $statement->execute(); -``` - -To execute (using a Select object) - -```php -use Zend\Db\Sql\Sql; - -$sql = new Sql($adapter); -$select = $sql->select(); -$select->from('foo'); -$select->where(['id' => 2]); - -$selectString = $sql->buildSqlString($select); -$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE); -``` - -`Zend\\Db\\Sql\\Sql` objects can also be bound to a particular table so that in getting a select, -insert, update, or delete object, they are all primarily seeded with the same table when produced. - -```php -use Zend\Db\Sql\Sql; - -$sql = new Sql($adapter, 'foo'); -$select = $sql->select(); -$select->where(['id' => 2]); // $select already has the from('foo') applied -``` - -## Zend\\Db\\Sql's Select, Insert, Update and Delete - -Each of these objects implements the following (2) interfaces: - -```php -interface PreparableSqlInterface -{ - public function prepareStatement(Adapter $adapter, StatementInterface $statement); -} - -interface SqlInterface -{ - public function getSqlString(PlatformInterface $adapterPlatform = null); -} -``` - -These are the functions you can call to either produce (a) a prepared statement, or (b) a string to -be executed. - -## Zend\\Db\\Sql\\Select - -`Zend\Db\Sql\Select` is an object with the primary function of presenting a unified API for building -platform specific SQL SELECT queries. The class can be instantiated and consumed without -`Zend\Db\Sql\Sql`: - -```php -use Zend\Db\Sql\Select; - -$select = new Select(); -// or, to produce a $select bound to a specific table -$select = new Select('foo'); -``` - -If a table is provided to the Select object, then `from()` cannot be called later to change the name -of the table. - -Once you have a valid Select object, the following API can be used to further specify various select -statement parts: - -```php -class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface -{ - const JOIN_INNER = 'inner'; - const JOIN_OUTER = 'outer'; - const JOIN_LEFT = 'left'; - const JOIN_RIGHT = 'right'; - const SQL_STAR = '*'; - const ORDER_ASCENDING = 'ASC'; - const ORDER_DESCENDING = 'DESC'; - - public $where; // @param Where $where - - public function __construct($table = null); - public function from($table); - public function columns(array $columns, $prefixColumnsWithTable = true); - public function join($name, $on, $columns = self::SQL_STAR, $type = self::JOIN_INNER); - public function where($predicate, $combination = Predicate\PredicateSet::OP_AND); - public function group($group); - public function having($predicate, $combination = Predicate\PredicateSet::OP_AND); - public function order($order); - public function limit($limit); - public function offset($offset); -} -``` - -### from(): - -```php -// as a string: -$select->from('foo'); - -// as an array to specify an alias: -// produces SELECT "t".* FROM "table" AS "t" - -$select->from(['t' => 'table']); - -// using a Sql\TableIdentifier: -// same output as above - -$select->from(new TableIdentifier(['t' => 'table'])); -``` - -### columns(): - -```php -// as array of names -$select->columns(['foo', 'bar']); - -// as an associative array with aliases as the keys: -// produces 'bar' AS 'foo', 'bax' AS 'baz' - -$select->columns(['foo' => 'bar', 'baz' => 'bax']); -``` - -### join(): - -```php -$select->join( - 'foo', // table name - 'id = bar.id', // expression to join on (will be quoted by platform object before insertion), - ['bar', 'baz'], // (optional) list of columns, same requirements as columns() above - $select::JOIN_OUTER // (optional), one of inner, outer, left, right also represented by constants in the API -); - -$select - ->from(['f' => 'foo']) // base table - ->join( - ['b' => 'bar'], // join table with alias - 'f.foo_id = b.foo_id' // join expression - ); -``` - -### where(), having(): - -The `Zend\Db\Sql\Select` object provides bit of flexibility as it regards to what kind of parameters -are acceptable when calling where() or having(). The method signature is listed as: - -```php -/** - * Create where clause - * - * @param Where|\Closure|string|array $predicate - * @param string $combination One of the OP_* constants from Predicate\PredicateSet - * @return Select - */ -public function where($predicate, $combination = Predicate\PredicateSet::OP_AND); -``` - -As you can see, there are a number of different ways to pass criteria to both having() and where(). - -If you provide a `Zend\Db\Sql\Where` object to where() or a `Zend\Db\Sql\Having` object to having(), -the internal objects for Select will be replaced completely. When the where/having() is processed, -this object will be iterated to produce the WHERE or HAVING section of the SELECT statement. - -If you provide a `Closure` to where() or having(), this function will be called with the Select's -`Where` object as the only parameter. So the following is possible: - -```php -$spec = function (Where $where) { - $where->like('username', 'ralph%'); -}; - -$select->where($spec); -``` - -If you provide a string, this string will be used to instantiate a -`Zend\Db\Sql\Predicate\Expression` object so that it's contents will be applied as is. This means -that there will be no quoting in the fragment provided. - -Consider the following code: - -```php -// SELECT "foo".* FROM "foo" WHERE x = 5 -$select->from('foo')->where('x = 5'); -``` - -If you provide an array who's values are keyed by an integer, the value can either be a string that -will be then used to build a `Predicate\Expression` or any object that implements -`Predicate\PredicateInterface`. These objects are pushed onto the Where stack with the $combination -provided. - -Consider the following code: - -```php -// SELECT "foo".* FROM "foo" WHERE x = 5 AND y = z -$select->from('foo')->where(['x = 5', 'y = z']); -``` - -If you provide an array with values keyed as strings, these values will be handled in the -following: - -- PHP value nulls will be made into a `Predicate\IsNull` object -- PHP value array()s will be made into a `Predicate\In` object -- PHP value strings will be made into a `Predicate\Operator` object such that the string key will be - identifier, and the value will target value. - -Consider the following code: - -```php -// SELECT "foo".* FROM "foo" WHERE "c1" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL -$select->from('foo')->where([ - 'c1' => null, - 'c2' => [1, 2, 3], - new \Zend\Db\Sql\Predicate\IsNotNull('c3'), -]); -``` - -### order(): - -```php -$select = new Select; -$select->order('id DESC'); // produces 'id' DESC - -$select = new Select; -$select - ->order('id DESC') - ->order('name ASC, age DESC'); // produces 'id' DESC, 'name' ASC, 'age' DESC - -$select = new Select; -$select->order(['name ASC', 'age DESC']); // produces 'name' ASC, 'age' DESC -``` - -### limit() and offset(): - -```php -$select = new Select; -$select->limit(5); // always takes an integer/numeric -$select->offset(10); // similarly takes an integer/numeric -``` - -## Zend\\Db\\Sql\\Insert - -The Insert API: - -```php -class Insert implements SqlInterface, PreparableSqlInterface -{ - const VALUES_MERGE = 'merge'; - const VALUES_SET = 'set'; - - public function __construct($table = null); - public function into($table); - public function columns(array $columns); - public function values(array $values, $flag = self::VALUES_SET); -} -``` - -Similarly to Select objects, the table can be set at construction time or via into(). - -### columns(): - -```php -$insert->columns(['foo', 'bar']); // set the valid columns -``` - -### values(): - -```php -// default behavior of values is to set the values -// successive calls will not preserve values from previous calls -$insert->values([ - 'col_1' => 'value1', - 'col_2' => 'value2', -]); -``` - -```php -// merging values with previous calls -$insert->values(['col_2' => 'value2'], $insert::VALUES_MERGE); -``` - -## Zend\\Db\\Sql\\Update - -```php -class Update -{ - const VALUES_MERGE = 'merge'; - const VALUES_SET = 'set'; - - public $where; // @param Where $where - public function __construct($table = null); - public function table($table); - public function set(array $values, $flag = self::VALUES_SET); - public function where($predicate, $combination = Predicate\PredicateSet::OP_AND); -} -``` - -### set(): - -```php -$update->set(['foo' => 'bar', 'baz' => 'bax']); -``` - -### where(): - -See where section below. - -## Zend\\Db\\Sql\\Delete - -```php -class Delete -{ - public $where; // @param Where $where - public function __construct($table = null); - public function from($table); - public function where($predicate, $combination = Predicate\PredicateSet::OP_AND); -} -``` - -### where(): - -See where section below. - -## Zend\\Db\\Sql\\Where & Zend\\Db\\Sql\\Having - -In the following, we will talk about Where, Having is implies as being the same API. - -Effectively, Where and Having extend from the same base object, a Predicate (and PredicateSet). All -of the parts that make up a where or having that are and'ed or or'd together are called predicates. -The full set of predicates is called a PredicateSet. This object set generally contains the values -(and identifiers) separate from the fragment they belong to until the last possible moment when the -statement is either used to be prepared (parameteritized), or executed. In parameterization, the -parameters will be replaced with their proper placeholder (a named or positional parameter), and the -values stored inside a Adapter\\ParameterContainer. When executed, the values will be interpolated -into the fragments they belong to and properly quoted. - -It is important to know that in this API, a distinction is made between what elements are considered -identifiers (TYPE\_IDENTIFIER) and which of those is a value (TYPE\_VALUE). There is also a special -use case type for literal values (TYPE\_LITERAL). These are all exposed via the -`Zend\Db\Sql\ExpressionInterface` interface. - -> ### Literals -> -> In ZF 2.1, an actual `Literal` type was added. `Zend\Db\Sql` now makes the distinction that Literals -> will not have any parameters that need interpolating whereas it is expected that `Expression` -> objects *might* have parameters that need interpolating. In cases where there are parameters in an -> `Expression`, `Zend\Db\Sql\AbstractSql` will do its best to identify placeholders when the -> Expression is processed during statement creation. In short, if you don't have parameters, use -> `Literal` objects. - -The Zend\\Db\\Sql\\Where (Predicate/PredicateSet) API: - -```php -// Where & Having: -class Predicate extends PredicateSet -{ - public $and; - public $or; - public $AND; - public $OR; - public $NEST; - public $UNNEST; - - public function nest(); - public function setUnnest(Predicate $predicate); - public function unnest(); - public function equalTo( - $left, - $right, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function notEqualTo( - $left, - $right, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function lessThan( - $left, - $right, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function greaterThan( - $left, - $right, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function lessThanOrEqualTo( - $left, - $right, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function greaterThanOrEqualTo( - $left, - $right, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function like($identifier, $like); - public function literal($literal); - public function expression($expression, $parameter); - public function isNull($identifier); - public function isNotNull($identifier); - public function in($identifier, array $valueSet = []); - public function between($identifier, $minValue, $maxValue); - - // Inherited From PredicateSet - - public function addPredicate(PredicateInterface $predicate, $combination = null); - public function getPredicates(); - public function orPredicate(PredicateInterface $predicate); - public function andPredicate(PredicateInterface $predicate); - public function getExpressionData(); - public function count(); -} -``` - -Each method in the Where API will produce a corresponding Predicate object of a similarly named -type, described below, with the full API of the object: - -### equalTo(), lessThan(), greaterThan(), lessThanOrEqualTo(), greaterThanOrEqualTo(): - -```php -$where->equalTo('id', 5); - -// same as the following workflow -$where->addPredicate( - new Predicate\Operator($left, Operator::OPERATOR_EQUAL_TO, $right, $leftType, $rightType) -); - -class Operator implements PredicateInterface -{ - const OPERATOR_EQUAL_TO = '='; - const OP_EQ = '='; - const OPERATOR_NOT_EQUAL_TO = '!='; - const OP_NE = '!='; - const OPERATOR_LESS_THAN = '<'; - const OP_LT = '<'; - const OPERATOR_LESS_THAN_OR_EQUAL_TO = '<='; - const OP_LTE = '<='; - const OPERATOR_GREATER_THAN = '>'; - const OP_GT = '>'; - const OPERATOR_GREATER_THAN_OR_EQUAL_TO = '>='; - const OP_GTE = '>='; - - public function __construct( - $left = null, - $operator = self::OPERATOR_EQUAL_TO, - $right = null, - $leftType = self::TYPE_IDENTIFIER, - $rightType = self::TYPE_VALUE - ); - public function setLeft($left); - public function getLeft(); - public function setLeftType($type); - public function getLeftType(); - public function setOperator($operator); - public function getOperator(); - public function setRight($value); - public function getRight(); - public function setRightType($type); - public function getRightType(); - public function getExpressionData(); -} -``` - -### like($identifier, $like): - -```php -$where->like($identifier, $like): - -// same as -$where->addPredicate( - new Predicate\Like($identifier, $like) -); - -// full API - -class Like implements PredicateInterface -{ - public function __construct($identifier = null, $like = null); - public function setIdentifier($identifier); - public function getIdentifier(); - public function setLike($like); - public function getLike(); -} -``` - -### literal($literal); - -```php -$where->literal($literal); - -// same as -$where->addPredicate( - new Predicate\Literal($literal) -); - -// full API -class Literal implements ExpressionInterface, PredicateInterface -{ - const PLACEHOLDER = '?'; - public function __construct($literal = ''); - public function setLiteral($literal); - public function getLiteral(); -} -``` - -### expression($expression, $parameter); - -```php -$where->expression($expression, $parameter); - -// same as -$where->addPredicate( - new Predicate\Expression($expression, $parameter) -); - -// full API -class Expression implements ExpressionInterface, PredicateInterface -{ - const PLACEHOLDER = '?'; - - public function __construct( - $expression = null, - $valueParameter = null - /* [, $valueParameter, ... ] */ - ); - public function setExpression($expression); - public function getExpression(); - public function setParameters($parameters); - public function getParameters(); - public function setTypes(array $types); - public function getTypes(); -} -``` - -### isNull($identifier); - -```php -$where->isNull($identifier); - -// same as -$where->addPredicate( - new Predicate\IsNull($identifier) -); - -// full API -class IsNull implements PredicateInterface -{ - public function __construct($identifier = null); - public function setIdentifier($identifier); - public function getIdentifier(); -} -``` - -### isNotNull($identifier); - -```php -$where->isNotNull($identifier); - -// same as -$where->addPredicate( - new Predicate\IsNotNull($identifier) -); - -// full API -class IsNotNull implements PredicateInterface -{ - public function __construct($identifier = null); - public function setIdentifier($identifier); - public function getIdentifier(); -} -``` - -### in($identifier, array $valueSet = []); - -```php -$where->in($identifier, array $valueSet = []); - -// same as -$where->addPredicate( - new Predicate\In($identifier, $valueSet) -); - -// full API -class In implements PredicateInterface -{ - public function __construct($identifier = null, array $valueSet = []); - public function setIdentifier($identifier); - public function getIdentifier(); - public function setValueSet(array $valueSet); - public function getValueSet(); -} -``` - -### between($identifier, $minValue, $maxValue); - -```php -$where->between($identifier, $minValue, $maxValue); - -// same as -$where->addPredicate( - new Predicate\Between($identifier, $minValue, $maxValue) -); - -// full API -class Between implements PredicateInterface -{ - public function __construct($identifier = null, $minValue = null, $maxValue = null); - public function setIdentifier($identifier); - public function getIdentifier(); - public function setMinValue($minValue); - public function getMinValue(); - public function setMaxValue($maxValue); - public function getMaxValue(); - public function setSpecification($specification); -} -``` diff --git a/doc/book/zend.db.table-gateway.md b/doc/book/zend.db.table-gateway.md deleted file mode 100644 index 0c04cc52e8..0000000000 --- a/doc/book/zend.db.table-gateway.md +++ /dev/null @@ -1,186 +0,0 @@ -# Zend\\Db\\TableGateway - -The Table Gateway object is intended to provide an object that represents a table in a database, and -the methods of this object mirror the most common operations on a database table. In code, the -interface for such an object looks like this: - -```php -interface Zend\Db\TableGateway\TableGatewayInterface -{ - public function getTable(); - public function select($where = null); - public function insert($set); - public function update($set, $where = null); - public function delete($where); -} -``` - -There are two primary implementations of the `TableGatewayInterface` that are of the most useful: -`AbstractTableGateway` and `TableGateway`. The `AbstractTableGateway` is an abstract basic -implementation that provides functionality for `select()`, `insert()`, `update()`, `delete()`, as -well as an additional API for doing these same kinds of tasks with explicit SQL objects. These -methods are `selectWith()`, `insertWith()`, `updateWith()` and `deleteWith()`. In addition, -AbstractTableGateway also implements a "Feature" API, that allows for expanding the behaviors of the -base `TableGateway` implementation without having to extend the class with this new functionality. -The `TableGateway` concrete implementation simply adds a sensible constructor to the -`AbstractTableGateway` class so that out-of-the-box, `TableGateway` does not need to be extended in -order to be consumed and utilized to its fullest. - -## Basic Usage - -The quickest way to get up and running with `Zend\Db\TableGateway` is to configure and utilize the -concrete implementation of the `TableGateway`. The API of the concrete `TableGateway` is: - -```php -class TableGateway extends AbstractTableGateway -{ - public $lastInsertValue; - public $table; - public $adapter; - - public function __construct( - $table, - Adapter $adapter, - $features = null, - ResultSet $resultSetPrototype = null, - Sql $sql = null - ); - - /** Inherited from AbstractTableGateway */ - - public function isInitialized(); - public function initialize(); - public function getTable(); - public function getAdapter(); - public function getColumns(); - public function getFeatureSet(); - public function getResultSetPrototype(); - public function getSql(); - public function select($where = null); - public function selectWith(Select $select); - public function insert($set); - public function insertWith(Insert $insert); - public function update($set, $where = null); - public function updateWith(Update $update); - public function delete($where); - public function deleteWith(Delete $delete); - public function getLastInsertValue(); -} -``` - -The concrete `TableGateway` object practices constructor injection for getting dependencies and -options into the instance. The table name and an instance of an Adapter are all that is needed to -setup a working `TableGateway` object. - -Out of the box, this implementation makes no assumptions about table structure or metadata, and when -`select()` is executed, a simple ResultSet object with the populated Adapter's Result (the -datasource) will be returned and ready for iteration. - -```php -use Zend\Db\TableGateway\TableGateway; - -$projectTable = new TableGateway('project', $adapter); -$rowset = $projectTable->select(['type' => 'PHP']); - -echo 'Projects of type PHP: '; -foreach ($rowset as $projectRow) { - echo $projectRow['name'] . PHP_EOL; -} - -// or, when expecting a single row: -$artistTable = new TableGateway('artist', $adapter); -$rowset = $artistTable->select(['id' => 2]); -$artistRow = $rowset->current(); - -var_dump($artistRow); -``` - -The `select()` method takes the same arguments as `Zend\Db\Sql\Select::where()` with the addition of -also being able to accept a closure, which in turn, will be passed the current Select object that is -being used to build the SELECT query. The following usage is possible: - -```php -use Zend\Db\TableGateway\TableGateway; -use Zend\Db\Sql\Select; - -$artistTable = new TableGateway('artist', $adapter); - -// search for at most 2 artists who's name starts with Brit, ascending -$rowset = $artistTable->select(function (Select $select) { - $select->where->like('name', 'Brit%'); - $select->order('name ASC')->limit(2); -}); -``` - -## TableGateway Features - -The Features API allows for extending the functionality of the base `TableGateway` object without -having to polymorphically extend the base class. This allows for a wider array of possible mixing -and matching of features to achieve a particular behavior that needs to be attained to make the base -implementation of `TableGateway` useful for a particular problem. - -With the `TableGateway` object, features should be injected though the constructor. The constructor -can take Features in 3 different forms: as a single feature object, as a FeatureSet object, or as an -array of Feature objects. - -There are a number of features built-in and shipped with Zend\\Db: - -- GlobalAdapterFeature: the ability to use a global/static adapter without needing to inject it into - a `TableGateway` instance. This is more useful when you are extending the `AbstractTableGateway` - implementation: - - ```php - use Zend\Db\TableGateway\AbstractTableGateway; - use Zend\Db\TableGateway\Feature; - - class MyTableGateway extends AbstractTableGateway - { - public function __construct() - { - $this->table = 'my_table'; - $this->featureSet = new Feature\FeatureSet(); - $this->featureSet->addFeature(new Feature\GlobalAdapterFeature()); - $this->initialize(); - } - } - - // elsewhere in code, in a bootstrap - Zend\Db\TableGateway\Feature\GlobalAdapterFeature::setStaticAdapter($adapter); - - // in a controller, or model somewhere - $table = new MyTableGateway(); // adapter is statically loaded - ``` - -- MasterSlaveFeature: the ability to use a master adapter for insert(), update(), and delete() while - using a slave adapter for all select() operations. - - ```php - $table = new TableGateway('artist', $adapter, new Feature\MasterSlaveFeature($slaveAdapter)); - ``` - -- MetadataFeature: the ability populate `TableGateway` with column information from a Metadata - object. It will also store the primary key information in case RowGatewayFeature needs to consume - this information. - - ```php - $table = new TableGateway('artist', $adapter, new Feature\MetadataFeature()); - ``` - -- EventFeature: the ability utilize a `TableGateway` object with Zend\\EventManager and to be able - to subscribe to various events in a `TableGateway` lifecycle. - - ```php -$table = new TableGateway('artist', $adapter, new Feature\EventFeature($eventManagerInstance)); -``` - -- RowGatewayFeature: the ability for `select()` to return a ResultSet object that upon iteration - will return a `RowGateway` object for each row. - - ```php - $table = new TableGateway('artist', $adapter, new Feature\RowGatewayFeature('id')); - $results = $table->select(['id' => 2]); - - $artistRow = $results->current(); - $artistRow->name = 'New Name'; - $artistRow->save(); - ``` diff --git a/doc/bookdown.json b/doc/bookdown.json deleted file mode 100644 index 3a60f4f9d1..0000000000 --- a/doc/bookdown.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title": "Zend\\Db", - "target": "html/", - "content": [ - "book/zend.db.adapter.md", - "book/zend.db.result-set.md", - "book/zend.db.sql.md", - "book/zend.db.sql.ddl.md", - "book/zend.db.table-gateway.md", - "book/zend.db.row-gateway.md", - "book/zend.db.metadata.md" - ] -} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..7ad5649a04 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,16 @@ +docs_dir: doc/book +site_dir: doc/html +pages: + - index.md + - Reference: + - Adapters: adapter.md + - "Result Sets": result-set.md + - "SQL Abstraction": sql.md + - "DDL Abstraction": sql-ddl.md + - "Table Gateways": table-gateway.md + - "Row Gateways": row-gateway.md + - "RDBMS Metadata": metadata.md +site_name: zend-db +site_description: zend-db +repo_url: 'https://github.com/zendframework/zend-db' +copyright: 'Copyright (c) 2016 Zend Technologies USA Inc.' From 33dec5d4a53a9c83501a8af01616d339fb85b533 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 27 Apr 2016 17:49:05 -0500 Subject: [PATCH 2/4] Added documentation build automation --- .gitignore | 2 ++ .travis.yml | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 078e74781b..f06217ca1a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,7 @@ clover.xml composer.lock coveralls-upload.json phpunit.xml +doc/html/ tmp/ vendor/ +zf-mkdoc-theme/ diff --git a/.travis.yml b/.travis.yml index 104696dc66..18e9fac5d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,17 +4,24 @@ language: php branches: except: - - /^release-.*$/ + - /^release-\d+\.\d+\.\d+.*$/ - /^ghgfk-.*$/ cache: directories: - $HOME/.composer/cache + - $HOME/.local + - zf-mkdoc-theme env: global: - TESTS_ZEND_DB_ADAPTER_DRIVER_MYSQL_HOSTNAME=localhost - TESTS_ZEND_DB_ADAPTER_DRIVER_PGSQL_HOSTNAME=localhost + - SITE_URL: https://zendframework.github.io/zend-db + - GH_USER_NAME: "Matthew Weier O'Phinney" + - GH_USER_EMAIL: matthew@weierophinney.net + - GH_REF: github.com/zendframework/zend-db.git + - secure: "L1Tnu2NIZpYU/P2JvOQAyOjAR8X52yVxW18b6x0qxT8d3LxHpunCFHwI6VV5D4llKJCRwyGdgKsVKNfroE/+368P2LQ71m6REzWGpW6q4uRQKdNhy2yjyDRKr5dPow+mEYKgGP8jbcmI6rxVDFpJTtPMTn/6PTmKmiaF6H0GCUFohy5tuGWkoujwkY8T3UjoRHqFv86L47P+UXlj5KmnYPYKTuNsiQNHJcpI21uA5UIBeGlazUJdi3a2UmRktg1SbAJcPcr87WxWd2Xe29ZaQLmpS93cCgq3e7skfWWptjEzp4Sr+mql67ZOO13wgNHK+K6uA2jvtdes+TrUA9+XeONZ3JGoQGCcpGl+j78Oi8Zt8jrz2+shQip7RvnT1grX6WKquhtsGR9woFsEh4WGbE+4B53fTVUDWuOdn5ywBjR1fJd90GbwMuq6fjB4m+LRL7dfc9Wap/zFPzn6k9YsdAAZHJfgT1inK+xp7g4QypVFgpjm5OVbipbtt+N/Ag8bySYB9ykIjD/1xmfdLy/HOyQobi+jQX8ktxgFfQrWb6N8ZuJNzC6D1t8opzdFl5Gzd8PKAieArkXmtnr2BGTdzDPmbsJWoHZ2XGldWtqcr7yYOfyXd7+szlGae1w62kxy0k293EleheXScSn3mc5RLChj/IF5RpebIc31nDU6DQs=" matrix: fast_finish: true @@ -81,6 +88,10 @@ script: - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit --coverage-clover clover.xml ; fi - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then ./vendor/bin/phpunit ; fi - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run ; fi + - if [[ $DEPLOY_DOCS == "true" && "$TRAVIS_TEST_RESULT" == "0" ]]; then wget -O theme-installer.sh "https://raw.githubusercontent.com/zendframework/zf-mkdoc-theme/master/theme-installer.sh" ; chmod 755 theme-installer.sh ; ./theme-installer.sh ; fi + +after_success: + - if [[ $DEPLOY_DOCS == "true" ]]; then echo "Preparing to build and deploy documentation" ; ./zf-mkdoc-theme/deploy.sh ; echo "Completed deploying documentation" ; fi after_script: - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/coveralls ; fi From d548c5ad1d2309f499eec11a3acda3024ebb1929 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 27 Apr 2016 17:50:55 -0500 Subject: [PATCH 3/4] Correct typo in landing page --- doc/book/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/book/index.html b/doc/book/index.html index b2ef8a4e93..0d001ba806 100644 --- a/doc/book/index.html +++ b/doc/book/index.html @@ -4,7 +4,7 @@

zend-db

Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations.

-
$ composer require zendframework/zend-stdlib
+
$ composer require zendframework/zend-db
From 0fdd2e264d7ac699085d1e10e8fa0829253bbda2 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 28 Apr 2016 08:00:03 -0500 Subject: [PATCH 4/4] Added CHANGELOG for #110 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fecbe721..50d30f16d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file, in reverse ### Added -- Nothing. +- [#110](https://github.com/zendframework/zend-db/pull/110) prepared the + documentation for publication at https://zendframework.github.io/zend-db/ ### Deprecated