diff --git a/README.md b/README.md index c20e5fe4b..c2e3da419 100644 --- a/README.md +++ b/README.md @@ -88,5 +88,26 @@ $ docker compose exec php composer test-integration - File issues at https://github.com/laminas/laminas-db/issues - Documentation is at https://docs.laminas.dev/laminas-db/ +## Working on the documentation + +To work on the documentation, you need to have basic familiarity with both [the AsciiDoc file format][asciidoc] and with [the Antora SSG (Static Site Generator)][antora]. + +> [!NOTE] +> Learning AsciiDoc sometimes concerns people, as there is the need to learn something new and we’re all pretty busy alread. +> However, if you’re familiar with Markdown, and you likely are, then there’s precious little that you’ll have to learn. +> AsciiDoc understands just about all Markdown. +> So, you could write Markdown and not have to learn AsciiDoc. + +But, you don’t need to know all that much: + +- The AsciiDoc files that contain the documentation are stored in _docs-site/modules/ROOT/pages_. +- The supporting images for the documentation are stored in _docs-site/modules/ROOT/images_. +- You put code examples, if you want to store them separately, in _docs-site/modules/ROOT/examples_. +- To rebuild the docs, from the docs-site directory, run `npx antora antora-playbook.yml`. + You’ll then find the generated documentation in docs-site/build/site/. + Open _index.html_ in that directory in your browser of choice to start reading the documentation. + +[antora]: https://docs.antora.org/antora/latest/ +[asciidoc]: https://docs.antora.org/antora/latest/ [docker-compose]: https://docs.docker.com/compose/intro/features-uses/ -[deploy-with-docker-compose]: https://deploywithdockercompose.com \ No newline at end of file +[deploy-with-docker-compose]: https://deploywithdockercompose.com diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 000000000..779fe4dd2 --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,5 @@ +name: laminas-db-documentation +version: ~ +title: The laminas-db documentation +nav: + - modules/ROOT/nav.adoc diff --git a/docs/book/adapters/adapter-aware-trait.md b/docs/book/adapters/adapter-aware-trait.md deleted file mode 100644 index 2f8fbfb55..000000000 --- a/docs/book/adapters/adapter-aware-trait.md +++ /dev/null @@ -1,131 +0,0 @@ -# AdapterAwareTrait - -The trait `Laminas\Db\Adapter\AdapterAwareTrait`, which provides implementation -for `Laminas\Db\Adapter\AdapterAwareInterface`, and allowed removal of -duplicated implementations in several components of Laminas or in custom -applications. - -The interface defines only the method `setDbAdapter()` with one parameter for an -instance of `Laminas\Db\Adapter\Adapter`: - -```php -public function setDbAdapter(\Laminas\Db\Adapter\Adapter $adapter) : self; -``` - -## Basic Usage - -### Create Class and Add Trait - -```php -use Laminas\Db\Adapter\AdapterAwareTrait; -use Laminas\Db\Adapter\AdapterAwareInterface; - -class Example implements AdapterAwareInterface -{ - use AdapterAwareTrait; -} -``` - -### Create and Set Adapter - -[Create a database adapter](../adapter.md#creating-an-adapter-using-configuration) and set the adapter to the instance of the `Example` -class: - -```php -$adapter = new Laminas\Db\Adapter\Adapter([ - 'driver' => 'Pdo_Sqlite', - 'database' => 'path/to/sqlite.db', -]); - -$example = new Example(); -$example->setAdapter($adapter); -``` - -## AdapterServiceDelegator - -The [delegator](https://docs.laminas.dev/laminas-servicemanager/delegators/) -`Laminas\Db\Adapter\AdapterServiceDelegator` can be used to set a database -adapter via the [service manager of laminas-servicemanager](https://docs.laminas.dev/laminas-servicemanager/quick-start/). - -The delegator tries to fetch a database adapter via the name -`Laminas\Db\Adapter\AdapterInterface` from the service container and sets the -adapter to the requested service. The adapter itself must be an instance of -`Laminas\Db\Adapter\Adapter`. - -> ### Integration for Mezzio and laminas-mvc based Applications -> -> In a Mezzio or laminas-mvc based application the database adapter is already -> registered during the installation with the laminas-component-installer. - -### Create Class and Use Trait - -Create a class and add the trait `AdapterAwareTrait`. - -```php -use Laminas\Db\Adapter\Adapter; -use Laminas\Db\Adapter\AdapterInterface; - -class Example implements AdapterAwareInterface -{ - use AdapterAwareTrait; - - public function getAdapter() : ?Adapter - { - return $this->adapter; - } -} -``` - -(A getter method is also added for demonstration.) - -### Create and Configure Service Manager - -Create and [configured the service manager](https://docs.laminas.dev/laminas-servicemanager/configuring-the-service-manager/): - -```php -use Interop\Container\ContainerInterface; -use Laminas\Db\Adapter\AdapterInterface; -use Laminas\Db\Adapter\AdapterServiceDelegator; -use Laminas\Db\Adapter\AdapterAwareTrait; -use Laminas\Db\Adapter\AdapterAwareInterface; - -$serviceManager = new Laminas\ServiceManager\ServiceManager([ - 'factories' => [ - // Database adapter - AdapterInterface::class => static function(ContainerInterface $container) { - return new Laminas\Db\Adapter\Adapter([ - 'driver' => 'Pdo_Sqlite', - 'database' => 'path/to/sqlite.db', - ]); - } - ], - 'invokables' => [ - // Example class - Example::class => Example::class, - ], - 'delegators' => [ - // Delegator for Example class to set the adapter - Example::class => [ - AdapterServiceDelegator::class, - ], - ], -]); -``` - -### Get Instance of Class - -[Retrieving an instance](https://docs.laminas.dev/laminas-servicemanager/quick-start/#3-retrieving-objects) -of the `Example` class with a database adapter: - -```php -/** @var Example $example */ -$example = $serviceManager->get(Example::class); - -var_dump($example->getAdapter() instanceof Laminas\Db\Adapter\Adapter); // true -``` - -## Concrete Implementations - -The validators [`Db\RecordExists` and `Db\NoRecordExists`](https://docs.laminas.dev/laminas-validator/validators/db/) -implements the trait and the plugin manager of [laminas-validator](https://docs.laminas.dev/laminas-validator/) -includes the delegator to set the database adapter for both validators. diff --git a/docs/book/index.html b/docs/book/index.html deleted file mode 100644 index 4f5faa578..000000000 --- a/docs/book/index.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-

laminas-db

- -

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

- -
$ composer require laminas/laminas-db
-
-
- diff --git a/docs/book/index.md b/docs/book/index.md deleted file mode 120000 index fe8400541..000000000 --- a/docs/book/index.md +++ /dev/null @@ -1 +0,0 @@ -../../README.md \ No newline at end of file diff --git a/docs/book/sql-ddl.md b/docs/book/sql-ddl.md deleted file mode 100644 index 299a80002..000000000 --- a/docs/book/sql-ddl.md +++ /dev/null @@ -1,203 +0,0 @@ -# DDL Abstraction - -`Laminas\Db\Sql\Ddl` is a sub-component of `Laminas\Db\Sql` allowing creation of DDL -(Data Definition Language) SQL statements. When combined with a platform -specific `Laminas\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 - -## Creating Tables - -Like `Laminas\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 Laminas\Db\Sql\Ddl; -use Laminas\Db\Sql\TableIdentifier; - -$table = new Ddl\CreateTable(); - -// With a table name: -$table = new Ddl\CreateTable('bar'); - -// With a schema name "foo": -$table = new Ddl\CreateTable(new TableIdentifier('bar', 'foo')); - -// Optionally, as a temporary table: -$table = new Ddl\CreateTable('bar', true); -``` - -You can also set the table after instantiation: - -```php -$table->setTable('bar'); -``` - -Currently, columns are added by creating a column object (described in the -[data type table below](#currently-supported-data-types)): - -```php -use Laminas\Db\Sql\Ddl\Column; - -$table->addColumn(new Column\Integer('id')); -$table->addColumn(new Column\Varchar('name', 255)); -``` - -Beyond adding columns to a table, you may also add constraints: - -```php -use Laminas\Db\Sql\Ddl\Constraint; - -$table->addConstraint(new Constraint\PrimaryKey('id')); -$table->addConstraint( - new Constraint\UniqueKey(['name', 'foo'], 'my_unique_key') -); -``` - -You can also use the `AUTO_INCREMENT` attribute for MySQL: - -```php -use Laminas\Db\Sql\Ddl\Column; - -$column = new Column\Integer('id'); -$column->setOption('AUTO_INCREMENT', true); -``` - -## Altering Tables - -Similar to `CreateTable`, you may also use `AlterTable` instances: - -```php -use Laminas\Db\Sql\Ddl; -use Laminas\Db\Sql\TableIdentifier; - -$table = new Ddl\AlterTable(); - -// With a table name: -$table = new Ddl\AlterTable('bar'); - -// With a schema name "foo": -$table = new Ddl\AlterTable(new TableIdentifier('bar', 'foo')); - -// 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 have the ability to *alter* existing columns: - -```php -use Laminas\Db\Sql\Ddl\Column; - -$table->changeColumn('name', Column\Varchar('new_name', 50)); -``` - -You may also *drop* existing columns or constraints: - -```php -$table->dropColumn('foo'); -$table->dropConstraint('my_index'); -``` - -## Dropping Tables - -To drop a table, create a `DropTable` instance: - -```php -use Laminas\Db\Sql\Ddl; -use Laminas\Db\Sql\TableIdentifier; - -// With a table name: -$drop = new Ddl\DropTable('bar'); - -// With a schema name "foo": -$drop = new Ddl\DropTable(new TableIdentifier('bar', 'foo')); -``` - -## Executing DDL Statements - -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: - -```php -use Laminas\Db\Sql\Sql; - -// Existence of $adapter is assumed. -$sql = new Sql($adapter); - -$adapter->query( - $sql->buildSqlString($ddl), - $adapter::QUERY_MODE_EXECUTE -); -``` - -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 `Laminas\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 `Laminas\Db\Sql\Ddl\Column` namespace. Data types must -implement `Laminas\Db\Sql\Ddl\Column\ColumnInterface`. - -In alphabetical order: - -Type | Arguments For Construction ------------------|--------------------------- -BigInteger | `$name`, `$nullable = false`, `$default = null`, `array $options = array()` -Binary | `$name`, `$length`, `nullable = false`, `$default = null`, `array $options = array()` -Blob | `$name`, `$length`, `nullable = false`, `$default = null`, `array $options = array()` -Boolean | `$name` -Char | `$name`, `length` -Column (generic) | `$name = null` -Date | `$name` -DateTime | `$name` -Decimal | `$name`, `$precision`, `$scale = null` -Float | `$name`, `$digits`, `$decimal` (Note: this class is deprecated as of 2.4.0; use Floating instead) -Floating | `$name`, `$digits`, `$decimal` -Integer | `$name`, `$nullable = false`, `default = null`, `array $options = array()` -Text | `$name`, `$length`, `nullable = false`, `$default = null`, `array $options = array()` -Time | `$name` -Timestamp | `$name` -Varbinary | `$name`, `$length` -Varchar | `$name`, `$length` - -Each of the above types can be utilized in any place that accepts a `Column\ColumnInterface` -instance. Currently, this is primarily in `CreateTable::addColumn()` and `AlterTable`'s -`addColumn()` and `changeColumn()` methods. - -## Currently Supported Constraint Types - -These types exist in the `Laminas\Db\Sql\Ddl\Constraint` namespace. Data types -must implement `Laminas\Db\Sql\Ddl\Constraint\ConstraintInterface`. - -In alphabetical order: - -Type | Arguments For Construction ------------|--------------------------- -Check | `$expression`, `$name` -ForeignKey | `$name`, `$column`, `$referenceTable`, `$referenceColumn`, `$onDeleteRule = null`, `$onUpdateRule = null` -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()`. diff --git a/docs/book/sql.md b/docs/book/sql.md deleted file mode 100644 index f0f73d641..000000000 --- a/docs/book/sql.md +++ /dev/null @@ -1,764 +0,0 @@ -# SQL Abstraction - -`Laminas\Db\Sql` is a SQL abstraction layer for building platform-specific SQL -queries via an object-oriented API. The end result of a `Laminas\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, `Laminas\Db\Sql` objects require a -`Laminas\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 `Laminas\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 `Laminas\Db\Sql\Sql` class helps you create them -and produce the result you are attempting to achieve. - -```php -use Laminas\Db\Sql\Sql; - -$sql = new Sql($adapter); -$select = $sql->select(); // returns a Laminas\Db\Sql\Select instance -$insert = $sql->insert(); // returns a Laminas\Db\Sql\Insert instance -$update = $sql->update(); // returns a Laminas\Db\Sql\Update instance -$delete = $sql->delete(); // returns a Laminas\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 Laminas\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 Laminas\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); -``` - -`Laminas\\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 Laminas\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 - -`Laminas\Db\Sql\Select` presents a unified API for building platform-specific SQL -SELECT queries. Instances may be created and consumed without -`Laminas\Db\Sql\Sql`: - -```php -use Laminas\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_FULL_OUTER = 'full 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|TableIdentifier $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(['t' => new TableIdentifier('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' -]); - -// Sql function call on the column -// (produces CONCAT_WS('/', 'bar', 'bax') AS 'foo') -$select->columns([ - 'foo' => new \Laminas\Db\Sql\Expression("CONCAT_WS('/', 'bar', '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, full 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() - -`Laminas\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 `Laminas\Db\Sql\Where` instance to `where()` or a -`Laminas\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 -`Laminas\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 \Laminas\Db\Sql\Predicate\IsNotNull('c3'), -]); -``` - -As another example of complex queries with nested conditions e.g. - -```sql -SELECT * WHERE (column1 is null or column1 = 2) AND (column2 = 3) -``` - -you need to use the `nest()` and `unnest()` methods, as follows: - -```php -$select->where->nest() // bracket opened - ->isNull('column1') - ->or - ->equalTo('column1', '2') - ->unnest(); // bracket closed - ->equalTo('column2', '3'); -``` - -### 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() - -The default behavior of values is to set the values. Successive calls will not -preserve values from previous calls. - -```php -$insert->values([ - 'col_1' => 'value1', - 'col_2' => 'value2', -]); -``` - -To merge values with previous calls, provide the appropriate flag: -`Laminas\Db\Sql\Insert::VALUES_MERGE` - -```php -$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 `Laminas\Db\Sql\ExpressionInterface` -interface. - -> ### Literals -> -> In Laminas 2.1, an actual `Literal` type was added. `Laminas\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`, -> `Laminas\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; -} -``` - -Expression parameters can be supplied either as a single scalar, an array of values, or as an array of value/types for more granular escaping. - -```php -$select - ->from('foo') - ->columns([ - new Expression( - '(COUNT(?) + ?) AS ?', - [ - ['some_column' => ExpressionInterface::TYPE_IDENTIFIER], - [5 => ExpressionInterface::TYPE_VALUE], - ['bar' => ExpressionInterface::TYPE_IDENTIFIER], - ], - ), - ]); - -// Produces SELECT (COUNT("some_column") + '5') AS "bar" FROM "foo" -``` - -### 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/docs/book/table-gateway.md b/docs/book/table-gateway.md deleted file mode 100644 index 05ebc0b26..000000000 --- a/docs/book/table-gateway.md +++ /dev/null @@ -1,274 +0,0 @@ -# Table Gateways - -The Table Gateway subcomponent provides an object-oriented representation of a -database table; its methods mirror the most common table operations. In code, -the interface resembles: - -```php -namespace Laminas\Db\TableGateway; - -use Laminas\Db\ResultSet\ResultSetInterface; -use Laminas\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 `Laminas\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 `Laminas\Db\TableGateway\TableGateway`, which defines -the following API: - -```php -namespace Laminas\Db\TableGateway; - -use Laminas\Db\Adapter\AdapterInterface; -use Laminas\Db\ResultSet\ResultSet; -use Laminas\Db\ResultSet\ResultSetInterface; -use Laminas\Db\Sql; -use Laminas\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 Laminas\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 -`Laminas\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 Laminas\Db\TableGateway\TableGateway; -use Laminas\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 through 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 laminas-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 Laminas\Db\TableGateway\AbstractTableGateway; - use Laminas\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 - Laminas\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 - [laminas-eventmanager](https://github.com/laminas/laminas-eventmanager) - `EventManager` instance within your `TableGateway` instance, and attach - listeners to the various events of its lifecycle. See the [section on - lifecycle events below](#tablegateway-lifecycle-events) for more information - on available events and the parameters they compose. - - ```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(); - ``` - -## TableGateway LifeCycle Events - -When the `EventFeature` is enabled on the `TableGateway` instance, you may -attach to any of the following events, which provide access to the parameters -listed. - -- `preInitialize` (no parameters) -- `postInitialize` (no parameters) -- `preSelect`, with the following parameters: - - `select`, with type `Laminas\Db\Sql\Select` -- `postSelect`, with the following parameters: - - `statement`, with type `Laminas\Db\Adapter\Driver\StatementInterface` - - `result`, with type `Laminas\Db\Adapter\Driver\ResultInterface` - - `resultSet`, with type `Laminas\Db\ResultSet\ResultSetInterface` -- `preInsert`, with the following parameters: - - `insert`, with type `Laminas\Db\Sql\Insert` -- `postInsert`, with the following parameters: - - `statement` with type `Laminas\Db\Adapter\Driver\StatementInterface` - - `result` with type `Laminas\Db\Adapter\Driver\ResultInterface` -- `preUpdate`, with the following parameters: - - `update`, with type `Laminas\Db\Sql\Update` -- `postUpdate`, with the following parameters: - - `statement`, with type `Laminas\Db\Adapter\Driver\StatementInterface` - - `result`, with type `Laminas\Db\Adapter\Driver\ResultInterface` -- `preDelete`, with the following parameters: - - `delete`, with type `Laminas\Db\Sql\Delete` -- `postDelete`, with the following parameters: - - `statement`, with type `Laminas\Db\Adapter\Driver\StatementInterface` - - `result`, with type `Laminas\Db\Adapter\Driver\ResultInterface` - -Listeners receive a `Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent` -instance as an argument. Within the listener, you can retrieve a parameter by -name from the event using the following syntax: - -```php -$parameter = $event->getParam($paramName); -``` - -As an example, you might attach a listener on the `postInsert` event as follows: - -```php -use Laminas\Db\Adapter\Driver\ResultInterface; -use Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent; -use Laminas\EventManager\EventManager; - -/** @var EventManager $eventManager */ -$eventManager->attach('postInsert', function (TableGatewayEvent $event) { - /** @var ResultInterface $result */ - $result = $event->getParam('result'); - $generatedId = $result->getGeneratedValue(); - - // do something with the generated identifier... -}); -``` diff --git a/docs/build/site/_/css/site.css b/docs/build/site/_/css/site.css new file mode 100644 index 000000000..53f6256cc --- /dev/null +++ b/docs/build/site/_/css/site.css @@ -0,0 +1,3 @@ +@font-face{font-family:Roboto;font-style:normal;font-weight:400;src:url(../font/roboto-latin-400-normal.woff2) format("woff2"),url(../font/roboto-latin-400-normal.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:Roboto;font-style:italic;font-weight:400;src:url(../font/roboto-latin-400-italic.woff2) format("woff2"),url(../font/roboto-latin-400-italic.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:Roboto;font-style:normal;font-weight:500;src:url(../font/roboto-latin-500-normal.woff2) format("woff2"),url(../font/roboto-latin-500-normal.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:Roboto;font-style:italic;font-weight:500;src:url(../font/roboto-latin-500-italic.woff2) format("woff2"),url(../font/roboto-latin-500-italic.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:Roboto Mono;font-style:normal;font-weight:400;src:url(../font/roboto-mono-latin-400-normal.woff2) format("woff2"),url(../font/roboto-mono-latin-400-normal.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:Roboto Mono;font-style:normal;font-weight:500;src:url(../font/roboto-mono-latin-500-normal.woff2) format("woff2"),url(../font/roboto-mono-latin-500-normal.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}html{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:1.0625em;height:100%;scroll-behavior:smooth}@media screen and (min-width:1024px){html{font-size:1.125em}}body{background:#fff;color:#222;font-family:Roboto,sans-serif;line-height:1.15;margin:0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere}a{text-decoration:none}a:hover{text-decoration:underline}a:active{background-color:none}code,kbd,pre{font-family:Roboto Mono,monospace}b,dt,strong,th{font-weight:500}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}em em{font-style:normal}strong strong{font-weight:400}button{cursor:pointer;font-family:inherit;font-size:1em;line-height:1.15;margin:0}button::-moz-focus-inner{border:none;padding:0}summary{cursor:pointer;-webkit-tap-highlight-color:transparent;outline:none}table{border-collapse:collapse;word-wrap:normal}object[type="image/svg+xml"]:not([width]){width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}::-webkit-input-placeholder{opacity:.5}::-moz-placeholder{opacity:.5}:-ms-input-placeholder{opacity:.5}::-ms-input-placeholder{opacity:.5}::placeholder{opacity:.5}@media (pointer:fine){@supports (scrollbar-width:thin){html{scrollbar-color:#c1c1c1 #fafafa}body *{scrollbar-width:thin;scrollbar-color:#c1c1c1 transparent}}html::-webkit-scrollbar{background-color:#fafafa;height:12px;width:12px}body ::-webkit-scrollbar{height:6px;width:6px}::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:#c1c1c1;border:3px solid transparent;border-radius:12px}body ::-webkit-scrollbar-thumb{border-width:1.75px;border-radius:6px}::-webkit-scrollbar-thumb:hover{background-color:#9c9c9c}}@media screen and (min-width:1024px){.body{display:-webkit-box;display:-ms-flexbox;display:flex}}.nav-container{position:fixed;top:3.5rem;left:0;width:100%;font-size:.94444rem;z-index:1;visibility:hidden}@media screen and (min-width:769px){.nav-container{width:15rem}}@media screen and (min-width:1024px){.nav-container{font-size:.86111rem;-webkit-box-flex:0;-ms-flex:none;flex:none;position:static;top:0;visibility:visible}}.nav-container.is-active{visibility:visible}.nav{background:#fafafa;position:relative;top:2.5rem;height:calc(100vh - 6rem)}@media screen and (min-width:769px){.nav{-webkit-box-shadow:.5px 0 3px #c1c1c1;box-shadow:.5px 0 3px #c1c1c1}}@media screen and (min-width:1024px){.nav{top:3.5rem;-webkit-box-shadow:none;box-shadow:none;position:sticky;height:calc(100vh - 3.5rem)}}.nav a{color:inherit}.nav .panels{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:inherit}html.is-clipped--nav{overflow-y:hidden}.nav-panel-menu{overflow-y:scroll;-ms-scroll-chaining:none;overscroll-behavior:none;height:calc(100% - 2.5rem)}.nav-panel-menu:not(.is-active) .nav-menu{opacity:.75}.nav-panel-menu:not(.is-active)::after{content:"";background:rgba(0,0,0,.5);display:block;position:absolute;top:0;right:0;bottom:0;left:0}.nav-menu{min-height:100%;padding:.5rem .75rem;line-height:1.35;position:relative}.nav-menu h3.title{color:#424242;font-size:inherit;font-weight:500;margin:0;padding:.25em 0 .125em}.nav-list{list-style:none;margin:0 0 0 .75rem;padding:0}.nav-menu>.nav-list+.nav-list{margin-top:.5rem}.nav-item{margin-top:.5em}.nav-item-toggle~.nav-list{padding-bottom:.125rem}.nav-item[data-depth="0"]>.nav-list:first-child{display:block;margin:0}.nav-item:not(.is-active)>.nav-list{display:none}.nav-item-toggle{background:transparent url(../img/caret.svg) no-repeat 50%/50%;border:none;outline:none;line-height:inherit;padding:0;position:absolute;height:1.35em;width:1.35em;margin-top:-.05em;margin-left:-1.35em}.nav-item.is-active>.nav-item-toggle{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.is-current-page>.nav-link,.is-current-page>.nav-text{font-weight:500}.nav-panel-explore{background:#fafafa;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;position:absolute;top:0;right:0;bottom:0;left:0}.nav-panel-explore:not(:first-child){top:auto;max-height:calc(50% + 2.5rem)}.nav-panel-explore .context{font-size:.83333rem;-ms-flex-negative:0;flex-shrink:0;color:#5d5d5d;-webkit-box-shadow:0 -1px 0 #e1e1e1;box-shadow:0 -1px 0 #e1e1e1;padding:0 .5rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;line-height:1;height:2.5rem}.nav-panel-explore:not(:first-child) .context{cursor:pointer}.nav-panel-explore .context .version{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:inherit;-ms-flex-align:inherit;align-items:inherit}.nav-panel-explore .context .version::after{content:"";background:url(../img/chevron.svg) no-repeat 100%/auto 100%;width:1.25em;height:.75em}.nav-panel-explore .components{line-height:1.6;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-shadow:inset 0 1px 5px #e1e1e1;box-shadow:inset 0 1px 5px #e1e1e1;background:#f0f0f0;padding:.5rem .75rem 0;margin:0;overflow-y:scroll;-ms-scroll-chaining:none;overscroll-behavior:none;max-height:100%;display:block}.nav-panel-explore:not(.is-active) .components{display:none}.nav-panel-explore .component{display:block}.nav-panel-explore .component+.component{margin-top:.5rem}.nav-panel-explore .component:last-child{margin-bottom:.75rem}.nav-panel-explore .component .title{font-weight:500}.nav-panel-explore .versions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-top:-.25rem;line-height:1;list-style:none}.nav-panel-explore .component .version{margin:.375rem .375rem 0 0}.nav-panel-explore .component .version a{border:1px solid #c1c1c1;border-radius:.25rem;opacity:.75;white-space:nowrap;padding:.125em .25em;display:inherit}.nav-panel-explore .component .is-current a{border-color:currentColor;opacity:.9;font-weight:500}@media screen and (max-width:1023.5px){aside.toc.sidebar{display:none}main>.content{overflow-x:auto}}@media screen and (min-width:1024px){main{-webkit-box-flex:1;-ms-flex:auto;flex:auto;min-width:0}main>.content{display:-webkit-box;display:-ms-flexbox;display:flex}aside.toc.embedded{display:none}aside.toc.sidebar{-webkit-box-flex:0;-ms-flex:0 0 9rem;flex:0 0 9rem;-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media screen and (min-width:1216px){aside.toc.sidebar{-ms-flex-preferred-size:12rem;flex-basis:12rem}}.toolbar{color:#5d5d5d;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background-color:#fafafa;-webkit-box-shadow:0 1px 0 #e1e1e1;box-shadow:0 1px 0 #e1e1e1;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:.83333rem;height:2.5rem;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;position:sticky;top:3.5rem;z-index:2}.toolbar a{color:inherit}.nav-toggle{background:url(../img/menu.svg) no-repeat 50% 47.5%;background-size:49%;border:none;outline:none;line-height:inherit;padding:0;height:2.5rem;width:2.5rem;margin-right:-.25rem}@media screen and (min-width:1024px){.nav-toggle{display:none}}.nav-toggle.is-active{background-image:url(../img/back.svg);background-size:41.5%}.home-link{display:block;background:url(../img/home-o.svg) no-repeat 50%;height:1.25rem;width:1.25rem;margin:.625rem}.home-link.is-current,.home-link:hover{background-image:url(../img/home.svg)}.edit-this-page{display:none;padding-right:.5rem}@media screen and (min-width:1024px){.edit-this-page{display:block}}.toolbar .edit-this-page a{color:#8e8e8e}.breadcrumbs{display:none;-webkit-box-flex:1;-ms-flex:1 1;flex:1 1;padding:0 .5rem 0 .75rem;line-height:1.35}@media screen and (min-width:1024px){.breadcrumbs{display:block}}a+.breadcrumbs{padding-left:.05rem}.breadcrumbs ul{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0;padding:0;list-style:none}.breadcrumbs li{display:inline;margin:0}.breadcrumbs li::after{content:"/";padding:0 .5rem}.breadcrumbs li:last-of-type::after{content:none}.page-versions{margin:0 .2rem 0 auto;position:relative;line-height:1}@media screen and (min-width:1024px){.page-versions{margin-right:.7rem}}.page-versions .version-menu-toggle{color:inherit;background:url(../img/chevron.svg) no-repeat;background-position:right .5rem top 50%;background-size:auto .75em;border:none;outline:none;line-height:inherit;padding:.5rem 1.5rem .5rem .5rem;position:relative;z-index:3}.page-versions .version-menu{display:-webkit-box;display:-ms-flexbox;display:flex;min-width:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end;background:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0)) no-repeat;background:linear-gradient(180deg,#f0f0f0 0,#f0f0f0) no-repeat;padding:1.375rem 1.5rem .5rem .5rem;position:absolute;top:0;right:0;white-space:nowrap}.page-versions:not(.is-active) .version-menu{display:none}.page-versions .version{display:block;padding-top:.5rem}.page-versions .version.is-current{display:none}.page-versions .version.is-missing{color:#8e8e8e;font-style:italic;text-decoration:none}.toc-menu{color:#5d5d5d}.toc.sidebar .toc-menu{margin-right:.75rem;position:sticky;top:6rem}.toc .toc-menu h3{color:#333;font-size:.88889rem;font-weight:500;line-height:1.3;margin:0 -.5px;padding-bottom:.25rem}.toc.sidebar .toc-menu h3{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:2.5rem;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.toc .toc-menu ul{font-size:.83333rem;line-height:1.2;list-style:none;margin:0;padding:0}.toc.sidebar .toc-menu ul{max-height:calc(100vh - 8.5rem);overflow-y:auto;-ms-scroll-chaining:none;overscroll-behavior:none}@supports (scrollbar-width:none){.toc.sidebar .toc-menu ul{scrollbar-width:none}}.toc .toc-menu ul::-webkit-scrollbar{width:0;height:0}@media screen and (min-width:1024px){.toc .toc-menu h3{font-size:.83333rem}.toc .toc-menu ul{font-size:.75rem}}.toc .toc-menu li{margin:0}.toc .toc-menu li[data-level="2"] a{padding-left:1.25rem}.toc .toc-menu li[data-level="3"] a{padding-left:2rem}.toc .toc-menu a{color:inherit;border-left:2px solid #e1e1e1;display:inline-block;padding:.25rem 0 .25rem .5rem;text-decoration:none}.sidebar.toc .toc-menu a{display:block;outline:none}.toc .toc-menu a:hover{color:#1565c0}.toc .toc-menu a.is-active{border-left-color:#1565c0;color:#333}.sidebar.toc .toc-menu a:focus{background:#fafafa}.toc .toc-menu .is-hidden-toc{display:none!important}.doc{color:#333;font-size:inherit;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;line-height:1.6;margin:0 auto;max-width:40rem;padding:0 1rem 4rem}@media screen and (min-width:1024px){.doc{-webkit-box-flex:1;-ms-flex:auto;flex:auto;font-size:.94444rem;margin:0 2rem;max-width:46rem;min-width:0}}.doc h1,.doc h2,.doc h3,.doc h4,.doc h5,.doc h6{color:#191919;font-weight:400;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;line-height:1.3;margin:1rem 0 0}.doc>h1.page:first-child{font-size:2rem;margin:1.5rem 0}@media screen and (min-width:769px){.doc>h1.page:first-child{margin-top:2.5rem}}.doc>h1.page:first-child+aside.toc.embedded{margin-top:-.5rem}.doc>h2#name+.sectionbody{margin-top:1rem}#preamble+.sect1,.doc .sect1+.sect1{margin-top:2rem}.doc h1.sect0{background:#f0f0f0;font-size:1.8em;margin:1.5rem -1rem 0;padding:.5rem 1rem}.doc h2:not(.discrete){border-bottom:1px solid #e1e1e1;margin-left:-1rem;margin-right:-1rem;padding:.4rem 1rem .1rem}.doc h3:not(.discrete),.doc h4:not(.discrete){font-weight:500}.doc h1 .anchor,.doc h2 .anchor,.doc h3 .anchor,.doc h4 .anchor,.doc h5 .anchor,.doc h6 .anchor{position:absolute;text-decoration:none;width:1.75ex;margin-left:-1.5ex;visibility:hidden;font-size:.8em;font-weight:400;padding-top:.05em}.doc h1 .anchor::before,.doc h2 .anchor::before,.doc h3 .anchor::before,.doc h4 .anchor::before,.doc h5 .anchor::before,.doc h6 .anchor::before{content:"\00a7"}.doc h1:hover .anchor,.doc h2:hover .anchor,.doc h3:hover .anchor,.doc h4:hover .anchor,.doc h5:hover .anchor,.doc h6:hover .anchor{visibility:visible}.doc dl,.doc p{margin:0}.doc a{color:#1565c0}.doc a:hover{color:#104d92}.doc a.bare{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.doc a.unresolved{color:#d32f2f}.doc i.fa{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;font-style:normal}.doc .colist>table code,.doc p code,.doc thead code{color:#222;background:#fafafa;border-radius:.25em;font-size:.95em;padding:.125em .25em}.doc code,.doc pre{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.doc pre{font-size:.88889rem;line-height:1.5;margin:0}.doc blockquote{margin:0}.doc .paragraph.lead>p{font-size:1rem}.doc .right{float:right}.doc .left{float:left}.doc .float-gap.right{margin:0 1rem 1rem 0}.doc .float-gap.left{margin:0 0 1rem 1rem}.doc .float-group::after{content:"";display:table;clear:both}.doc .stretch{width:100%}.doc .underline{text-decoration:underline}.doc .line-through{text-decoration:line-through}.doc .dlist,.doc .exampleblock,.doc .hdlist,.doc .imageblock,.doc .listingblock,.doc .literalblock,.doc .olist,.doc .paragraph,.doc .partintro,.doc .quoteblock,.doc .sidebarblock,.doc .tabs,.doc .ulist,.doc .verseblock,.doc .videoblock,.doc details,.doc hr{margin:1rem 0 0}.doc table.tableblock{font-size:.83333rem}.doc .tablecontainer,.doc .tablecontainer+*,.doc :not(.tablecontainer)>table.tableblock,.doc :not(.tablecontainer)>table.tableblock+*{margin-top:1.5rem}.doc p.tableblock+p.tableblock{margin-top:.5rem}.doc td.tableblock>.content>:first-child{margin-top:0}.doc table.tableblock td,.doc table.tableblock th{padding:.5rem}.doc table.tableblock,.doc table.tableblock>*>tr>*{border:0 solid #e1e1e1}.doc table.grid-all>*>tr>*{border-width:1px}.doc table.grid-cols>*>tr>*{border-width:0 1px}.doc table.grid-rows>*>tr>*{border-width:1px 0}.doc table.grid-all>thead th,.doc table.grid-rows>thead th{border-bottom-width:2.5px}.doc table.frame-all{border-width:1px}.doc table.frame-ends{border-width:1px 0}.doc table.frame-sides{border-width:0 1px}.doc table.frame-none>colgroup+*>:first-child>*,.doc table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}.doc table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}.doc table.frame-ends>*>tr>:first-child,.doc table.frame-none>*>tr>:first-child{border-left-width:0}.doc table.frame-ends>*>tr>:last-child,.doc table.frame-none>*>tr>:last-child{border-right-width:0}.doc table.stripes-all>tbody>tr,.doc table.stripes-even>tbody>tr:nth-of-type(2n),.doc table.stripes-hover>tbody>tr:hover,.doc table.stripes-odd>tbody>tr:nth-of-type(odd){background:#fafafa}.doc table.tableblock>tfoot{background:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background:linear-gradient(180deg,#f0f0f0 0,#fff)}.doc .halign-left{text-align:left}.doc .halign-right{text-align:right}.doc .halign-center{text-align:center}.doc .valign-top{vertical-align:top}.doc .valign-bottom{vertical-align:bottom}.doc .valign-middle{vertical-align:middle}.doc .admonitionblock{margin:1.4rem 0 0}.doc .admonitionblock p,.doc .admonitionblock td.content{font-size:.88889rem}.doc .admonitionblock td.content>.title+*,.doc .admonitionblock td.content>:not(.title):first-child{margin-top:0}.doc .admonitionblock pre{font-size:.83333rem}.doc .admonitionblock>table{table-layout:fixed;position:relative;width:100%}.doc .admonitionblock td.content{padding:1rem 1rem .75rem;background:#fafafa;width:100%;word-wrap:anywhere}.doc .admonitionblock .icon{position:absolute;top:0;left:0;font-size:.83333rem;padding:0 .5rem;height:1.25rem;line-height:1;font-weight:500;text-transform:uppercase;border-radius:.45rem;-webkit-transform:translate(-.5rem,-50%);transform:translate(-.5rem,-50%)}.doc .admonitionblock.caution .icon{background-color:#a0439c;color:#fff}.doc .admonitionblock.important .icon{background-color:#d32f2f;color:#fff}.doc .admonitionblock.note .icon{background-color:#217ee7;color:#fff}.doc .admonitionblock.tip .icon{background-color:#41af46;color:#fff}.doc .admonitionblock.warning .icon{background-color:#e18114;color:#fff}.doc .admonitionblock .icon i{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%}.doc .admonitionblock .icon i::after{content:attr(title)}.doc .imageblock,.doc .videoblock{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.doc .imageblock.text-left,.doc .videoblock.text-left{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.doc .imageblock.text-right,.doc .videoblock.text-right{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.doc .image>img,.doc .image>object,.doc .image>svg,.doc .imageblock img,.doc .imageblock object,.doc .imageblock svg{display:inline-block;height:auto;max-width:100%;vertical-align:middle}.doc .image:not(.left):not(.right)>img{margin-top:-.2em}.doc .videoblock iframe{max-width:100%;vertical-align:middle}#preamble .abstract blockquote{background:#f0f0f0;border-left:5px solid #e1e1e1;color:#4a4a4a;font-size:.88889rem;padding:.75em 1em}.doc .quoteblock,.doc .verseblock{background:#fafafa;border-left:5px solid #5d5d5d;color:#5d5d5d}.doc .quoteblock{padding:.25rem 2rem 1.25rem}.doc .quoteblock .attribution{color:#8e8e8e;font-size:.83333rem;margin-top:.75rem}.doc .quoteblock blockquote{margin-top:1rem}.doc .quoteblock .paragraph{font-style:italic}.doc .quoteblock cite{padding-left:1em}.doc .verseblock{font-size:1.15em;padding:1rem 2rem}.doc .verseblock pre{font-family:inherit;font-size:inherit}.doc ol,.doc ul{margin:0;padding:0 0 0 2rem}.doc ol.none,.doc ol.unnumbered,.doc ol.unstyled,.doc ul.checklist,.doc ul.no-bullet,.doc ul.none,.doc ul.unstyled{list-style-type:none}.doc ol.unnumbered,.doc ul.no-bullet{padding-left:1.25rem}.doc ol.unstyled,.doc ul.unstyled{padding-left:0}.doc ul.circle{list-style-type:circle}.doc ul.disc{list-style-type:disc}.doc ul.square{list-style-type:square}.doc ul.circle ul:not([class]),.doc ul.disc ul:not([class]),.doc ul.square ul:not([class]){list-style:inherit}.doc ol.arabic{list-style-type:decimal}.doc ol.decimal{list-style-type:decimal-leading-zero}.doc ol.loweralpha{list-style-type:lower-alpha}.doc ol.upperalpha{list-style-type:upper-alpha}.doc ol.lowerroman{list-style-type:lower-roman}.doc ol.upperroman{list-style-type:upper-roman}.doc ol.lowergreek{list-style-type:lower-greek}.doc ul.checklist{padding-left:1.75rem}.doc ul.checklist p>i.fa-check-square-o:first-child,.doc ul.checklist p>i.fa-square-o:first-child{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:1.25rem;margin-left:-1.25rem}.doc ul.checklist i.fa-check-square-o::before{content:"\2713"}.doc ul.checklist i.fa-square-o::before{content:"\274f"}.doc .dlist .dlist,.doc .dlist .olist,.doc .dlist .ulist,.doc .olist .dlist,.doc .olist .olist,.doc .olist .ulist,.doc .olist li+li,.doc .ulist .dlist,.doc .ulist .olist,.doc .ulist .ulist,.doc .ulist li+li{margin-top:.5rem}.doc .admonitionblock .listingblock,.doc .olist .listingblock,.doc .ulist .listingblock{padding:0}.doc .admonitionblock .title,.doc .exampleblock .title,.doc .imageblock .title,.doc .listingblock .title,.doc .literalblock .title,.doc .openblock .title,.doc .tableblock caption,.doc .videoblock .title{color:#5d5d5d;font-size:.88889rem;font-style:italic;font-weight:500;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;letter-spacing:.01em;padding-bottom:.075rem}.doc .tableblock caption{text-align:left}.doc .olist .title,.doc .ulist .title{font-style:italic;font-weight:500;margin-bottom:.25rem}.doc .imageblock .title{margin-top:.5rem;padding-bottom:0}.doc details{margin-left:1rem}.doc details>summary{display:block;position:relative;line-height:1.6;margin-bottom:.5rem}.doc details>summary::-webkit-details-marker{display:none}.doc details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1rem;-webkit-transform:translateX(15%);transform:translateX(15%)}.doc details[open]>summary::before{border-color:currentColor transparent transparent;border-width:.5rem .3rem 0;-webkit-transform:translateY(15%);transform:translateY(15%)}.doc details>summary::after{content:"";width:1rem;height:1em;position:absolute;top:.3em;left:-1rem}.doc details.result{margin-top:.25rem}.doc details.result>summary{color:#5d5d5d;font-style:italic;margin-bottom:0}.doc details.result>.content{margin-left:-1rem}.doc .exampleblock>.content,.doc details.result>.content{background:#fff;border:.25rem solid #5d5d5d;border-radius:.5rem;padding:.75rem}.doc .exampleblock>.content::after,.doc details.result>.content::after{content:"";display:table;clear:both}.doc .exampleblock>.content>:first-child,.doc details>.content>:first-child{margin-top:0}.doc .sidebarblock{background:#e1e1e1;border-radius:.75rem;padding:.75rem 1.5rem}.doc .sidebarblock>.content>.title{font-size:1.25rem;font-weight:500;line-height:1.3;margin-bottom:-.3em;text-align:center}.doc .sidebarblock>.content>:not(.title):first-child{margin-top:0}.doc .listingblock.wrap pre,.doc .tableblock pre{white-space:pre-wrap}.doc .listingblock pre:not(.highlight),.doc .literalblock pre,.doc pre.highlight code{background:#fafafa;-webkit-box-shadow:inset 0 0 1.75px #e1e1e1;box-shadow:inset 0 0 1.75px #e1e1e1;display:block;overflow-x:auto;padding:.875em}.doc .listingblock>.content{position:relative}.doc .source-toolbox{display:-webkit-box;display:-ms-flexbox;display:flex;visibility:hidden;position:absolute;top:.25rem;right:.5rem;color:grey;font-family:Roboto,sans-serif;font-size:.72222rem;line-height:1;white-space:nowrap;z-index:1}.doc .listingblock:hover .source-toolbox{visibility:visible}.doc .source-toolbox .source-lang{text-transform:uppercase;letter-spacing:.075em}.doc .source-toolbox>:not(:last-child)::after{content:"|";letter-spacing:0;padding:0 1ch}.doc .source-toolbox .copy-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:none;border:none;color:inherit;outline:none;padding:0;font-size:inherit;line-height:inherit;width:1em;height:1em}.doc .source-toolbox .copy-icon{-webkit-box-flex:0;-ms-flex:none;flex:none;width:inherit;height:inherit}.doc .source-toolbox img.copy-icon{-webkit-filter:invert(50.2%);filter:invert(50.2%)}.doc .source-toolbox svg.copy-icon{fill:currentColor}.doc .source-toolbox .copy-toast{-webkit-box-flex:0;-ms-flex:none;flex:none;position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-top:1em;background-color:#333;border-radius:.25em;padding:.5em;color:#fff;cursor:auto;opacity:0;-webkit-transition:opacity .5s ease .5s;transition:opacity .5s ease .5s}.doc .source-toolbox .copy-toast::after{content:"";position:absolute;top:0;width:1em;height:1em;border:.55em solid transparent;border-left-color:#333;-webkit-transform:rotate(-90deg) translateX(50%) translateY(50%);transform:rotate(-90deg) translateX(50%) translateY(50%);-webkit-transform-origin:left;transform-origin:left}.doc .source-toolbox .copy-button.clicked .copy-toast{opacity:1;-webkit-transition:none;transition:none}.doc .language-console .hljs-meta{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.doc .dlist dt{font-style:italic}.doc .dlist dd{margin:0 0 .25rem 1.5rem}.doc .dlist dd:last-of-type{margin-bottom:0}.doc td.hdlist1,.doc td.hdlist2{padding:.5rem 0 0;vertical-align:top}.doc tr:first-child>.hdlist1,.doc tr:first-child>.hdlist2{padding-top:0}.doc td.hdlist1{font-weight:500;padding-right:.25rem}.doc td.hdlist2{padding-left:.25rem}.doc .colist{font-size:.88889rem;margin:.25rem 0 -.25rem}.doc .colist>table>tbody>tr>:first-child,.doc .colist>table>tr>:first-child{padding:.25em .5rem 0;vertical-align:top}.doc .colist>table>tbody>tr>:last-child,.doc .colist>table>tr>:last-child{padding:.25rem 0}.doc .conum[data-value]{border:1px solid;border-radius:100%;display:inline-block;font-family:Roboto,sans-serif;font-size:.75rem;font-style:normal;line-height:1.2;text-align:center;width:1.25em;height:1.25em;letter-spacing:-.25ex;text-indent:-.25ex}.doc .conum[data-value]::after{content:attr(data-value)}.doc .conum[data-value]+b{display:none}.doc hr{border:solid #e1e1e1;border-width:2px 0 0;height:0}.doc b.button{white-space:nowrap}.doc b.button::before{content:"[";padding-right:.25em}.doc b.button::after{content:"]";padding-left:.25em}.doc kbd{display:inline-block;font-size:.66667rem;background:#fafafa;border:1px solid #c1c1c1;border-radius:.25em;-webkit-box-shadow:0 1px 0 #c1c1c1,0 0 0 .1em #fff inset;box-shadow:0 1px 0 #c1c1c1,inset 0 0 0 .1em #fff;padding:.25em .5em;vertical-align:text-bottom;white-space:nowrap}.doc .keyseq,.doc kbd{line-height:1}.doc .keyseq{font-size:.88889rem}.doc .keyseq kbd{margin:0 .125em}.doc .keyseq kbd:first-child{margin-left:0}.doc .keyseq kbd:last-child{margin-right:0}.doc .menuseq,.doc .path{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.doc .menuseq i.caret::before{content:"\203a";font-size:1.1em;font-weight:500;line-height:.90909}.doc :not(pre).nowrap{white-space:nowrap}.doc .nobreak{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;word-wrap:normal}#footnotes{font-size:.85em;line-height:1.5;margin:2rem -.5rem 0}.doc td.tableblock>.content #footnotes{margin:2rem 0 0}#footnotes hr{border-top-width:1px;margin-top:0;width:20%}#footnotes .footnote{margin:.5em 0 0 1em}#footnotes .footnote+.footnote{margin-top:.25em}#footnotes .footnote>a:first-of-type{display:inline-block;margin-left:-2em;text-align:right;width:1.5em}nav.pagination{border-top:1px solid #e1e1e1;line-height:1;margin:2rem -1rem -1rem;padding:.75rem 1rem 0}nav.pagination,nav.pagination span{display:-webkit-box;display:-ms-flexbox;display:flex}nav.pagination span{-webkit-box-flex:50%;-ms-flex:50%;flex:50%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}nav.pagination .prev{padding-right:.5rem}nav.pagination .next{margin-left:auto;padding-left:.5rem;text-align:right}nav.pagination span::before{color:#8e8e8e;font-size:.75em;padding-bottom:.1em}nav.pagination .prev::before{content:"Prev"}nav.pagination .next::before{content:"Next"}nav.pagination a{font-weight:500;line-height:1.3;position:relative}nav.pagination a::after,nav.pagination a::before{color:#8e8e8e;font-weight:400;font-size:1.5em;line-height:.75;position:absolute;top:0;width:1rem}nav.pagination .prev a::before{content:"\2039";-webkit-transform:translateX(-100%);transform:translateX(-100%)}nav.pagination .next a::after{content:"\203a"}html.is-clipped--navbar{overflow-y:hidden}body{padding-top:3.5rem}.navbar{background:#191919;color:#fff;font-size:.88889rem;height:3.5rem;position:fixed;top:0;width:100%;z-index:4}.navbar a{text-decoration:none}.navbar-brand{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:auto;flex:auto;padding-left:1rem}.navbar-brand .navbar-item{color:#fff}.navbar-brand .navbar-item:first-child{-ms-flex-item-align:center;align-self:center;padding:0;font-size:1.22222rem;-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:1}.navbar-brand .navbar-item:first-child a{color:inherit;word-wrap:normal}.navbar-brand .navbar-item:first-child :not(:last-child){padding-right:.375rem}.navbar-brand .navbar-item.search{-webkit-box-flex:1;-ms-flex:auto;flex:auto;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}#search-input{color:#333;font-family:inherit;font-size:.95rem;width:150px;border:1px solid #dbdbdb;border-radius:.1em;line-height:1.5;padding:0 .25em}#search-input:disabled{background-color:#dbdbdb;cursor:not-allowed;pointer-events:all!important}#search-input:disabled::-webkit-input-placeholder{color:#4c4c4c}#search-input:disabled::-moz-placeholder{color:#4c4c4c}#search-input:disabled:-ms-input-placeholder{color:#4c4c4c}#search-input:disabled::-ms-input-placeholder{color:#4c4c4c}#search-input:disabled::placeholder{color:#4c4c4c}#search-input:focus{outline:none}.navbar-burger{background:none;border:none;outline:none;line-height:1;position:relative;width:3rem;padding:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-left:auto;min-width:0}.navbar-burger span{background-color:#fff;height:1.5px;width:1rem}.navbar-burger:not(.is-active) span{-webkit-transition:opacity 0s .25s,margin-top .25s ease-out .25s,-webkit-transform .25s ease-out;transition:opacity 0s .25s,margin-top .25s ease-out .25s,-webkit-transform .25s ease-out;transition:transform .25s ease-out,opacity 0s .25s,margin-top .25s ease-out .25s;transition:transform .25s ease-out,opacity 0s .25s,margin-top .25s ease-out .25s,-webkit-transform .25s ease-out}.navbar-burger span+span{margin-top:.25rem}.navbar-burger.is-active span+span{margin-top:-1.5px}.navbar-burger.is-active span:first-child{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.navbar-item,.navbar-link{color:#222;display:block;line-height:1.6;padding:.5rem 1rem}.navbar-item.has-dropdown{padding:0}.navbar-item .icon{width:1.25rem;height:1.25rem;display:block}.navbar-item .icon img,.navbar-item .icon svg{fill:currentColor;width:inherit;height:inherit}.navbar-link{padding-right:2.5em}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-dropdown .navbar-item.has-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-dropdown .navbar-item small{color:#8e8e8e;font-size:.66667rem}.navbar-divider{background-color:#e1e1e1;border:none;height:1px;margin:.25rem 0}.navbar .button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:#fff;border:1px solid #e1e1e1;border-radius:.15rem;height:1.75rem;color:#222;padding:0 .75em;white-space:nowrap}@media screen and (max-width:768.5px){.navbar-brand .navbar-item.search{padding-left:0;padding-right:0}}@media screen and (min-width:769px){#search-input{width:200px}}@media screen and (max-width:1023.5px){.navbar-brand{height:inherit}.navbar-brand .navbar-item{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex}.navbar-menu{background:#fff;-webkit-box-shadow:0 8px 16px rgba(10,10,10,.1);box-shadow:0 8px 16px rgba(10,10,10,.1);max-height:calc(100vh - 3.5rem);overflow-y:auto;-ms-scroll-chaining:none;overscroll-behavior:none;padding:.5rem 0}.navbar-menu:not(.is-active){display:none}.navbar-menu .navbar-link:hover,.navbar-menu a.navbar-item:hover{background:#f5f5f5}}@media screen and (min-width:1024px){.navbar-burger{display:none}.navbar,.navbar-end,.navbar-item,.navbar-link,.navbar-menu{display:-webkit-box;display:-ms-flexbox;display:flex}.navbar-item,.navbar-link{position:relative;-webkit-box-flex:0;-ms-flex:none;flex:none}.navbar-item:not(.has-dropdown),.navbar-link{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-link::after{border-width:0 0 1px 1px;border-style:solid;content:"";display:block;height:.5em;pointer-events:none;position:absolute;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);width:.5em;margin-top:-.375em;right:1.125em;top:50%}.navbar-end .navbar-link,.navbar-end>.navbar-item{color:#fff}.navbar-end .navbar-item.has-dropdown:hover .navbar-link,.navbar-end .navbar-link:hover,.navbar-end>a.navbar-item:hover{background:#000;color:#fff}.navbar-end .navbar-link::after{border-color:currentColor}.navbar-dropdown{background:#fff;border:1px solid #e1e1e1;border-top:none;border-radius:0 0 .25rem .25rem;display:none;top:100%;left:0;min-width:100%;position:absolute}.navbar-dropdown .navbar-item{padding:.5rem 3rem .5rem 1rem;white-space:nowrap}.navbar-dropdown .navbar-item small{position:relative;right:-2rem}.navbar-dropdown .navbar-item:last-child{border-radius:inherit}.navbar-dropdown.is-right{left:auto;right:0}.navbar-dropdown a.navbar-item:hover{background:#f5f5f5}}footer.footer{background-color:#e1e1e1;color:#5d5d5d;font-size:.83333rem;line-height:1.6;padding:1.5rem}.footer p{margin:.5rem 0}.footer a{color:#191919} + +/*! Adapted from the GitHub style by Vasily Polovnyov */.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:500}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:500}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:500}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:500}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:500}@page{margin:.5in}@media print{.hide-for-print{display:none!important}html{font-size:.9375em}a{color:inherit!important;text-decoration:underline}a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none}img,object,svg,tr{page-break-inside:avoid}thead{display:table-header-group}pre{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;white-space:pre-wrap}body{padding-top:2rem}.navbar{background:none;color:inherit;position:absolute}.navbar *{color:inherit!important}.nav-container,.navbar>:not(.navbar-brand),.toolbar,aside.toc,nav.pagination{display:none}.doc{color:inherit;margin:auto;max-width:none;padding-bottom:2rem}.doc .admonitionblock td.icon{-webkit-print-color-adjust:exact;color-adjust:exact}.doc .listingblock code[data-lang]::before{display:block}footer.footer{background:none;border-top:1px solid #e1e1e1;color:#8e8e8e;padding:.25rem .5rem 0}.footer *{color:inherit}} \ No newline at end of file diff --git a/docs/build/site/_/font/roboto-latin-400-italic.woff b/docs/build/site/_/font/roboto-latin-400-italic.woff new file mode 100644 index 000000000..ebee16b9e Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-400-italic.woff differ diff --git a/docs/build/site/_/font/roboto-latin-400-italic.woff2 b/docs/build/site/_/font/roboto-latin-400-italic.woff2 new file mode 100644 index 000000000..e1b7a79f9 Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-400-italic.woff2 differ diff --git a/docs/build/site/_/font/roboto-latin-400-normal.woff b/docs/build/site/_/font/roboto-latin-400-normal.woff new file mode 100644 index 000000000..9eaa94f9b Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-400-normal.woff differ diff --git a/docs/build/site/_/font/roboto-latin-400-normal.woff2 b/docs/build/site/_/font/roboto-latin-400-normal.woff2 new file mode 100644 index 000000000..020729ef8 Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-400-normal.woff2 differ diff --git a/docs/build/site/_/font/roboto-latin-500-italic.woff b/docs/build/site/_/font/roboto-latin-500-italic.woff new file mode 100644 index 000000000..b6ad1c5be Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-500-italic.woff differ diff --git a/docs/build/site/_/font/roboto-latin-500-italic.woff2 b/docs/build/site/_/font/roboto-latin-500-italic.woff2 new file mode 100644 index 000000000..ae1933f38 Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-500-italic.woff2 differ diff --git a/docs/build/site/_/font/roboto-latin-500-normal.woff b/docs/build/site/_/font/roboto-latin-500-normal.woff new file mode 100644 index 000000000..d39bb52a5 Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-500-normal.woff differ diff --git a/docs/build/site/_/font/roboto-latin-500-normal.woff2 b/docs/build/site/_/font/roboto-latin-500-normal.woff2 new file mode 100644 index 000000000..29342a8de Binary files /dev/null and b/docs/build/site/_/font/roboto-latin-500-normal.woff2 differ diff --git a/docs/build/site/_/font/roboto-mono-latin-400-normal.woff b/docs/build/site/_/font/roboto-mono-latin-400-normal.woff new file mode 100644 index 000000000..be3eb4c4c Binary files /dev/null and b/docs/build/site/_/font/roboto-mono-latin-400-normal.woff differ diff --git a/docs/build/site/_/font/roboto-mono-latin-400-normal.woff2 b/docs/build/site/_/font/roboto-mono-latin-400-normal.woff2 new file mode 100644 index 000000000..f8894bab5 Binary files /dev/null and b/docs/build/site/_/font/roboto-mono-latin-400-normal.woff2 differ diff --git a/docs/build/site/_/font/roboto-mono-latin-500-normal.woff b/docs/build/site/_/font/roboto-mono-latin-500-normal.woff new file mode 100644 index 000000000..43ca6a1b9 Binary files /dev/null and b/docs/build/site/_/font/roboto-mono-latin-500-normal.woff differ diff --git a/docs/build/site/_/font/roboto-mono-latin-500-normal.woff2 b/docs/build/site/_/font/roboto-mono-latin-500-normal.woff2 new file mode 100644 index 000000000..b4f2bf8c2 Binary files /dev/null and b/docs/build/site/_/font/roboto-mono-latin-500-normal.woff2 differ diff --git a/docs/build/site/_/img/back.svg b/docs/build/site/_/img/back.svg new file mode 100644 index 000000000..bf7d30e9a --- /dev/null +++ b/docs/build/site/_/img/back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/site/_/img/caret.svg b/docs/build/site/_/img/caret.svg new file mode 100644 index 000000000..1af41bc6e --- /dev/null +++ b/docs/build/site/_/img/caret.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/site/_/img/chevron.svg b/docs/build/site/_/img/chevron.svg new file mode 100644 index 000000000..40e962aff --- /dev/null +++ b/docs/build/site/_/img/chevron.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/site/_/img/home-o.svg b/docs/build/site/_/img/home-o.svg new file mode 100644 index 000000000..95d193b77 --- /dev/null +++ b/docs/build/site/_/img/home-o.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/site/_/img/home.svg b/docs/build/site/_/img/home.svg new file mode 100644 index 000000000..4e96b3545 --- /dev/null +++ b/docs/build/site/_/img/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/site/_/img/menu.svg b/docs/build/site/_/img/menu.svg new file mode 100644 index 000000000..8b43b2e00 --- /dev/null +++ b/docs/build/site/_/img/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/site/_/img/octicons-16.svg b/docs/build/site/_/img/octicons-16.svg new file mode 100644 index 000000000..e3b4e2022 --- /dev/null +++ b/docs/build/site/_/img/octicons-16.svg @@ -0,0 +1 @@ +Octicons v11.2.0 by GitHub - https://primer.style/octicons/ - License: MIT \ No newline at end of file diff --git a/docs/build/site/_/js/site.js b/docs/build/site/_/js/site.js new file mode 100644 index 000000000..74a8b5fd0 --- /dev/null +++ b/docs/build/site/_/js/site.js @@ -0,0 +1,6 @@ +!function(){"use strict";var e,o,r,s=/^sect(\d)$/,i=document.querySelector(".nav-container"),a=document.querySelector(".nav-toggle"),c=i.querySelector(".nav"),l=(a.addEventListener("click",function(e){if(a.classList.contains("is-active"))return u(e);v(e);var e=document.documentElement,t=(e.classList.add("is-clipped--nav"),a.classList.add("is-active"),i.classList.add("is-active"),c.getBoundingClientRect()),n=window.innerHeight-Math.round(t.top);Math.round(t.height)!==n&&(c.style.height=n+"px");e.addEventListener("click",u)}),i.addEventListener("click",v),i.querySelector("[data-panel=menu]"));function t(){var e,t,n=window.location.hash;if(n&&(n.indexOf("%")&&(n=decodeURIComponent(n)),!(e=l.querySelector('.nav-link[href="'+n+'"]')))){n=document.getElementById(n.slice(1));if(n)for(var i=n,a=document.querySelector("article.doc");(i=i.parentNode)&&i!==a;){var c=i.id;if((c=c||(c=s.test(i.className))&&(i.firstElementChild||{}).id)&&(e=l.querySelector('.nav-link[href="#'+c+'"]')))break}}if(e)t=e.parentNode;else{if(!r)return;e=(t=r).querySelector(".nav-link")}t!==o&&(h(l,".nav-item.is-active").forEach(function(e){e.classList.remove("is-active","is-current-path","is-current-page")}),t.classList.add("is-current-page"),d(o=t),p(l,e))}function d(e){for(var t,n=e.parentNode;!(t=n.classList).contains("nav-menu");)"LI"===n.tagName&&t.contains("nav-item")&&t.add("is-active","is-current-path"),n=n.parentNode;e.classList.add("is-active")}function n(){var e,t,n,i;this.classList.toggle("is-active")&&(e=parseFloat(window.getComputedStyle(this).marginTop),t=this.getBoundingClientRect(),n=l.getBoundingClientRect(),0<(i=(t.bottom-n.top-n.height+e).toFixed()))&&(l.scrollTop+=Math.min((t.top-n.top-e).toFixed(),i))}function u(e){v(e);e=document.documentElement;e.classList.remove("is-clipped--nav"),a.classList.remove("is-active"),i.classList.remove("is-active"),e.removeEventListener("click",u)}function v(e){e.stopPropagation()}function p(e,t){var n=e.getBoundingClientRect(),i=n.height,a=window.getComputedStyle(c);"sticky"===a.position&&(i-=n.top-parseFloat(a.top)),e.scrollTop=Math.max(0,.5*(t.getBoundingClientRect().height-i)+t.offsetTop)}function h(e,t){return[].slice.call(e.querySelectorAll(t))}l&&(e=i.querySelector("[data-panel=explore]"),o=l.querySelector(".is-current-page"),(r=o)?(d(o),p(l,o.querySelector(".nav-link"))):l.scrollTop=0,h(l,".nav-item-toggle").forEach(function(e){var t=e.parentElement,e=(e.addEventListener("click",n.bind(t)),function(e,t){e=e.nextElementSibling;return(!e||!t||e[e.matches?"matches":"msMatchesSelector"](t))&&e}(e,".nav-text"));e&&(e.style.cursor="pointer",e.addEventListener("click",n.bind(t)))}),e&&e.querySelector(".context").addEventListener("click",function(){h(c,"[data-panel]").forEach(function(e){e.classList.toggle("is-active")})}),l.addEventListener("mousedown",function(e){1":"")+".sect"+c);r.push("h"+(i+1)+"[id]")}else r.push("h1[id].sect0");n.push(r.join(">"))}m=n.join(","),f=d.parentNode;var a,s=[].slice.call((f||document).querySelectorAll(m));if(!s.length)return e.parentNode.removeChild(e);var l={},u=s.reduce(function(e,t){var o=document.createElement("a"),n=(o.textContent=t.textContent,l[o.href="#"+t.id]=o,document.createElement("li"));return n.dataset.level=parseInt(t.nodeName.slice(1),10)-1,n.appendChild(o),e.appendChild(n),e},document.createElement("ul")),f=e.querySelector(".toc-menu"),m=(f||((f=document.createElement("div")).className="toc-menu"),document.createElement("h3")),e=(m.textContent=e.dataset.title||"Contents",f.appendChild(m),f.appendChild(u),!document.getElementById("toc")&&d.querySelector("h1.page ~ :not(.is-before-toc)"));e&&((m=document.createElement("aside")).className="toc embedded",m.appendChild(f.cloneNode(!0)),e.parentNode.insertBefore(m,e)),window.addEventListener("load",function(){p(),window.addEventListener("scroll",p)})}}function p(){var n,i,t,e=window.pageYOffset,o=1.15*h(document.documentElement,"fontSize"),r=d.offsetTop;e&&window.innerHeight+e+2>=document.documentElement.scrollHeight?(a=Array.isArray(a)?a:Array(a||0),n=[],i=s.length-1,s.forEach(function(e,t){var o="#"+e.id;t===i||e.getBoundingClientRect().top+h(e,"paddingTop")>r?(n.push(o),a.indexOf(o)<0&&l[o].classList.add("is-active")):~a.indexOf(o)&&l[a.shift()].classList.remove("is-active")}),u.scrollTop=u.scrollHeight-u.offsetHeight,a=1r)return!0;t="#"+e.id}),t?t!==a&&(a&&l[a].classList.remove("is-active"),(e=l[t]).classList.add("is-active"),u.scrollHeight>u.offsetHeight&&(u.scrollTop=Math.max(0,e.offsetTop+e.offsetHeight-u.offsetHeight)),a=t):a&&(l[a].classList.remove("is-active"),a=void 0))}function h(e,t){return parseFloat(window.getComputedStyle(e)[t])}}(); +!function(){"use strict";var o=document.querySelector("article.doc"),t=document.querySelector(".toolbar");function i(e){return e&&(~e.indexOf("%")?decodeURIComponent(e):e).slice(1)}function r(e){if(e){if(e.altKey||e.ctrlKey)return;window.location.hash="#"+this.id,e.preventDefault()}window.scrollTo(0,function e(t,n){return o.contains(t)?e(t.offsetParent,t.offsetTop+n):n}(this,0)-t.getBoundingClientRect().bottom)}window.addEventListener("load",function e(t){var n;(n=i(window.location.hash))&&(n=document.getElementById(n))&&(r.bind(n)(),setTimeout(r.bind(n),0)),window.removeEventListener("load",e)}),Array.prototype.slice.call(document.querySelectorAll('a[href^="#"]')).forEach(function(e){var t;(t=i(e.hash))&&(t=document.getElementById(t))&&e.addEventListener("click",r.bind(t))})}(); +!function(){"use strict";var t,e=document.querySelector(".page-versions .version-menu-toggle");e&&(t=document.querySelector(".page-versions"),e.addEventListener("click",function(e){t.classList.toggle("is-active"),e.stopPropagation()}),document.documentElement.addEventListener("click",function(){t.classList.remove("is-active")}))}(); +!function(){"use strict";var t=document.querySelector(".navbar-burger");t&&t.addEventListener("click",function(t){t.stopPropagation(),document.documentElement.classList.toggle("is-clipped--navbar"),this.classList.toggle("is-active");t=document.getElementById(this.dataset.target);{var e;t.classList.toggle("is-active")&&(t.style.maxHeight="",e=window.innerHeight-Math.round(t.getBoundingClientRect().top),parseInt(window.getComputedStyle(t).maxHeight,10)!==e)&&(t.style.maxHeight=e+"px")}}.bind(t))}(); +!function(){"use strict";var o=/^\$ (\S[^\\\n]*(\\\n(?!\$ )[^\\\n]*)*)(?=\n|$)/gm,s=/( ) *\\\n *|\\\n( ?) */g,l=/ +$/gm,e=(document.getElementById("site-script")||{dataset:{}}).dataset,d=null==e.uiRootPath?".":e.uiRootPath,r=e.svgAs,p=window.navigator.clipboard;[].slice.call(document.querySelectorAll(".doc pre.highlight, .doc .literalblock pre")).forEach(function(e){var t,n,a,c;if(e.classList.contains("highlight"))(i=(t=e.querySelector("code")).dataset.lang)&&"console"!==i&&((a=document.createElement("span")).className="source-lang",a.appendChild(document.createTextNode(i)));else{if(!e.innerText.startsWith("$ "))return;var i=e.parentNode.parentNode;i.classList.remove("literalblock"),i.classList.add("listingblock"),e.classList.add("highlightjs","highlight"),(t=document.createElement("code")).className="language-console hljs",t.dataset.lang="console",t.appendChild(e.firstChild),e.appendChild(t)}(i=document.createElement("div")).className="source-toolbox",a&&i.appendChild(a),p&&((n=document.createElement("button")).className="copy-button",n.setAttribute("title","Copy to clipboard"),"svg"===r?((a=document.createElementNS("http://www.w3.org/2000/svg","svg")).setAttribute("class","copy-icon"),(c=document.createElementNS("http://www.w3.org/2000/svg","use")).setAttribute("href",d+"/img/octicons-16.svg#icon-clippy"),a.appendChild(c),n.appendChild(a)):((c=document.createElement("img")).src=d+"/img/octicons-16.svg#view-clippy",c.alt="copy icon",c.className="copy-icon",n.appendChild(c)),(a=document.createElement("span")).className="copy-toast",a.appendChild(document.createTextNode("Copied!")),n.appendChild(a),i.appendChild(n)),e.parentNode.appendChild(i),n&&n.addEventListener("click",function(e){var t=e.innerText.replace(l,"");"console"===e.dataset.lang&&t.startsWith("$ ")&&(t=function(e){var t,n=[];for(;t=o.exec(e);)n.push(t[1].replace(s,"$1$2"));return n.join(" && ")}(t));window.navigator.clipboard.writeText(t).then(function(){this.classList.add("clicked"),this.offsetHeight,this.classList.remove("clicked")}.bind(this),function(){})}.bind(n,t))})}(); \ No newline at end of file diff --git a/docs/build/site/index.html b/docs/build/site/index.html new file mode 100644 index 000000000..339c59bb9 --- /dev/null +++ b/docs/build/site/index.html @@ -0,0 +1,8 @@ + + + + + +Redirect Notice +

Redirect Notice

+

The page you requested has been relocated to laminas-db-documentation/index.html.

diff --git a/docs/build/site/laminas-db-documentation/_images/laminas-db-architecture-overview.odg b/docs/build/site/laminas-db-documentation/_images/laminas-db-architecture-overview.odg new file mode 100644 index 000000000..d93b31946 Binary files /dev/null and b/docs/build/site/laminas-db-documentation/_images/laminas-db-architecture-overview.odg differ diff --git a/docs/build/site/laminas-db-documentation/adapter.html b/docs/build/site/laminas-db-documentation/adapter.html new file mode 100644 index 000000000..0628a3031 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/adapter.html @@ -0,0 +1,827 @@ + + + + + + Adapters :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

Adapters

+
+
+
+

Laminas\Db\Adapter\Adapter is the central object of the laminas-db component. +It is responsible for adapting any code written in or for laminas-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 Laminas\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 Laminas\Db\Adapter\Adapter class. +The most common use case, while not the most explicit, is to pass an array of configuration to the Adapter:

+
+
+
+
use Laminas\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.

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyIs 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:

+
+
+
+
$adapter = new Laminas\Db\Adapter\Adapter([
+    'driver'   => 'Mysqli',
+    'database' => 'laminas_db_example',
+    'username' => 'developer',
+    'password' => 'developer-password',
+]);
+
+
+
+

Another example, of a Sqlite connection via PDO:

+
+
+
+
$adapter = new Laminas\Db\Adapter\Adapter([
+    'driver'   => 'Pdo_Sqlite',
+    'database' => 'path/to/sqlite.db',
+]);
+
+
+
+

Another example, of an IBM i DB2 connection via IbmDb2:

+
+
+
+
$adapter = new Laminas\Db\Adapter\Adapter([
+    'database' => '*LOCAL', // or name from WRKRDBDIRE, may be serial #
+    'driver' => 'IbmDb2',
+    'driver_options' => [
+        'autocommit' => DB2_AUTOCOMMIT_ON,
+        'i5_naming' => DB2_I5_NAMING_ON,
+        'i5_libl' => 'SCHEMA1 SCHEMA2 SCHEMA3',
+    ],
+    'username' => '__USER__',
+    'password' => '__PASS__',
+    // 'persistent' => true,
+    'platform' => 'IbmDb2',
+    'platform_options' => ['quote_identifiers' => false],
+]);
+
+
+
+

Another example, of an IBM i DB2 connection via PDO:

+
+
+
+
$adapter = new Laminas\Db\Adapter\Adapter([
+    'dsn' => 'ibm:DB_NAME', // DB_NAME is from WRKRDBDIRE, may be serial #
+    'driver' => 'pdo',
+    'driver_options' => [
+        // PDO::ATTR_PERSISTENT => true,
+        PDO::ATTR_AUTOCOMMIT => true,
+        PDO::I5_ATTR_DBC_SYS_NAMING => true,
+        PDO::I5_ATTR_DBC_CURLIB => '',
+        PDO::I5_ATTR_DBC_LIBL => 'SCHEMA1 SCHEMA2 SCHEMA3',
+    ],
+    'username' => '__USER__',
+    'password' => '__PASS__',
+    'platform' => 'IbmDb2',
+    'platform_options' => ['quote_identifiers' => false],
+]);
+
+
+
+

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 driver

    +
  • +
  • +

    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 mezzio and explicit way of creating an adapter is by injecting all your dependencies up front. +Laminas\Db\Adapter\Adapter uses constructor injection, and all required dependencies are injected through the constructor, which has the following signature (in pseudo-code):

+
+
+
+
use Laminas\Db\Adapter\Platform\PlatformInterface;
+use Laminas\Db\ResultSet\ResultSet;
+
+class Laminas\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 Laminas\Db\Adapter\Driver\DriverInterface.

    +
  • +
  • +

    $platform (optional): an instance of Laminas\Db\Platform\PlatformInterface; +the default will be created based off the driver implementation.

    +
  • +
  • +

    $queryResultSetPrototype (optional): an instance of Laminas\Db\ResultSet\ResultSet; +to understand this object’s role, see the section below on querying.

    +
  • +
+
+
+
+
+

Query Preparation

+
+
+

By default, Laminas\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:

+
+
+
+
$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:

+
+
+
+
$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.

+
+
+
+
// 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 Laminas\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: Laminas\Db\Adapter\Driver\ConnectionInterface

    +
  • +
  • +

    A statement: Laminas\Db\Adapter\Driver\StatementInterface

    +
  • +
  • +

    A result: Laminas\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, Laminas\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:

+
+
+
+
namespace Laminas\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:

+
+
+
+
namespace Laminas\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:

+
+
+
+
namespace Laminas\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:

+
+
+
+
namespace Laminas\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):

+
+
+
+
$platform = $adapter->getPlatform();
+
+// or
+$platform = $adapter->platform; // magic property access
+
+
+
+

The following are examples of Platform usage:

+
+
+
+
// $adapter is a Laminas\Db\Adapter\Adapter instance;
+// $platform is a Laminas\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:

+
+
+
+
namespace Laminas\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:

+
+
+
+
$container->offsetSet('limit', 5);
+
+
+
+

be bound as an integer. +To achieve this, pass in the ParameterContainer::TYPE_INTEGER constant as the 3rd parameter:

+
+
+
+
$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:

+
+
+
+
$adapter = new Laminas\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'];
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/adapters/adapter-aware-trait.html b/docs/build/site/laminas-db-documentation/adapters/adapter-aware-trait.html new file mode 100644 index 000000000..7717f28b6 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/adapters/adapter-aware-trait.html @@ -0,0 +1,311 @@ + + + + + + AdapterAwareTrait :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

AdapterAwareTrait

+
+
+
+

The trait Laminas\Db\Adapter\AdapterAwareTrait, which provides implementation for Laminas\Db\Adapter\AdapterAwareInterface, and allowed removal of duplicated implementations in several components of Laminas or in custom applications.

+
+
+

The interface defines only the method setDbAdapter() with one parameter for an instance of Laminas\Db\Adapter\Adapter:

+
+
+
+
public function setDbAdapter(\Laminas\Db\Adapter\Adapter $adapter) : self;
+
+
+
+
+
+

Basic Usage

+
+
+

Create Class and Add Trait

+
+
+
use Laminas\Db\Adapter\AdapterAwareTrait;
+use Laminas\Db\Adapter\AdapterAwareInterface;
+
+class Example implements AdapterAwareInterface
+{
+    use AdapterAwareTrait;
+}
+
+
+
+
+

Create and Set Adapter

+
+

Create a database adapter and set the adapter to the instance of the Example class:

+
+
+
+
$adapter = new Laminas\Db\Adapter\Adapter([
+    'driver'   => 'Pdo_Sqlite',
+    'database' => 'path/to/sqlite.db',
+]);
+
+$example = new Example();
+$example->setAdapter($adapter);
+
+
+
+
+
+
+

AdapterServiceDelegator

+
+
+

The delegator Laminas\Db\Adapter\AdapterServiceDelegator can be used to set a database adapter via the service manager of laminas-servicemanager.

+
+
+

The delegator tries to fetch a database adapter via the name Laminas\Db\Adapter\AdapterInterface from the service container and sets the adapter to the requested service. +The adapter itself must be an instance of Laminas\Db\Adapter\Adapter.

+
+
+
+
+

=== Integration for Mezzio and laminas-mvc based Applications

+
+
+

In a Mezzio or laminas-mvc based application the database adapter is already registered during the installation with the laminas-component-installer.

+
+
+
+
+

Create Class and Use Trait

+
+

Create a class and add the trait AdapterAwareTrait.

+
+
+
+
use Laminas\Db\Adapter\Adapter;
+use Laminas\Db\Adapter\AdapterInterface;
+
+class Example implements AdapterAwareInterface
+{
+    use AdapterAwareTrait;
+
+    public function getAdapter() : ?Adapter
+    {
+        return $this->adapter;
+    }
+}
+
+
+
+

(A getter method is also added for demonstration.)

+
+
+
+

Create and Configure Service Manager

+ +
+
+
use Interop\Container\ContainerInterface;
+use Laminas\Db\Adapter\AdapterInterface;
+use Laminas\Db\Adapter\AdapterServiceDelegator;
+use Laminas\Db\Adapter\AdapterAwareTrait;
+use Laminas\Db\Adapter\AdapterAwareInterface;
+
+$serviceManager = new Laminas\ServiceManager\ServiceManager([
+    'factories' => [
+        // Database adapter
+        AdapterInterface::class => static function(ContainerInterface $container) {
+            return new Laminas\Db\Adapter\Adapter([
+                'driver'   => 'Pdo_Sqlite',
+                'database' => 'path/to/sqlite.db',
+            ]);
+        }
+    ],
+    'invokables' => [
+        // Example class
+        Example::class => Example::class,
+    ],
+    'delegators' => [
+        // Delegator for Example class to set the adapter
+        Example::class => [
+            AdapterServiceDelegator::class,
+        ],
+    ],
+]);
+
+
+
+
+

Get Instance of Class

+
+

Retrieving an instance of the Example class with a database adapter:

+
+
+
+
/** @var Example $example */
+$example = $serviceManager->get(Example::class);
+
+var_dump($example->getAdapter() instanceof Laminas\Db\Adapter\Adapter); // true
+
+
+
+
+
+
+

Concrete Implementations

+
+
+

The validators Db\RecordExists and Db\NoRecordExists implements the trait and the plugin manager of laminas-validator includes the delegator to set the database adapter for both validators.

+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/application-integration/usage-in-a-laminas-mvc-application.html b/docs/build/site/laminas-db-documentation/application-integration/usage-in-a-laminas-mvc-application.html new file mode 100644 index 000000000..93b9e2c16 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/application-integration/usage-in-a-laminas-mvc-application.html @@ -0,0 +1,447 @@ + + + + + + Usage in a laminas-mvc Application :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

Usage in a laminas-mvc Application

+
+
+
+

The minimal installation for a laminas-mvc based application doesn’t include any database features.

+
+
+
+
+

When installing the Laminas MVC Skeleton Application

+
+
+

While Composer is installing the MVC Application, you can add the laminas-db package while prompted.

+
+
+
+
+

Adding to an existing Laminas MVC Skeleton Application

+
+
+

If the MVC application is already created, then use Composer to add the laminas-db package.

+
+
+
+
+

The Abstract Factory

+
+
+

Now that the laminas-db package is installed, the abstract factory Laminas\Db\Adapter\AdapterAbstractServiceFactory is available to be used with the service configuration.

+
+
+

Configuring the adapter

+
+

The abstract factory expects the configuration key db in order to create a Laminas\Db\Adapter\Adapter instance.

+
+
+
+

Working with a Sqlite database

+
+

Sqlite is a lightweight option to have the application working with a database.

+
+
+

Here is an example of the configuration array for a sqlite database. +Assuming the sqlite file path is data/sample.sqlite, the following configuration will produce the adapter:

+
+
+
+
return [
+    'db' => [
+        'driver' => 'Pdo',
+        'adapters' => [
+            sqliteAdapter::class => [
+                'driver' => 'Pdo',
+                'dsn' => 'sqlite:data/sample.sqlite',
+            ],
+        ],
+    ],
+];
+
+
+
+

The data/ filepath for the sqlite file is the default data/ directory from the Laminas MVC application.

+
+
+
+

Working with a MySQL database

+
+

Unlike a sqlite database, the MySQL database adapter requires a MySQL server.

+
+
+

Here is an example of a configuration array for a MySQL database.

+
+
+
+
return [
+    'db' => [
+        'driver' => 'Pdo',
+        'adapters' => [
+            mysqlAdapter::class => [
+                'driver' => 'Pdo',
+                'dsn' => 'mysql:dbname=your_database_name;host=your_mysql_host;charset=utf8',
+                'username' => 'your_mysql_username',
+                'password' => 'your_mysql_password',
+                'driver_options' => [
+                    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
+                ],
+            ],
+        ],
+    ],
+];
+
+
+
+
+
+
+

Working with the adapter

+
+
+

Once you have configured an adapter, as in the above examples, you now have a Laminas\Db\Adapter\Adapter available to your application.

+
+
+

A factory for a class that consumes an adapter can pull the adapter by the name used in configuration. +As an example, for the sqlite database configured earlier, we could write the following:

+
+
+
+
use sqliteAdapter ;
+
+$adapter = $container->get(sqliteAdapter::class) ;
+
+
+
+

For the MySQL Database configured earlier:

+
+
+
+
use mysqlAdapter ;
+
+$adapter = $container->get(mysqlAdapter::class) ;
+
+
+ +
+
+
+

Running with Docker

+
+
+

When working with a MySQL database and when running the application with Docker, some files need to be added or adjusted.

+
+
+

Adding the MySQL extension to the PHP container

+
+

Change the Dockerfile to add the PDO MySQL extension to PHP.

+
+
+
+
FROM php:7.3-apache
+
+RUN apt-get update \
+ && apt-get install -y git zlib1g-dev libzip-dev \
+ && docker-php-ext-install zip pdo_mysql \
+ && a2enmod rewrite \
+ && sed -i 's!/var/www/html!/var/www/public!g' /etc/apache2/sites-available/000-default.conf \
+ && mv /var/www/html /var/www/public \
+ && curl -sS https://getcomposer.org/installer \
+  | php -- --install-dir=/usr/local/bin --filename=composer
+
+WORKDIR /var/www
+
+
+
+
+

Adding the mysql container

+
+

Change the docker-compose.yml file to add a new container for mysql.

+
+
+
+
  mysql:
+    image: mysql
+    ports:
+     - 3306:3306
+    command:
+      --default-authentication-plugin=mysql_native_password
+    volumes:
+     - ./.data/db:/var/lib/mysql
+     - ./.docker/mysql/:/docker-entrypoint-initdb.d/
+    environment:
+     - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
+
+
+
+

Though it is not the topic to explain how to write a docker-compose.yml file, a few details need to be highlighted :

+
+
+
    +
  • +

    The name of the container is mysql.

    +
  • +
  • +

    MySQL database files will be stored in the directory /.data/db/.

    +
  • +
  • +

    SQL schemas will need to be added to the /.docker/mysql/ directory so that Docker will be able to build and populate the database(s).

    +
  • +
  • +

    The mysql docker image is using the $MYSQL_ROOT_PASSWORD environment variable to set the mysql root password.

    +
  • +
+
+
+
+ +
+

Now link the mysql container and the laminas container so that the application knows where to find the mysql server.

+
+
+
+
  laminas:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    ports:
+     - 8080:80
+    volumes:
+     - .:/var/www
+    links:
+     - mysql:mysql
+
+
+
+
+

Adding phpMyAdmin

+
+

Optionnally, you can also add a container for phpMyAdmin.

+
+
+
+
  phpmyadmin:
+    image: phpmyadmin/phpmyadmin
+    ports:
+     - 8081:80
+    environment:
+     - PMA_HOST=${PMA_HOST}
+
+
+
+

The image uses the $PMA_HOST environment variable to set the host of the mysql server. +The expected value is the name of the mysql container.

+
+
+

Putting everything together:

+
+
+
+
version: "2.1"
+services:
+  laminas:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    ports:
+     - 8080:80
+    volumes:
+     - .:/var/www
+    links:
+     - mysql:mysql
+  mysql:
+    image: mysql
+    ports:
+     - 3306:3306
+    command:
+      --default-authentication-plugin=mysql_native_password
+    volumes:
+     - ./.data/db:/var/lib/mysql
+     - ./.docker/mysql/:/docker-entrypoint-initdb.d/
+    environment:
+     - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
+  phpmyadmin:
+    image: phpmyadmin/phpmyadmin
+    ports:
+     - 8081:80
+    environment:
+     - PMA_HOST=${PMA_HOST}
+
+
+
+
+

Defining credentials

+
+

The docker-compose.yml file uses ENV variables to define the credentials.

+
+
+

Docker will read the ENV variables from a .env file.

+
+
+
+
MYSQL_ROOT_PASSWORD=rootpassword
+PMA_HOST=mysql
+
+
+
+
+

Initiating the database schemas

+
+

At build, if the /.data/db directory is missing, Docker will create the mysql database with any .sql files found in the .docker/mysql/ directory. +(These are the files with the CREATE DATABASE, USE (database), and CREATE TABLE, INSERT INTO directives defined earlier in this document). +If multiple .sql files are present, it is a good idea to safely order the list because Docker will read the files in ascending order.

+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/getting-started.html b/docs/build/site/laminas-db-documentation/getting-started.html new file mode 100644 index 000000000..0db90d43b --- /dev/null +++ b/docs/build/site/laminas-db-documentation/getting-started.html @@ -0,0 +1,169 @@ + + + + + + Untitled :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Getting started

+
+
+

Assuming that you have Composer installed globally, run the following command in your project directory to install laminas-db:

+
+
+
+
composer require laminas/laminas-db
+
+
+
+

Configuring laminas-db

+ +
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/index.html b/docs/build/site/laminas-db-documentation/index.html new file mode 100644 index 000000000..9479adcbd --- /dev/null +++ b/docs/build/site/laminas-db-documentation/index.html @@ -0,0 +1,331 @@ + + + + + + What is laminas-db? :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

What is laminas-db?

+
+
+
+

Laminas-db simplifies database interaction in your PHP projects, providing a unified, programmatic interface for working with databases. +It avoids the need for you to hand-craft SQL, letting you build objects that construct SQL for you instead.

+
+
+

The project provides database, SQL, and result set abstraction layers, a Row data gateway and Table data gateway implementation, plus so much more.

+
+
+

Before we dive deeper let’s quickly consider four solid reasons why laminas-db is an excellent choice for your project.

+
+
+
+
+

Why would you use laminas-db?

+
+
+

PHP is not lacking in packages that provide database support. +There’s the Doctrine (database abstraction layer), Laravel’s Eloquent, and Propel to name but a few of the better known ones. +Well, here are a few solid reasons why it’s worth using laminas-db.

+
+
+

It’s pretty quick and easy to learn

+
+

Assuming that you have a basic understanding of SQL (92 or above) and OOP (object-oriented programming) in PHP, then you should be able to get up and running with laminas-db pretty quickly. +This is because most of the classes model some aspect of SQL.

+
+
+

For example, if you need to build a select, insert, update, or delete query, there are the Select, Insert, Update, and Delete objects. +If you need to create, alter, and drop tables, there are the CreateTable, AlterTable, and DropTable objects. +What’s more, where applicable, they’re all designed to work with one another.

+
+
+
+

It provides significant flexibility

+
+

While OOP is the most common way to use the library, you don’t have to use an object for every operation. +For example, you can build queries using objects, or a combination of objects and free form strings if that’s what you prefer. +The library works with you, not against you.

+
+
+

Take the following, quite basic, SQL query as an example:

+
+
+
+
SELECT first_name, last_name, email_address
+FROM users
+WHERE dob >= "2006.04.05";
+
+
+
+

In the above SQL query, we’re retrieving the first name, last name, and email address of every user from the users table whose date of birth was on or after the 5th of April, 2006.

+
+
+

Here’s how we could write it with objects:

+
+
+
+
use Laminas\Db\Sql\Sql;
+
+$sql    = new Sql($adapter);
+$select = $sql->select();
+$select->from('users');
+$select->columns(['first_name', 'last_name', 'email_address']);
+$select->where(['dob' >= "2006.04.05"]);
+
+
+
+

In the above example, you can see an example of instantiating a Select object that queries the "users" table for the required columns, where the where clause is defined using a plain PHP associative array.

+
+
+

However, we could get a little fancier by defining the where clause using a Where object instead, as in the following example.

+
+
+
+
use Laminas\Db\Sql\Sql;
+
+$sql    = new Sql($adapter);
+$select = $sql->select();
+$select->from('users');
+$select->columns(['first_name', 'last_name', 'email_address']);
+$select->where(['dob' >= "2006.04.05"]);
+
+
+
+

While these two examples are quite simplistic, they hint at what is possible with the library.

+
+
+
+

Clear code. No magic

+
+

There’s nothing magic about how laminas-db works. +It only runs the queries that you ask it to run, as you asked it to run them. +It doesn’t add extra columns to your queries. +It doesn’t run nested queries without your knowledge. +It doesn’t attempt to be smart on your behalf.

+
+
+

Sure, that might mean that there’s a bit more work for you to do. +But, at all times, you know what’s going on.

+
+
+
+

It’s community-driven

+
+

Since its inception, laminas-db has been an open source, community driven and lead project. +It was designed and developed by developers for developers, to be a programmatic way of building and running SQL queries, against a range of database vendors.

+
+
+

Given that, you can view its entire history on GitHub, and contribute to it, as and when you find a bug or missing feature that you want to implement. +You’re able to influence its direction. +You don’t need to wait for it to come to you.

+
+
+
+
+
+

laminas-db’s architecture

+
+
+

Laminas\Db\Adapter\Adapter

+
+

It all starts with Laminas\Db\Adapter\Adapter, which is laminas-db’s central object. +It is responsible for adapting any code written in or for laminas-db to the targeted PHP extensions and vendor databases; these are:

+
+ + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table 1. The list of officially supported drivers
DatabasePHP Extension/Driver

IBM Db2

The ext/ibm_db2 driver

MySQL

The ext/mysqli driver and the Pdo_Mysql PDO extension

Oracle

The ext/oci8 driver

PostgreSQL

The ext/pgsql driver and the Pdo_Pgsql PDO extension

Microsoft SQLServer

The ext/sqlsrv driver

SQLite

The Pdo_Sqlite PDO extension

+
+

It creates an abstraction layer for the PHP extensions in the Driver sub-namespace of Laminas\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.

+
+
+
+

\Laminas\Db\Adapter\Platform\AbstractPlatform

+
+

The Platform abstraction layer is based on AbstractPlatform. +It’s from this class that a vendor-specific abstraction is created.

+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/installation.html b/docs/build/site/laminas-db-documentation/installation.html new file mode 100644 index 000000000..562e20451 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/installation.html @@ -0,0 +1,165 @@ + + + + + + Untitled :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Installation

+
+
+

Assuming that you have Composer installed globally, run the following command in your project directory to install laminas-db:

+
+
+
+
composer require laminas/laminas-db
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/metadata.html b/docs/build/site/laminas-db-documentation/metadata.html new file mode 100644 index 000000000..51a50d2e1 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/metadata.html @@ -0,0 +1,408 @@ + + + + + + RDBMS Metadata :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

RDBMS Metadata

+
+
+
+

Laminas\Db\Metadata is as sub-component of laminas-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:

+
+
+
+
namespace Laminas\Db\Metadata;
+
+interface MetadataInterface
+{
+    public function getSchemas();
+
+    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(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(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(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(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 Laminas\Db\Metadata involves:

+
+
+
    +
  • +

    Constructing a Laminas\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.

    +
  • +
+
+
+

The Metadata::get*Names() methods will return arrays of strings, while the other methods will return value objects specific to the type queried.

+
+
+
+
$metadata = new Laminas\Db\Metadata\Metadata($adapter);
+
+// get the table names
+$tableNames = $metadata->getTableNames();
+
+foreach ($tableNames as $tableName) {
+    echo 'In Table ' . $tableName . PHP_EOL;
+
+    $table = $metadata->getTable($tableName);
+
+    echo '    With columns: ' . PHP_EOL;
+    foreach ($table->getColumns() as $column) {
+        echo '        ' . $column->getName()
+            . ' -> ' . $column->getDataType()
+            . PHP_EOL;
+    }
+
+    echo PHP_EOL;
+    echo '    With constraints: ' . PHP_EOL;
+
+    foreach ($metadata->getConstraints($tableName) as $constraint) {
+        echo '        ' . $constraint->getName()
+            . ' -> ' . $constraint->getType()
+            . PHP_EOL;
+
+        if (! $constraint->hasColumns()) {
+            continue;
+        }
+
+        echo '            column: ' . implode(', ', $constraint->getColumns());
+        if ($constraint->isForeignKey()) {
+            $fkCols = [];
+            foreach ($constraint->getReferencedColumns() as $refColumn) {
+                $fkCols[] = $constraint->getReferencedTableName() . '.' . $refColumn;
+            }
+            echo ' => ' . implode(', ', $fkCols);
+        }
+
+        echo PHP_EOL;
+    }
+
+    echo '----' . PHP_EOL;
+}
+
+
+
+
+
+

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:

+
+
+

TableObject

+
+
+
class Laminas\Db\Metadata\Object\TableObject
+{
+    public function __construct($name);
+    public function setColumns(array $columns);
+    public function getColumns();
+    public function setConstraints($constraints);
+    public function getConstraints();
+    public function setName($name);
+    public function getName();
+}
+
+
+
+
+

ColumnObject

+
+
+
class Laminas\Db\Metadata\Object\ColumnObject
+{
+    public function __construct($name, $tableName, $schemaName = null);
+    public function setName($name);
+    public function getName();
+    public function getTableName();
+    public function setTableName($tableName);
+    public function setSchemaName($schemaName);
+    public function getSchemaName();
+    public function getOrdinalPosition();
+    public function setOrdinalPosition($ordinalPosition);
+    public function getColumnDefault();
+    public function setColumnDefault($columnDefault);
+    public function getIsNullable();
+    public function setIsNullable($isNullable);
+    public function isNullable();
+    public function getDataType();
+    public function setDataType($dataType);
+    public function getCharacterMaximumLength();
+    public function setCharacterMaximumLength($characterMaximumLength);
+    public function getCharacterOctetLength();
+    public function setCharacterOctetLength($characterOctetLength);
+    public function getNumericPrecision();
+    public function setNumericPrecision($numericPrecision);
+    public function getNumericScale();
+    public function setNumericScale($numericScale);
+    public function getNumericUnsigned();
+    public function setNumericUnsigned($numericUnsigned);
+    public function isNumericUnsigned();
+    public function getErratas();
+    public function setErratas(array $erratas);
+    public function getErrata($errataName);
+    public function setErrata($errataName, $errataValue);
+}
+
+
+
+
+

ConstraintObject

+
+
+
class Laminas\Db\Metadata\Object\ConstraintObject
+{
+    public function __construct($name, $tableName, $schemaName = null);
+    public function setName($name);
+    public function getName();
+    public function setSchemaName($schemaName);
+    public function getSchemaName();
+    public function getTableName();
+    public function setTableName($tableName);
+    public function setType($type);
+    public function getType();
+    public function hasColumns();
+    public function getColumns();
+    public function setColumns(array $columns);
+    public function getReferencedTableSchema();
+    public function setReferencedTableSchema($referencedTableSchema);
+    public function getReferencedTableName();
+    public function setReferencedTableName($referencedTableName);
+    public function getReferencedColumns();
+    public function setReferencedColumns(array $referencedColumns);
+    public function getMatchOption();
+    public function setMatchOption($matchOption);
+    public function getUpdateRule();
+    public function setUpdateRule($updateRule);
+    public function getDeleteRule();
+    public function setDeleteRule($deleteRule);
+    public function getCheckClause();
+    public function setCheckClause($checkClause);
+    public function isPrimaryKey();
+    public function isUnique();
+    public function isForeignKey();
+    public function isCheck();
+
+}
+
+
+
+
+

TriggerObject

+
+
+
class Laminas\Db\Metadata\Object\TriggerObject
+{
+    public function getName();
+    public function setName($name);
+    public function getEventManipulation();
+    public function setEventManipulation($eventManipulation);
+    public function getEventObjectCatalog();
+    public function setEventObjectCatalog($eventObjectCatalog);
+    public function getEventObjectSchema();
+    public function setEventObjectSchema($eventObjectSchema);
+    public function getEventObjectTable();
+    public function setEventObjectTable($eventObjectTable);
+    public function getActionOrder();
+    public function setActionOrder($actionOrder);
+    public function getActionCondition();
+    public function setActionCondition($actionCondition);
+    public function getActionStatement();
+    public function setActionStatement($actionStatement);
+    public function getActionOrientation();
+    public function setActionOrientation($actionOrientation);
+    public function getActionTiming();
+    public function setActionTiming($actionTiming);
+    public function getActionReferenceOldTable();
+    public function setActionReferenceOldTable($actionReferenceOldTable);
+    public function getActionReferenceNewTable();
+    public function setActionReferenceNewTable($actionReferenceNewTable);
+    public function getActionReferenceOldRow();
+    public function setActionReferenceOldRow($actionReferenceOldRow);
+    public function getActionReferenceNewRow();
+    public function setActionReferenceNewRow($actionReferenceNewRow);
+    public function getCreated();
+    public function setCreated($created);
+}
+
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/result-set.html b/docs/build/site/laminas-db-documentation/result-set.html new file mode 100644 index 000000000..648565068 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/result-set.html @@ -0,0 +1,315 @@ + + + + + + Result Sets :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

Result Sets

+
+
+
+

Laminas\Db\ResultSet is a sub-component of laminas-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 Laminas\Db\Adapter\Driver\ResultInterface instances.

+
+
+

Result sets must implement the Laminas\Db\ResultSet\ResultSetInterface, and all sub-components of laminas-db that return a result set as part of their API will assume an instance of a ResultSetInterface should be returned. +In most cases, 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:

+
+
+
+
use Countable;
+use Traversable;
+
+interface ResultSetInterface extends Traversable, Countable
+{
+    public function initialize(mixed $dataSource) : void;
+    public function getFieldCount() : int;
+}
+
+
+
+
+
+

Quick start

+
+
+

Laminas\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, Laminas\Db\Adapter\Adapter will use a prototypical Laminas\Db\ResultSet\ResultSet object for iterating when using the Laminas\Db\Adapter\Adapter::query() method.

+
+
+

The following is an example workflow similar to what one might find inside Laminas\Db\Adapter\Adapter::query():

+
+
+
+
use Laminas\Db\Adapter\Driver\ResultInterface;
+use Laminas\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;
+    }
+}
+
+
+
+
+
+

Laminas\Db\ResultSet\ResultSet and Laminas\Db\ResultSet\AbstractResultSet

+
+
+

For most purposes, either an instance of Laminas\Db\ResultSet\ResultSet or a derivative of Laminas\Db\ResultSet\AbstractResultSet will be used. +The implementation of the AbstractResultSet offers the following core functionality:

+
+
+
+
namespace Laminas\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;
+}
+
+
+
+
+
+

Laminas\Db\ResultSet\HydratingResultSet

+
+
+

Laminas\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 laminas-hydrator, which you will need to install:

+
+
+
+
$ composer require laminas/laminas-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:

+
+
+
+
use Laminas\Db\Adapter\Driver\ResultInterface;
+use Laminas\Db\ResultSet\HydratingResultSet;
+use Laminas\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 laminas-hydrator documentation to get a better sense of the different strategies that can be employed in order to populate a target object.

+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/row-gateway.html b/docs/build/site/laminas-db-documentation/row-gateway.html new file mode 100644 index 000000000..22be2f36d --- /dev/null +++ b/docs/build/site/laminas-db-documentation/row-gateway.html @@ -0,0 +1,249 @@ + + + + + + Row Data Gateway :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

Row Data Gateway

+
+
+
+

The Row Gateway component is a sub-component of laminas-db that implements the Row Data Gateway pattern described in the book Patterns of Enterprise Application Architecture. +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():

+
+
+
+
+

Quick start

+
+
+

RowGateway is generally used in conjunction with objects that produce Laminas\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.

+
+
+
+
use Laminas\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. +In that paradigm, select() operations will produce a ResultSet that iterates RowGateway instances.

+
+
+

As an example:

+
+
+
+
use Laminas\Db\TableGateway\Feature\RowGatewayFeature;
+use Laminas\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();
+
+
+
+
+
+

It supports ActiveRecord-style objects

+
+
+

If you wish to have custom behaviour in 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:

+
+
+
+
use Laminas\Db\TableGateway\Feature\RowGatewayFeature;
+use Laminas\Db\TableGateway\TableGateway;
+use Laminas\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)));
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/sql-ddl.html b/docs/build/site/laminas-db-documentation/sql-ddl.html new file mode 100644 index 000000000..1a9f0a3ae --- /dev/null +++ b/docs/build/site/laminas-db-documentation/sql-ddl.html @@ -0,0 +1,487 @@ + + + + + + DDL Abstraction :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

DDL Abstraction

+
+
+
+

Laminas\Db\Sql\Ddl is a sub-component of Laminas\Db\Sql allowing creation of DDL (Data Definition Language) SQL statements. +When combined with a platform specific Laminas\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

    +
  • +
+
+
+
+
+

Creating Tables

+
+
+

Like Laminas\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:

+
+
+
+
use Laminas\Db\Sql\Ddl;
+use Laminas\Db\Sql\TableIdentifier;
+
+$table = new Ddl\CreateTable();
+
+// With a table name:
+$table = new Ddl\CreateTable('bar');
+
+// With a schema name "foo":
+$table = new Ddl\CreateTable(new TableIdentifier('bar', 'foo'));
+
+// Optionally, as a temporary table:
+$table = new Ddl\CreateTable('bar', true);
+
+
+
+

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 below):

+
+
+
+
use Laminas\Db\Sql\Ddl\Column;
+
+$table->addColumn(new Column\Integer('id'));
+$table->addColumn(new Column\Varchar('name', 255));
+
+
+
+

Beyond adding columns to a table, you may also add constraints:

+
+
+
+
use Laminas\Db\Sql\Ddl\Constraint;
+
+$table->addConstraint(new Constraint\PrimaryKey('id'));
+$table->addConstraint(
+    new Constraint\UniqueKey(['name', 'foo'], 'my_unique_key')
+);
+
+
+
+

You can also use the AUTO_INCREMENT attribute for MySQL:

+
+
+
+
use Laminas\Db\Sql\Ddl\Column;
+
+$column = new Column\Integer('id');
+$column->setOption('AUTO_INCREMENT', true);
+
+
+
+
+
+

Altering Tables

+
+
+

Similar to CreateTable, you may also use AlterTable instances:

+
+
+
+
use Laminas\Db\Sql\Ddl;
+use Laminas\Db\Sql\TableIdentifier;
+
+$table = new Ddl\AlterTable();
+
+// With a table name:
+$table = new Ddl\AlterTable('bar');
+
+// With a schema name "foo":
+$table = new Ddl\AlterTable(new TableIdentifier('bar', 'foo'));
+
+// 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 have the ability to alter existing columns:

+
+
+
+
use Laminas\Db\Sql\Ddl\Column;
+
+$table->changeColumn('name', Column\Varchar('new_name', 50));
+
+
+
+

You may also drop existing columns or constraints:

+
+
+
+
$table->dropColumn('foo');
+$table->dropConstraint('my_index');
+
+
+
+
+
+

Dropping Tables

+
+
+

To drop a table, create a DropTable instance:

+
+
+
+
use Laminas\Db\Sql\Ddl;
+use Laminas\Db\Sql\TableIdentifier;
+
+// With a table name:
+$drop = new Ddl\DropTable('bar');
+
+// With a schema name "foo":
+$drop = new Ddl\DropTable(new TableIdentifier('bar', 'foo'));
+
+
+
+
+
+

Executing DDL Statements

+
+
+

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:

+
+
+
+
use Laminas\Db\Sql\Sql;
+
+// Existence of $adapter is assumed.
+$sql = new Sql($adapter);
+
+$adapter->query(
+    $sql->buildSqlString($ddl),
+    $adapter::QUERY_MODE_EXECUTE
+);
+
+
+
+

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 Laminas\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 Laminas\Db\Sql\Ddl\Column namespace. +Data types must implement Laminas\Db\Sql\Ddl\Column\ColumnInterface.

+
+
+

In alphabetical order:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeArguments For Construction

BigInteger

$name, $nullable = false, $default = null, array $options = array()

Binary

$name, $length, nullable = false, $default = null, array $options = array()

Blob

$name, $length, nullable = false, $default = null, array $options = array()

Boolean

$name

Char

$name, length

Column (generic)

$name = null

Date

$name

DateTime

$name

Decimal

$name, $precision, $scale = null

Float

$name, $digits, $decimal (Note: this class is deprecated as of 2.4.0; +use Floating instead)

Floating

$name, $digits, $decimal

Integer

$name, $nullable = false, default = null, array $options = array()

Text

$name, $length, nullable = false, $default = null, array $options = array()

Time

$name

Timestamp

$name

Varbinary

$name, $length

Varchar

$name, $length

+
+

Each of the above types can be utilized in any place that accepts a Column\ColumnInterface instance. +Currently, this is primarily in CreateTable::addColumn() and AlterTable's addColumn() and changeColumn() methods.

+
+
+
+
+

Currently Supported Constraint Types

+
+
+

These types exist in the Laminas\Db\Sql\Ddl\Constraint namespace. +Data types must implement Laminas\Db\Sql\Ddl\Constraint\ConstraintInterface.

+
+
+

In alphabetical order:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeArguments For Construction

Check

$expression, $name

ForeignKey

$name, $column, $referenceTable, $referenceColumn, $onDeleteRule = null, $onUpdateRule = null

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().

+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/sql.html b/docs/build/site/laminas-db-documentation/sql.html new file mode 100644 index 000000000..459c9d82c --- /dev/null +++ b/docs/build/site/laminas-db-documentation/sql.html @@ -0,0 +1,1045 @@ + + + + + + SQL Abstraction :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

SQL Abstraction

+
+
+
+

Laminas\Db\Sql is a SQL abstraction layer for building platform-specific SQL queries via an object-oriented API. +The end result of a Laminas\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, Laminas\Db\Sql objects require a Laminas\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 Laminas\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 Laminas\Db\Sql\Sql class helps you create them and produce the result you are attempting to achieve.

+
+
+
+
use Laminas\Db\Sql\Sql;
+
+$sql    = new Sql($adapter);
+$select = $sql->select(); // returns a Laminas\Db\Sql\Select instance
+$insert = $sql->insert(); // returns a Laminas\Db\Sql\Insert instance
+$update = $sql->update(); // returns a Laminas\Db\Sql\Update instance
+$delete = $sql->delete(); // returns a Laminas\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):

+
+
+
+
use Laminas\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)

+
+
+
+
use Laminas\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);
+
+
+
+

Laminas\\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:

+
+
+
+
use Laminas\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:

+
+
+
+
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

+
+
+

Laminas\Db\Sql\Select presents a unified API for building platform-specific SQL SELECT queries. +Instances may be created and consumed without Laminas\Db\Sql\Sql:

+
+
+
+
use Laminas\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:

+
+
+
+
class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface
+{
+    const JOIN_INNER = 'inner';
+    const JOIN_OUTER = 'outer';
+    const JOIN_FULL_OUTER  = 'full 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|TableIdentifier $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()

+
+
+
// 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(['t' => new TableIdentifier('table')]);
+
+
+
+
+

columns()

+
+
+
// 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'
+]);
+
+// Sql function call on the column
+// (produces CONCAT_WS('/', 'bar', 'bax') AS 'foo')
+$select->columns([
+    'foo' => new \Laminas\Db\Sql\Expression("CONCAT_WS('/', 'bar', 'bax')")
+]);
+
+
+
+
+

join()

+
+
+
$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, full 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()

+
+

Laminas\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:

+
+
+
+
/**
+ * 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 Laminas\Db\Sql\Where instance to where() or a Laminas\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:

+
+
+
+
$select->where(function (Where $where) {
+    $where->like('username', 'ralph%');
+});
+
+
+
+

If you provide a string, this string will be used to create a Laminas\Db\Sql\Predicate\Expression instance, and its contents will be applied as-is, with no quoting:

+
+
+
+
// 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:

+
+
+
+
// 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 valuePredicate type

null

Predicate\IsNull

array

Predicate\In

string

Predicate\Operator, where the key is the identifier.

+
+

As an example:

+
+
+
+
// 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 \Laminas\Db\Sql\Predicate\IsNotNull('c3'),
+]);
+
+
+
+

As another example of complex queries with nested conditions e.g.

+
+
+
+
SELECT * WHERE (column1 is null or column1 = 2) AND (column2 = 3)
+
+
+
+

you need to use the nest() and unnest() methods, as follows:

+
+
+
+
$select->where->nest() // bracket opened
+    ->isNull('column1')
+    ->or
+    ->equalTo('column1', '2')
+    ->unnest();  // bracket closed
+    ->equalTo('column2', '3');
+
+
+
+
+

order()

+
+
+
$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()

+
+
+
$select = new Select;
+$select->limit(5);   // always takes an integer/numeric
+$select->offset(10); // similarly takes an integer/numeric
+
+
+
+
+
+
+

Insert

+
+
+

The Insert API:

+
+
+
+
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()

+
+
+
$insert->columns(['foo', 'bar']); // set the valid columns
+
+
+
+
+

values()

+
+

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',
+]);
+
+
+
+

To merge values with previous calls, provide the appropriate flag: Laminas\Db\Sql\Insert::VALUES_MERGE

+
+
+
+
$insert->values(['col_2' => 'value2'], $insert::VALUES_MERGE);
+
+
+
+
+
+
+

Update

+
+
+
+
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()

+
+
+
$update->set(['foo' => 'bar', 'baz' => 'bax']);
+
+
+
+
+

where()

+ +
+
+
+
+

Delete

+
+
+
+
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()

+ +
+
+
+
+

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 Laminas\Db\Sql\ExpressionInterface interface.

+
+
+
+
+

=== Literals

+
+
+

In Laminas 2.1, an actual Literal type was added. +Laminas\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, Laminas\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:

+
+
+
+
// 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()

+
+
+
$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:

+
+
+
+
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)

+
+
+
$where->like($identifier, $like):
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Like($identifier, $like)
+);
+
+
+
+

The following is the Like API:

+
+
+
+
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)

+
+
+
$where->literal($literal);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Literal($literal)
+);
+
+
+
+

The following is the Literal API:

+
+
+
+
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)

+
+
+
$where->expression($expression, $parameter);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Expression($expression, $parameter)
+);
+
+
+
+

The following is the Expression API:

+
+
+
+
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;
+}
+
+
+
+

Expression parameters can be supplied either as a single scalar, an array of values, or as an array of value/types for more granular escaping.

+
+
+
+
$select
+    ->from('foo')
+    ->columns([
+        new Expression(
+            '(COUNT(?) + ?) AS ?',
+            [
+                ['some_column' => ExpressionInterface::TYPE_IDENTIFIER],
+                [5 => ExpressionInterface::TYPE_VALUE],
+                ['bar' => ExpressionInterface::TYPE_IDENTIFIER],
+            ],
+        ),
+    ]);
+
+// Produces SELECT (COUNT("some_column") + '5') AS "bar" FROM "foo"
+
+
+
+
+

isNull($identifier)

+
+
+
$where->isNull($identifier);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\IsNull($identifier)
+);
+
+
+
+

The following is the IsNull API:

+
+
+
+
class IsNull implements PredicateInterface
+{
+    public function __construct(string $identifier = null);
+    public function setIdentifier(string $identifier) : self;
+    public function getIdentifier() : string;
+}
+
+
+
+
+

isNotNull($identifier)

+
+
+
$where->isNotNull($identifier);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\IsNotNull($identifier)
+);
+
+
+
+

The following is the IsNotNull API:

+
+
+
+
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)

+
+
+
$where->in($identifier, $valueSet);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\In($identifier, $valueSet)
+);
+
+
+
+

The following is the In API:

+
+
+
+
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)

+
+
+
$where->between($identifier, $minValue, $maxValue);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Between($identifier, $minValue, $maxValue)
+);
+
+
+
+

The following is the Between API:

+
+
+
+
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);
+}
+
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/sql.md.html b/docs/build/site/laminas-db-documentation/sql.md.html new file mode 100644 index 000000000..fb4555907 --- /dev/null +++ b/docs/build/site/laminas-db-documentation/sql.md.html @@ -0,0 +1,1011 @@ + + + + + + SQL Abstraction :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

SQL Abstraction

+
+
+
+

Laminas\Db\Sql is a SQL abstraction layer for building platform-specific SQL queries via an object-oriented API. +The end result of a Laminas\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, Laminas\Db\Sql objects require a Laminas\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 Laminas\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 Laminas\Db\Sql\Sql class helps you create them and produce the result you are attempting to achieve.

+
+
+
+
use Laminas\Db\Sql\Sql;
+
+$sql    = new Sql($adapter);
+$select = $sql->select(); // returns a Laminas\Db\Sql\Select instance
+$insert = $sql->insert(); // returns a Laminas\Db\Sql\Insert instance
+$update = $sql->update(); // returns a Laminas\Db\Sql\Update instance
+$delete = $sql->delete(); // returns a Laminas\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):

+
+
+
+
use Laminas\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)

+
+
+
+
use Laminas\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);
+
+
+
+

Laminas\\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:

+
+
+
+
use Laminas\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:

+
+
+
+
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

+
+
+

Laminas\Db\Sql\Select presents a unified API for building platform-specific SQL SELECT queries. +Instances may be created and consumed without Laminas\Db\Sql\Sql:

+
+
+
+
use Laminas\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:

+
+
+
+
class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface
+{
+    const JOIN_INNER = 'inner';
+    const JOIN_OUTER = 'outer';
+    const JOIN_FULL_OUTER  = 'full 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|TableIdentifier $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()

+
+
+
// 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(['t' => new TableIdentifier('table')]);
+
+
+
+
+

columns()

+
+
+
// 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'
+]);
+
+// Sql function call on the column
+// (produces CONCAT_WS('/', 'bar', 'bax') AS 'foo')
+$select->columns([
+    'foo' => new \Laminas\Db\Sql\Expression("CONCAT_WS('/', 'bar', 'bax')")
+]);
+
+
+
+
+

join()

+
+
+
$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, full 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()

+
+

Laminas\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:

+
+
+
+
/**
+ * 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 Laminas\Db\Sql\Where instance to where() or a Laminas\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:

+
+
+
+
$select->where(function (Where $where) {
+    $where->like('username', 'ralph%');
+});
+
+
+
+

If you provide a string, this string will be used to create a Laminas\Db\Sql\Predicate\Expression instance, and its contents will be applied as-is, with no quoting:

+
+
+
+
// 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:

+
+
+
+
// 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 valuePredicate type

null

Predicate\IsNull

array

Predicate\In

string

Predicate\Operator, where the key is the identifier.

+
+

As an example:

+
+
+
+
// 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 \Laminas\Db\Sql\Predicate\IsNotNull('c3'),
+]);
+
+
+
+

As another example of complex queries with nested conditions e.g.

+
+
+
+
SELECT * WHERE (column1 is null or column1 = 2) AND (column2 = 3)
+
+
+
+

you need to use the nest() and unnest() methods, as follows:

+
+
+
+
$select->where->nest() // bracket opened
+    ->isNull('column1')
+    ->or
+    ->equalTo('column1', '2')
+    ->unnest();  // bracket closed
+    ->equalTo('column2', '3');
+
+
+
+
+

order()

+
+
+
$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()

+
+
+
$select = new Select;
+$select->limit(5);   // always takes an integer/numeric
+$select->offset(10); // similarly takes an integer/numeric
+
+
+
+
+
+
+

Insert

+
+
+

The Insert API:

+
+
+
+
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()

+
+
+
$insert->columns(['foo', 'bar']); // set the valid columns
+
+
+
+
+

values()

+
+

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',
+]);
+
+
+
+

To merge values with previous calls, provide the appropriate flag: Laminas\Db\Sql\Insert::VALUES_MERGE

+
+
+
+
$insert->values(['col_2' => 'value2'], $insert::VALUES_MERGE);
+
+
+
+
+
+
+

Update

+
+
+
+
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()

+
+
+
$update->set(['foo' => 'bar', 'baz' => 'bax']);
+
+
+
+
+

where()

+ +
+
+
+
+

Delete

+
+
+
+
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()

+ +
+
+
+
+

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 Laminas\Db\Sql\ExpressionInterface interface.

+
+
+
+
+

=== Literals

+
+
+

In Laminas 2.1, an actual Literal type was added. +Laminas\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, Laminas\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:

+
+
+
+
// 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()

+
+
+
$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:

+
+
+
+
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)

+
+
+
$where->like($identifier, $like):
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Like($identifier, $like)
+);
+
+
+
+

The following is the Like API:

+
+
+
+
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)

+
+
+
$where->literal($literal);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Literal($literal)
+);
+
+
+
+

The following is the Literal API:

+
+
+
+
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)

+
+
+
$where->expression($expression, $parameter);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Expression($expression, $parameter)
+);
+
+
+
+

The following is the Expression API:

+
+
+
+
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;
+}
+
+
+
+

Expression parameters can be supplied either as a single scalar, an array of values, or as an array of value/types for more granular escaping.

+
+
+
+
$select
+    ->from('foo')
+    ->columns([
+        new Expression(
+            '(COUNT(?) + ?) AS ?',
+            [
+                ['some_column' => ExpressionInterface::TYPE_IDENTIFIER],
+                [5 => ExpressionInterface::TYPE_VALUE],
+                ['bar' => ExpressionInterface::TYPE_IDENTIFIER],
+            ],
+        ),
+    ]);
+
+// Produces SELECT (COUNT("some_column") + '5') AS "bar" FROM "foo"
+
+
+
+
+

isNull($identifier)

+
+
+
$where->isNull($identifier);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\IsNull($identifier)
+);
+
+
+
+

The following is the IsNull API:

+
+
+
+
class IsNull implements PredicateInterface
+{
+    public function __construct(string $identifier = null);
+    public function setIdentifier(string $identifier) : self;
+    public function getIdentifier() : string;
+}
+
+
+
+
+

isNotNull($identifier)

+
+
+
$where->isNotNull($identifier);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\IsNotNull($identifier)
+);
+
+
+
+

The following is the IsNotNull API:

+
+
+
+
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)

+
+
+
$where->in($identifier, $valueSet);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\In($identifier, $valueSet)
+);
+
+
+
+

The following is the In API:

+
+
+
+
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)

+
+
+
$where->between($identifier, $minValue, $maxValue);
+
+// The above is equivalent to:
+$where->addPredicate(
+    new Predicate\Between($identifier, $minValue, $maxValue)
+);
+
+
+
+

The following is the Between API:

+
+
+
+
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);
+}
+
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/build/site/laminas-db-documentation/table-gateway.html b/docs/build/site/laminas-db-documentation/table-gateway.html new file mode 100644 index 000000000..d620aeedd --- /dev/null +++ b/docs/build/site/laminas-db-documentation/table-gateway.html @@ -0,0 +1,533 @@ + + + + + + Table Data Gateways :: The laminas-db documentation + + + + + +
+ +
+
+ +
+ +
+ +
+

Table Data Gateways

+
+
+
+

The Table Gateway sub-component provides an object-oriented representation of a database table; +its methods mirror the most common table operations. +In code, the interface resembles:

+
+
+
+
namespace Laminas\Db\TableGateway;
+
+use Laminas\Db\ResultSet\ResultSetInterface;
+use Laminas\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 Laminas\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 Laminas\Db\TableGateway\TableGateway, which defines the following API:

+
+
+
+
namespace Laminas\Db\TableGateway;
+
+use Laminas\Db\Adapter\AdapterInterface;
+use Laminas\Db\ResultSet\ResultSet;
+use Laminas\Db\ResultSet\ResultSetInterface;
+use Laminas\Db\Sql;
+use Laminas\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.

+
+
+
+
use Laminas\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 Laminas\Db\Sql\Select::where(); +arguments will be passed to the Select instance used to build the SELECT query. +This means the following is possible:

+
+
+
+
use Laminas\Db\TableGateway\TableGateway;
+use Laminas\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 through 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 laminas-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:

    +
    +
    +
      use Laminas\Db\TableGateway\AbstractTableGateway;
    +  use Laminas\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
    +  Laminas\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.

    +
    +
    +
      $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.

    +
    +
    +
      $table = new TableGateway('artist', $adapter, new Feature\MetadataFeature());
    +
    +
    +
  • +
  • +

    EventFeature: the ability to compose a laminas-eventmanager EventManager instance within your TableGateway instance, and attach listeners to the various events of its lifecycle. +See the section on lifecycle events below for more information on available events and the parameters they compose.

    +
    +
    +
      $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.

    +
    +
    +
      $table   = new TableGateway('artist', $adapter, new Feature\RowGatewayFeature('id'));
    +  $results = $table->select(['id' => 2]);
    +
    +  $artistRow       = $results->current();
    +  $artistRow->name = 'New Name';
    +  $artistRow->save();
    +
    +
    +
  • +
+
+
+
+
+

TableGateway LifeCycle Events

+
+
+

When the EventFeature is enabled on the TableGateway instance, you may attach to any of the following events, which provide access to the parameters listed.

+
+
+
    +
  • +

    preInitialize (no parameters)

    +
  • +
  • +

    postInitialize (no parameters)

    +
  • +
  • +

    preSelect, with the following parameters:

    +
    +
      +
    • +

      select, with type Laminas\Db\Sql\Select

      +
    • +
    +
    +
  • +
  • +

    postSelect, with the following parameters:

    +
    +
      +
    • +

      statement, with type Laminas\Db\Adapter\Driver\StatementInterface

      +
    • +
    • +

      result, with type Laminas\Db\Adapter\Driver\ResultInterface

      +
    • +
    • +

      resultSet, with type Laminas\Db\ResultSet\ResultSetInterface

      +
    • +
    +
    +
  • +
  • +

    preInsert, with the following parameters:

    +
    +
      +
    • +

      insert, with type Laminas\Db\Sql\Insert

      +
    • +
    +
    +
  • +
  • +

    postInsert, with the following parameters:

    +
    +
      +
    • +

      statement with type Laminas\Db\Adapter\Driver\StatementInterface

      +
    • +
    • +

      result with type Laminas\Db\Adapter\Driver\ResultInterface

      +
    • +
    +
    +
  • +
  • +

    preUpdate, with the following parameters:

    +
    +
      +
    • +

      update, with type Laminas\Db\Sql\Update

      +
    • +
    +
    +
  • +
  • +

    postUpdate, with the following parameters:

    +
    +
      +
    • +

      statement, with type Laminas\Db\Adapter\Driver\StatementInterface

      +
    • +
    • +

      result, with type Laminas\Db\Adapter\Driver\ResultInterface

      +
    • +
    +
    +
  • +
  • +

    preDelete, with the following parameters:

    +
    +
      +
    • +

      delete, with type Laminas\Db\Sql\Delete

      +
    • +
    +
    +
  • +
  • +

    postDelete, with the following parameters:

    +
    +
      +
    • +

      statement, with type Laminas\Db\Adapter\Driver\StatementInterface

      +
    • +
    • +

      result, with type Laminas\Db\Adapter\Driver\ResultInterface

      +
    • +
    +
    +
  • +
+
+
+

Listeners receive a Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent instance as an argument. +Within the listener, you can retrieve a parameter by name from the event using the following syntax:

+
+
+
+
$parameter = $event->getParam($paramName);
+
+
+
+

As an example, you might attach a listener on the postInsert event as follows:

+
+
+
+
use Laminas\Db\Adapter\Driver\ResultInterface;
+use Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent;
+use Laminas\EventManager\EventManager;
+
+/** @var EventManager $eventManager */
+$eventManager->attach('postInsert', function (TableGatewayEvent $event) {
+    /** @var ResultInterface $result */
+    $result = $event->getParam('result');
+    $generatedId = $result->getGeneratedValue();
+
+    // do something with the generated identifier...
+});
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + diff --git a/docs/local-antora-playbook.yml b/docs/local-antora-playbook.yml new file mode 100644 index 000000000..92ae4f978 --- /dev/null +++ b/docs/local-antora-playbook.yml @@ -0,0 +1,12 @@ +site: + title: The laminas-db documentation + start_page: laminas-db-documentation::index.adoc +content: + sources: + - url: ../ + branches: HEAD + start_path: docs-site +ui: + bundle: + url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable + snapshot: true diff --git a/docs/modules/ROOT/attachments/.gitkeep b/docs/modules/ROOT/attachments/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/modules/ROOT/examples/.gitkeep b/docs/modules/ROOT/examples/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/modules/ROOT/images/.gitkeep b/docs/modules/ROOT/images/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/modules/ROOT/images/laminas-db-architecture-overview.odg b/docs/modules/ROOT/images/laminas-db-architecture-overview.odg new file mode 100644 index 000000000..d93b31946 Binary files /dev/null and b/docs/modules/ROOT/images/laminas-db-architecture-overview.odg differ diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 000000000..d3cefe249 --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,10 @@ +* xref:index.adoc[] +** xref:getting-started.adoc[Getting Started] +** xref:adapter.adoc[Adapters] +*** xref:adapters/adapter-aware-trait.adoc[Adapter-aware Trait] +** xref:metadata.adoc[Metadata] +** xref:result-set.adoc[Result Sets] +** xref:row-gateway.adoc[Row Gateways] +** xref:table-gateway.adoc[Table Gateways] +** xref:sql.adoc[SQL Abstraction] +*** xref:sql-ddl.adoc[DDL Abstraction] diff --git a/docs/book/adapter.md b/docs/modules/ROOT/pages/adapter.adoc similarity index 56% rename from docs/book/adapter.md rename to docs/modules/ROOT/pages/adapter.adoc index 95ad7de7a..36934cca7 100644 --- a/docs/book/adapter.md +++ b/docs/modules/ROOT/pages/adapter.adoc @@ -1,70 +1,93 @@ -# Adapters += Adapters -`Laminas\Db\Adapter\Adapter` is the central object of the laminas-db component. It is -responsible for adapting any code written in or for laminas-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 `Laminas\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. +`Laminas\Db\Adapter\Adapter` is the central object of the laminas-db component. +It is responsible for adapting any code written in or for laminas-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 `Laminas\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 +== Creating an adapter using configuration -Create an adapter by instantiating the `Laminas\Db\Adapter\Adapter` class. The most -common use case, while not the most explicit, is to pass an array of -configuration to the `Adapter`: +Create an adapter by instantiating the `Laminas\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 +[,php] +---- use Laminas\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. +|=== +| 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 +[,php] +---- $adapter = new Laminas\Db\Adapter\Adapter([ 'driver' => 'Mysqli', 'database' => 'laminas_db_example', 'username' => 'developer', 'password' => 'developer-password', ]); -``` +---- Another example, of a Sqlite connection via PDO: -```php +[,php] +---- $adapter = new Laminas\Db\Adapter\Adapter([ 'driver' => 'Pdo_Sqlite', 'database' => 'path/to/sqlite.db', ]); -``` +---- Another example, of an IBM i DB2 connection via IbmDb2: -```php +[,php] +---- $adapter = new Laminas\Db\Adapter\Adapter([ 'database' => '*LOCAL', // or name from WRKRDBDIRE, may be serial # 'driver' => 'IbmDb2', @@ -79,11 +102,12 @@ $adapter = new Laminas\Db\Adapter\Adapter([ 'platform' => 'IbmDb2', 'platform_options' => ['quote_identifiers' => false], ]); -``` +---- Another example, of an IBM i DB2 connection via PDO: -```php +[,php] +---- $adapter = new Laminas\Db\Adapter\Adapter([ 'dsn' => 'ibm:DB_NAME', // DB_NAME is from WRKRDBDIRE, may be serial # 'driver' => 'pdo', @@ -99,35 +123,32 @@ $adapter = new Laminas\Db\Adapter\Adapter([ 'platform' => 'IbmDb2', 'platform_options' => ['quote_identifiers' => false], ]); -``` +---- -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. +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 driver -- `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 +* `IbmDb2`: The ext/ibm_db2 driver +* `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 +== Creating an adapter using dependency injection -The more mezzio and explicit way of creating an adapter is by injecting all -your dependencies up front. `Laminas\Db\Adapter\Adapter` uses constructor -injection, and all required dependencies are injected through the constructor, -which has the following signature (in pseudo-code): +The more mezzio and explicit way of creating an adapter is by injecting all your dependencies up front. +`Laminas\Db\Adapter\Adapter` uses constructor injection, and all required dependencies are injected through the constructor, which has the following signature (in pseudo-code): -```php +[,php] +---- use Laminas\Db\Adapter\Platform\PlatformInterface; use Laminas\Db\ResultSet\ResultSet; @@ -139,100 +160,88 @@ class Laminas\Db\Adapter\Adapter ResultSet $queryResultSetPrototype = null ); } -``` +---- What can be injected: -- `$driver`: an array of connection parameters (see above) or an instance of - `Laminas\Db\Adapter\Driver\DriverInterface`. -- `$platform` (optional): an instance of `Laminas\Db\Platform\PlatformInterface`; - the default will be created based off the driver implementation. -- `$queryResultSetPrototype` (optional): an instance of - `Laminas\Db\ResultSet\ResultSet`; to understand this object's role, see the - section below on querying. +* `$driver`: an array of connection parameters (see above) or an instance of `Laminas\Db\Adapter\Driver\DriverInterface`. +* `$platform` (optional): an instance of `Laminas\Db\Platform\PlatformInterface`; +the default will be created based off the driver implementation. +* `$queryResultSetPrototype` (optional): an instance of `Laminas\Db\ResultSet\ResultSet`; +to understand this object's role, see the section below on querying. -## Query Preparation +== Query Preparation -By default, `Laminas\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: +By default, `Laminas\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 +[,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`. +* 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 +== 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. +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: +To execute a query without the preparation step, you will need to pass a flag as the second argument indicating execution is required: -```php +[,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. +The primary difference to notice is that you must provide the `Adapter::QUERY_MODE_EXECUTE` (execute) flag as the second parameter. -## Creating Statements +== 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. +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 +[,php] +---- // with optional parameters to bind up-front: $statement = $adapter->createStatement($sql, $optionalParameters); $result = $statement->execute(); -``` +---- -## Using the Driver Object +== Using the Driver Object -The `Driver` object is the primary place where `Laminas\Db\Adapter\Adapter` -implements the connection level abstraction specific to a given extension. To -make this possible, each driver is composed of 3 objects: +The `Driver` object is the primary place where `Laminas\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: `Laminas\Db\Adapter\Driver\ConnectionInterface` -- A statement: `Laminas\Db\Adapter\Driver\StatementInterface` -- A result: `Laminas\Db\Adapter\Driver\ResultInterface` +* A connection: `Laminas\Db\Adapter\Driver\ConnectionInterface` +* A statement: `Laminas\Db\Adapter\Driver\StatementInterface` +* A result: `Laminas\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: +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, - `Laminas\Db\Adapter\Driver\Mysqli`) -- That driver class is instantiated. -- If no connection, statement, or result objects are injected, defaults are - instantiated. +* An adapter is created with a set of connection parameters. +* The adapter chooses the proper driver to instantiate (for example, `Laminas\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: +This driver is now ready to be called on when particular workflows are requested. +Here is what the `Driver` API looks like: -```php +[,php] +---- namespace Laminas\Db\Adapter\Driver; interface DriverInterface @@ -251,25 +260,23 @@ interface DriverInterface 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). +* 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 +[,php] +---- namespace Laminas\Db\Adapter\Driver; interface StatementInterface extends StatementContainerInterface @@ -285,11 +292,12 @@ interface StatementInterface extends StatementContainerInterface public function setParameterContainer(ParameterContainer $parameterContainer) : void; public function getParameterContainer() : ParameterContainer; } -``` +---- And finally, the `Result` API: -```php +[,php] +---- namespace Laminas\Db\Adapter\Driver; use Countable; @@ -304,17 +312,16 @@ interface ResultInterface extends Countable, Iterator public function getResource() : resource; public function getFieldCount() : int; } -``` +---- -## Using The Platform Object +== 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: +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 +[,php] +---- namespace Laminas\Db\Adapter\Platform; interface PlatformInterface @@ -330,22 +337,22 @@ interface PlatformInterface 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): +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 +[,php] +---- $platform = $adapter->getPlatform(); // or $platform = $adapter->platform; // magic property access -``` +---- The following are examples of `Platform` usage: -```php +[,php] +---- // $adapter is a Laminas\Db\Adapter\Adapter instance; // $platform is a Laminas\Db\Adapter\Platform\Sql92 instance. $platform = $adapter->getPlatform(); @@ -377,16 +384,16 @@ 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 +== 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: +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 +[,php] +---- namespace Laminas\Db\Adapter; use ArrayAccess; @@ -434,33 +441,32 @@ class ParameterContainer implements Iterator, ArrayAccess, Countable /** 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: +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 +[,php] +---- $container->offsetSet('limit', 5); -``` +---- -be bound as an integer. To achieve this, pass in the -`ParameterContainer::TYPE_INTEGER` constant as the 3rd parameter: +be bound as an integer. +To achieve this, pass in the `ParameterContainer::TYPE_INTEGER` constant as the 3rd parameter: -```php +[,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. +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 +== Examples -Creating a `Driver`, a vendor-portable query, and preparing and iterating the -result: +Creating a `Driver`, a vendor-portable query, and preparing and iterating the result: -```php +[,php] +---- $adapter = new Laminas\Db\Adapter\Adapter($driverConfig); $qi = function ($name) use ($adapter) { @@ -495,4 +501,4 @@ $results = $statement->execute(['id' => 1]); $row = $results->current(); $name = $row['name']; -``` +---- diff --git a/docs/modules/ROOT/pages/adapters/adapter-aware-trait.adoc b/docs/modules/ROOT/pages/adapters/adapter-aware-trait.adoc new file mode 100644 index 000000000..c6a0aab13 --- /dev/null +++ b/docs/modules/ROOT/pages/adapters/adapter-aware-trait.adoc @@ -0,0 +1,126 @@ += AdapterAwareTrait + +The trait `Laminas\Db\Adapter\AdapterAwareTrait`, which provides implementation for `Laminas\Db\Adapter\AdapterAwareInterface`, and allowed removal of duplicated implementations in several components of Laminas or in custom applications. + +The interface defines only the method `setDbAdapter()` with one parameter for an instance of `Laminas\Db\Adapter\Adapter`: + +[,php] +---- +public function setDbAdapter(\Laminas\Db\Adapter\Adapter $adapter) : self; +---- + +== Basic Usage + +=== Create Class and Add Trait + +[,php] +---- +use Laminas\Db\Adapter\AdapterAwareTrait; +use Laminas\Db\Adapter\AdapterAwareInterface; + +class Example implements AdapterAwareInterface +{ + use AdapterAwareTrait; +} +---- + +=== Create and Set Adapter + +link:../adapter.md#creating-an-adapter-using-configuration[Create a database adapter] and set the adapter to the instance of the `Example` class: + +[,php] +---- +$adapter = new Laminas\Db\Adapter\Adapter([ + 'driver' => 'Pdo_Sqlite', + 'database' => 'path/to/sqlite.db', +]); + +$example = new Example(); +$example->setAdapter($adapter); +---- + +== AdapterServiceDelegator + +The https://docs.laminas.dev/laminas-servicemanager/delegators/[delegator] `Laminas\Db\Adapter\AdapterServiceDelegator` can be used to set a database adapter via the https://docs.laminas.dev/laminas-servicemanager/quick-start/[service manager of laminas-servicemanager]. + +The delegator tries to fetch a database adapter via the name `Laminas\Db\Adapter\AdapterInterface` from the service container and sets the adapter to the requested service. +The adapter itself must be an instance of `Laminas\Db\Adapter\Adapter`. + +____ +=== Integration for Mezzio and laminas-mvc based Applications + +In a Mezzio or laminas-mvc based application the database adapter is already registered during the installation with the laminas-component-installer. +____ + +=== Create Class and Use Trait + +Create a class and add the trait `AdapterAwareTrait`. + +[,php] +---- +use Laminas\Db\Adapter\Adapter; +use Laminas\Db\Adapter\AdapterInterface; + +class Example implements AdapterAwareInterface +{ + use AdapterAwareTrait; + + public function getAdapter() : ?Adapter + { + return $this->adapter; + } +} +---- + +(A getter method is also added for demonstration.) + +=== Create and Configure Service Manager + +Create and https://docs.laminas.dev/laminas-servicemanager/configuring-the-service-manager/[configured the service manager]: + +[,php] +---- +use Interop\Container\ContainerInterface; +use Laminas\Db\Adapter\AdapterInterface; +use Laminas\Db\Adapter\AdapterServiceDelegator; +use Laminas\Db\Adapter\AdapterAwareTrait; +use Laminas\Db\Adapter\AdapterAwareInterface; + +$serviceManager = new Laminas\ServiceManager\ServiceManager([ + 'factories' => [ + // Database adapter + AdapterInterface::class => static function(ContainerInterface $container) { + return new Laminas\Db\Adapter\Adapter([ + 'driver' => 'Pdo_Sqlite', + 'database' => 'path/to/sqlite.db', + ]); + } + ], + 'invokables' => [ + // Example class + Example::class => Example::class, + ], + 'delegators' => [ + // Delegator for Example class to set the adapter + Example::class => [ + AdapterServiceDelegator::class, + ], + ], +]); +---- + +=== Get Instance of Class + +https://docs.laminas.dev/laminas-servicemanager/quick-start/#3-retrieving-objects[Retrieving an instance] of the `Example` class with a database adapter: + +[,php] +---- +/** @var Example $example */ +$example = $serviceManager->get(Example::class); + +var_dump($example->getAdapter() instanceof Laminas\Db\Adapter\Adapter); // true +---- + +== Concrete Implementations + +The validators https://docs.laminas.dev/laminas-validator/validators/db/[`Db\RecordExists` and `Db\NoRecordExists`] implements the trait and the plugin manager of https://docs.laminas.dev/laminas-validator/[laminas-validator] includes the delegator to set the database adapter for both validators. diff --git a/docs/book/application-integration/usage-in-a-laminas-mvc-application.md b/docs/modules/ROOT/pages/application-integration/usage-in-a-laminas-mvc-application.adoc similarity index 80% rename from docs/book/application-integration/usage-in-a-laminas-mvc-application.md rename to docs/modules/ROOT/pages/application-integration/usage-in-a-laminas-mvc-application.adoc index 7082e299d..078d39e45 100644 --- a/docs/book/application-integration/usage-in-a-laminas-mvc-application.md +++ b/docs/modules/ROOT/pages/application-integration/usage-in-a-laminas-mvc-application.adoc @@ -1,31 +1,32 @@ -# Usage in a laminas-mvc Application += Usage in a laminas-mvc Application The minimal installation for a laminas-mvc based application doesn't include any database features. -## When installing the Laminas MVC Skeleton Application +== When installing the Laminas MVC Skeleton Application -While `Composer` is [installing the MVC Application](https://docs.laminas.dev/laminas-mvc/quick-start/#install-the-laminas-mvc-skeleton-application), you can add the `laminas-db` package while prompted. +While `Composer` is https://docs.laminas.dev/laminas-mvc/quick-start/#install-the-laminas-mvc-skeleton-application[installing the MVC Application], you can add the `laminas-db` package while prompted. -## Adding to an existing Laminas MVC Skeleton Application +== Adding to an existing Laminas MVC Skeleton Application -If the MVC application is already created, then use Composer to [add the laminas-db](../index.md) package. +If the MVC application is already created, then use Composer to xref:../index.adoc[add the laminas-db] package. -## The Abstract Factory +== The Abstract Factory Now that the laminas-db package is installed, the abstract factory `Laminas\Db\Adapter\AdapterAbstractServiceFactory` is available to be used with the service configuration. -### Configuring the adapter +=== Configuring the adapter The abstract factory expects the configuration key `db` in order to create a `Laminas\Db\Adapter\Adapter` instance. -### Working with a Sqlite database +=== Working with a Sqlite database Sqlite is a lightweight option to have the application working with a database. Here is an example of the configuration array for a sqlite database. Assuming the sqlite file path is `data/sample.sqlite`, the following configuration will produce the adapter: -```php +[,php] +---- return [ 'db' => [ 'driver' => 'Pdo', @@ -37,17 +38,18 @@ return [ ], ], ]; -``` +---- The `data/` filepath for the sqlite file is the default `data/` directory from the Laminas MVC application. -### Working with a MySQL database +=== Working with a MySQL database Unlike a sqlite database, the MySQL database adapter requires a MySQL server. Here is an example of a configuration array for a MySQL database. -```php +[,php] +---- return [ 'db' => [ 'driver' => 'Pdo', @@ -64,40 +66,43 @@ return [ ], ], ]; -``` +---- -## Working with the adapter +== Working with the adapter Once you have configured an adapter, as in the above examples, you now have a `Laminas\Db\Adapter\Adapter` available to your application. A factory for a class that consumes an adapter can pull the adapter by the name used in configuration. As an example, for the sqlite database configured earlier, we could write the following: -```php +[,php] +---- use sqliteAdapter ; $adapter = $container->get(sqliteAdapter::class) ; -``` +---- For the MySQL Database configured earlier: -```php +[,php] +---- use mysqlAdapter ; $adapter = $container->get(mysqlAdapter::class) ; -``` +---- -You can read more about the [adapter in the adapter chapter of the documentation](../adapter.md). +You can read more about the xref:../adapter.adoc[adapter in the adapter chapter of the documentation]. -## Running with Docker +== Running with Docker When working with a MySQL database and when running the application with Docker, some files need to be added or adjusted. -### Adding the MySQL extension to the PHP container +=== Adding the MySQL extension to the PHP container Change the `Dockerfile` to add the PDO MySQL extension to PHP. -```Dockerfile +[,Dockerfile] +---- FROM php:7.3-apache RUN apt-get update \ @@ -110,13 +115,14 @@ RUN apt-get update \ | php -- --install-dir=/usr/local/bin --filename=composer WORKDIR /var/www -``` +---- -### Adding the mysql container +=== Adding the mysql container Change the `docker-compose.yml` file to add a new container for mysql. -```yaml +[,yaml] +---- mysql: image: mysql ports: @@ -128,20 +134,21 @@ Change the `docker-compose.yml` file to add a new container for mysql. - ./.docker/mysql/:/docker-entrypoint-initdb.d/ environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} -``` +---- Though it is not the topic to explain how to write a `docker-compose.yml` file, a few details need to be highlighted : -- The name of the container is `mysql`. -- MySQL database files will be stored in the directory `/.data/db/`. -- SQL schemas will need to be added to the `/.docker/mysql/` directory so that Docker will be able to build and populate the database(s). -- The mysql docker image is using the `$MYSQL_ROOT_PASSWORD` environment variable to set the mysql root password. +* The name of the container is `mysql`. +* MySQL database files will be stored in the directory `/.data/db/`. +* SQL schemas will need to be added to the `/.docker/mysql/` directory so that Docker will be able to build and populate the database(s). +* The mysql docker image is using the `$MYSQL_ROOT_PASSWORD` environment variable to set the mysql root password. -### Link the containers +=== Link the containers Now link the mysql container and the laminas container so that the application knows where to find the mysql server. -```yaml +[,yaml] +---- laminas: build: context: . @@ -152,27 +159,29 @@ Now link the mysql container and the laminas container so that the application k - .:/var/www links: - mysql:mysql -``` +---- -### Adding phpMyAdmin +=== Adding phpMyAdmin Optionnally, you can also add a container for phpMyAdmin. -```yaml +[,yaml] +---- phpmyadmin: image: phpmyadmin/phpmyadmin ports: - 8081:80 environment: - PMA_HOST=${PMA_HOST} -``` +---- The image uses the `$PMA_HOST` environment variable to set the host of the mysql server. The expected value is the name of the mysql container. Putting everything together: -```yaml +[,yaml] +---- version: "2.1" services: laminas: @@ -202,20 +211,21 @@ services: - 8081:80 environment: - PMA_HOST=${PMA_HOST} -``` +---- -### Defining credentials +=== Defining credentials The `docker-compose.yml` file uses ENV variables to define the credentials. Docker will read the ENV variables from a `.env` file. -```env +[,env] +---- MYSQL_ROOT_PASSWORD=rootpassword PMA_HOST=mysql -``` +---- -### Initiating the database schemas +=== Initiating the database schemas At build, if the `/.data/db` directory is missing, Docker will create the mysql database with any `.sql` files found in the `.docker/mysql/` directory. (These are the files with the `CREATE DATABASE`, `USE (database)`, and `CREATE TABLE, INSERT INTO` directives defined earlier in this document). diff --git a/docs/modules/ROOT/pages/getting-started.adoc b/docs/modules/ROOT/pages/getting-started.adoc new file mode 100644 index 000000000..3b95f3942 --- /dev/null +++ b/docs/modules/ROOT/pages/getting-started.adoc @@ -0,0 +1,11 @@ +== Getting started + +Assuming that you have https://getcomposer.org[Composer] installed globally, run the following command in your project directory to install laminas-db: + +[source,bash] +---- +composer require laminas/laminas-db +---- + +=== Configuring laminas-db + diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 000000000..a57b69f4a --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,134 @@ += What is laminas-db? +:tabledatagateway-definition: https://www.martinfowler.com/eaaCatalog/tableDataGateway.html +:rowdatagateway-definition: https://martinfowler.com/eaaCatalog/rowDataGateway.html +:ibmdb2-driver: https://www.php.net/manual/en/intro.ibm-db2.php + +Laminas-db simplifies database interaction in your PHP projects, providing a unified, programmatic interface for working with databases. +It avoids the need for you to hand-craft SQL, letting you build objects that construct SQL for you instead. + +The project provides database, SQL, and result set abstraction layers, a {rowdatagateway-definition}[Row data gateway] and {tabledatagateway-definition}[Table data gateway] implementation, plus so much more. + +Before we dive deeper let's quickly consider four solid reasons why laminas-db is an excellent choice for your project. + +== Why would you use laminas-db? + +PHP is not lacking in packages that provide database support. +There's the https://www.doctrine-project.org/projects/doctrine-dbal/en/4.3/index.html[Doctrine (database abstraction layer)], https://laravel.com/docs/12.x/eloquent[Laravel's Eloquent], and https://propelorm.org/[Propel] to name but a few of the better known ones. +Well, here are a few solid reasons why it's worth using laminas-db. + +=== It's pretty quick and easy to learn + +Assuming that you have a basic understanding of SQL (https://en.wikipedia.org/wiki/SQL-92[92] or above) and OOP (object-oriented programming) in PHP, then you should be able to get up and running with laminas-db pretty quickly. +This is because most of the classes model some aspect of SQL. + +For example, if you need to build a select, insert, update, or delete query, there are xref:sql.adoc[the Select, Insert, Update, and Delete objects]. +If you need to create, alter, and drop tables, there are xref:sql-ddl.adoc[the CreateTable, AlterTable, and DropTable objects]. +What's more, where applicable, they're all designed to work with one another. + +=== It provides significant flexibility + +While OOP is the most common way to use the library, you don't have to use an object for every operation. +For example, you can build queries using objects, or a combination of objects and free form strings if that's what you prefer. +The library works with you, not against you. + +Take the following, quite basic, SQL query as an example: + +[source,sql] +---- +SELECT first_name, last_name, email_address +FROM users +WHERE dob >= "2006.04.05"; +---- + +In the above SQL query, we're retrieving the first name, last name, and email address of every user from the users table whose date of birth was on or after the 5th of April, 2006. + +Here's how we could write it with objects: + +[source,sql] +---- +use Laminas\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); +$select->from('users'); +$select->columns(['first_name', 'last_name', 'email_address']); +$select->where(['dob' >= "2006.04.05"]); +---- + +In the above example, you can see an example of instantiating a `Select` object that queries the "users" table for the required columns, where https://www.geeksforgeeks.org/sql/sql-where-clause/[the where clause] is defined using a plain PHP associative array. + +// Eventually, link `Where` to the relevant section of the docs +However, we could get a little fancier by defining the where clause using a `Where` object instead, as in the following example. + +[source,php] +---- +use Laminas\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); +$select->from('users'); +$select->columns(['first_name', 'last_name', 'email_address']); +$select->where(['dob' >= "2006.04.05"]); +---- + +While these two examples are quite simplistic, they hint at what is possible with the library. + +=== Clear code. No magic + +There's nothing magic about how laminas-db works. +It only runs the queries that you ask it to run, as *you* asked it to run them. +It doesn't add extra columns to your queries. +It doesn't run nested queries without your knowledge. +It doesn't attempt to be smart on your behalf. + +Sure, that might mean that there's a bit more work for you to do. +But, at all times, you know what's going on. + +=== It's community-driven + +Since its inception, laminas-db has been an open source, community driven and lead project. +It was designed and developed *by* developers *for* developers, to be a programmatic way of building and running SQL queries, against a range of database vendors. + +Given that, you can view its entire history on GitHub, and contribute to it, as and when you find a bug or missing feature that you want to implement. +You're able to influence its direction. +You don't need to wait for it to come to you. + +== laminas-db's architecture + +=== Laminas\Db\Adapter\Adapter + +It all starts with `Laminas\Db\Adapter\Adapter`, which is laminas-db's central object. +It is responsible for adapting any code written in or for laminas-db to the targeted PHP extensions and vendor databases; these are: + +.The list of officially supported drivers +[cols="25h,~",%autowidth,stripes=even,options="header"] +|=== +|Database +|PHP Extension/Driver + +|IBM Db2 +|The {ibmdb2-driver}[ext/ibm_db2 driver] + +|MySQL +|The https://www.php.net/manual/en/intro.mysqli.php[ext/mysqli driver] and the https://www.php.net/manual/en/ref.pdo-mysql.php[Pdo_Mysql PDO extension] + +|Oracle +|The https://www.php.net/manual/en/intro.oci8.php[ext/oci8 driver] + +|PostgreSQL +|The https://www.php.net/manual/en/intro.pgsql.php[ext/pgsql driver] and the https://www.php.net/manual/en/ref.pdo-pgsql.php[Pdo_Pgsql PDO extension] + +|Microsoft SQLServer +|The https://www.php.net/manual/en/intro.sqlsrv.php[ext/sqlsrv driver] + +|SQLite +|The https://www.php.net/manual/en/ref.pdo-sqlite.php[Pdo_Sqlite PDO extension] +|=== + +It creates an abstraction layer for the PHP extensions in the `Driver` sub-namespace of `Laminas\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. + +=== \Laminas\Db\Adapter\Platform\AbstractPlatform + +The Platform abstraction layer is based on AbstractPlatform. +It’s from this class that a vendor-specific abstraction is created. diff --git a/docs/book/metadata.md b/docs/modules/ROOT/pages/metadata.adoc similarity index 90% rename from docs/book/metadata.md rename to docs/modules/ROOT/pages/metadata.adoc index b997acb81..433091ebd 100644 --- a/docs/book/metadata.md +++ b/docs/modules/ROOT/pages/metadata.adoc @@ -1,11 +1,10 @@ -# RDBMS Metadata += RDBMS Metadata -`Laminas\Db\Metadata` is as sub-component of laminas-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: +`Laminas\Db\Metadata` is as sub-component of laminas-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 +[,php] +---- namespace Laminas\Db\Metadata; interface MetadataInterface @@ -32,21 +31,20 @@ interface MetadataInterface public function getTriggers(string $schema = null) : Object\TriggerObject[]; public function getTrigger(string $triggerName, string $schema = null) : Object\TriggerObject; } -``` +---- -## Basic Usage +== Basic Usage Usage of `Laminas\Db\Metadata` involves: -- Constructing a `Laminas\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. +* Constructing a `Laminas\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. -The `Metadata::get*Names()` methods will return arrays of strings, while the -other methods will return value objects specific to the type queried. +The `Metadata::get*Names()` methods will return arrays of strings, while the other methods will return value objects specific to the type queried. -```php +[,php] +---- $metadata = new Laminas\Db\Metadata\Metadata($adapter); // get the table names @@ -90,16 +88,17 @@ foreach ($tableNames as $tableName) { echo '----' . PHP_EOL; } -``` +---- -## Metadata 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: +Metadata returns value objects that provide an interface to help developers better explore the metadata. +Below is the API for the various value objects: -### TableObject +=== TableObject -```php +[,php] +---- class Laminas\Db\Metadata\Object\TableObject { public function __construct($name); @@ -110,11 +109,12 @@ class Laminas\Db\Metadata\Object\TableObject public function setName($name); public function getName(); } -``` +---- -### ColumnObject +=== ColumnObject -```php +[,php] +---- class Laminas\Db\Metadata\Object\ColumnObject { public function __construct($name, $tableName, $schemaName = null); @@ -149,11 +149,12 @@ class Laminas\Db\Metadata\Object\ColumnObject public function getErrata($errataName); public function setErrata($errataName, $errataValue); } -``` +---- -### ConstraintObject +=== ConstraintObject -```php +[,php] +---- class Laminas\Db\Metadata\Object\ConstraintObject { public function __construct($name, $tableName, $schemaName = null); @@ -188,11 +189,12 @@ class Laminas\Db\Metadata\Object\ConstraintObject public function isCheck(); } -``` +---- -### TriggerObject +=== TriggerObject -```php +[,php] +---- class Laminas\Db\Metadata\Object\TriggerObject { public function getName(); @@ -226,4 +228,4 @@ class Laminas\Db\Metadata\Object\TriggerObject public function getCreated(); public function setCreated($created); } -``` +---- diff --git a/docs/book/result-set.md b/docs/modules/ROOT/pages/result-set.adoc similarity index 56% rename from docs/book/result-set.md rename to docs/modules/ROOT/pages/result-set.adoc index 96f36b225..61ed7005d 100644 --- a/docs/book/result-set.md +++ b/docs/modules/ROOT/pages/result-set.adoc @@ -1,18 +1,14 @@ -# Result Sets += Result Sets -`Laminas\Db\ResultSet` is a sub-component of laminas-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 -`Laminas\Db\Adapter\Driver\ResultInterface` instances. +`Laminas\Db\ResultSet` is a sub-component of laminas-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 `Laminas\Db\Adapter\Driver\ResultInterface` instances. -Result sets must implement the `Laminas\Db\ResultSet\ResultSetInterface`, and all -sub-components of laminas-db that return a result set as part of their API will -assume an instance of a `ResultSetInterface` should be returned. In most cases, -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: +Result sets must implement the `Laminas\Db\ResultSet\ResultSetInterface`, and all sub-components of laminas-db that return a result set as part of their API will assume an instance of a `ResultSetInterface` should be returned. +In most cases, 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 +[,php] +---- use Countable; use Traversable; @@ -21,20 +17,17 @@ interface ResultSetInterface extends Traversable, Countable public function initialize(mixed $dataSource) : void; public function getFieldCount() : int; } -``` +---- -## Quick start +== Quick start -`Laminas\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, `Laminas\Db\Adapter\Adapter` will use a prototypical -`Laminas\Db\ResultSet\ResultSet` object for iterating when using the -`Laminas\Db\Adapter\Adapter::query()` method. +`Laminas\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, `Laminas\Db\Adapter\Adapter` will use a prototypical `Laminas\Db\ResultSet\ResultSet` object for iterating when using the `Laminas\Db\Adapter\Adapter::query()` method. -The following is an example workflow similar to what one might find inside -`Laminas\Db\Adapter\Adapter::query()`: +The following is an example workflow similar to what one might find inside `Laminas\Db\Adapter\Adapter::query()`: -```php +[,php] +---- use Laminas\Db\Adapter\Driver\ResultInterface; use Laminas\Db\ResultSet\ResultSet; @@ -50,16 +43,15 @@ if ($result instanceof ResultInterface && $result->isQueryResult()) { echo $row->my_column . PHP_EOL; } } -``` +---- -## Laminas\\Db\\ResultSet\\ResultSet and Laminas\\Db\\ResultSet\\AbstractResultSet +== Laminas\Db\ResultSet\ResultSet and Laminas\Db\ResultSet\AbstractResultSet -For most purposes, either an instance of `Laminas\Db\ResultSet\ResultSet` or a -derivative of `Laminas\Db\ResultSet\AbstractResultSet` will be used. The -implementation of the `AbstractResultSet` offers the following core -functionality: +For most purposes, either an instance of `Laminas\Db\ResultSet\ResultSet` or a derivative of `Laminas\Db\ResultSet\AbstractResultSet` will be used. +The implementation of the `AbstractResultSet` offers the following core functionality: -```php +[,php] +---- namespace Laminas\Db\ResultSet; use Iterator; @@ -83,31 +75,25 @@ abstract class AbstractResultSet implements Iterator, ResultSetInterface /** get rows as array */ public function toArray() : array; } -``` +---- -## Laminas\\Db\\ResultSet\\HydratingResultSet +== Laminas\Db\ResultSet\HydratingResultSet -`Laminas\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. +`Laminas\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 -[laminas-hydrator](https://docs.laminas.dev/laminas-hydrator), which you will -need to install: +The `HydratingResultSet` depends on https://docs.laminas.dev/laminas-hydrator[laminas-hydrator], which you will need to install: -```bash +[,bash] +---- $ composer require laminas/laminas-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: +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 +[,php] +---- use Laminas\Db\Adapter\Driver\ResultInterface; use Laminas\Db\ResultSet\HydratingResultSet; use Laminas\Hydrator\Reflection as ReflectionHydrator; @@ -150,8 +136,6 @@ if ($result instanceof ResultInterface && $result->isQueryResult()) { echo $user->getFirstName() . ' ' . $user->getLastName() . PHP_EOL; } } -``` +---- -For more information, see the [laminas-hydrator](https://docs.laminas.dev/laminas-hydrator/) -documentation to get a better sense of the different strategies that can be -employed in order to populate a target object. +For more information, see the https://docs.laminas.dev/laminas-hydrator/[laminas-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/docs/book/row-gateway.md b/docs/modules/ROOT/pages/row-gateway.adoc similarity index 54% rename from docs/book/row-gateway.md rename to docs/modules/ROOT/pages/row-gateway.adoc index eca10035c..c5bdcb0ca 100644 --- a/docs/book/row-gateway.md +++ b/docs/modules/ROOT/pages/row-gateway.adoc @@ -1,35 +1,20 @@ -# Row Gateways += Row Data Gateway -`Laminas\Db\RowGateway` is a sub-component of laminas-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. +The Row Gateway component is a sub-component of laminas-db that implements the Row Data Gateway pattern described in the book http://www.martinfowler.com/books/eaa.html[Patterns of Enterprise Application Architecture]. +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 Laminas\Db\RowGateway; +== Quick start -interface RowGatewayInterface -{ - public function save(); - public function delete(); -} -``` - -## Quick start - -`RowGateway` is generally used in conjunction with objects that produce -`Laminas\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. +`RowGateway` is generally used in conjunction with objects that produce `Laminas\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 +[source,php] +---- use Laminas\Db\RowGateway\RowGateway; // Query the database: @@ -48,16 +33,15 @@ $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. +The workflow described above is greatly simplified when `RowGateway` is used in conjunction with the link:table-gateway.md#tablegateway-features[TableGateway RowGatewayFeature]. +In that paradigm, `select()` operations will produce a `ResultSet` that iterates `RowGateway` instances. As an example: -```php +[,php] +---- use Laminas\Db\TableGateway\Feature\RowGatewayFeature; use Laminas\Db\TableGateway\TableGateway; @@ -67,17 +51,15 @@ $results = $table->select(['id' => 2]); $artistRow = $results->current(); $artistRow->name = 'New Name'; $artistRow->save(); -``` +---- -## ActiveRecord Style Objects +== It supports 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: +essentially making them behave similarly to the http://www.martinfowler.com/eaaCatalog/activeRecord.html[ActiveRecord] pattern), pass a prototype object implementing the `RowGatewayInterface` to the `RowGatewayFeature` constructor instead of a primary key: -```php +[source,php] +---- use Laminas\Db\TableGateway\Feature\RowGatewayFeature; use Laminas\Db\TableGateway\TableGateway; use Laminas\Db\RowGateway\RowGatewayInterface; @@ -95,4 +77,4 @@ class Artist implements RowGatewayInterface } $table = new TableGateway('artist', $adapter, new RowGatewayFeature(new Artist($adapter))); -``` +---- diff --git a/docs/modules/ROOT/pages/sql.adoc b/docs/modules/ROOT/pages/sql.adoc new file mode 100644 index 000000000..6a2ae3b37 --- /dev/null +++ b/docs/modules/ROOT/pages/sql.adoc @@ -0,0 +1,5 @@ += SQL Abstraction + +`Laminas\Db\Sql` is a SQL abstraction layer for building platform-specific SQL queries via an object-oriented API. +The end result of a `Laminas\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, `Laminas\Db\Sql` objects require a `Laminas\Db\Adapter\Adapter` object in order to produce the desired results. diff --git a/docs/modules/ROOT/pages/sql/sql-ddl.adoc b/docs/modules/ROOT/pages/sql/sql-ddl.adoc new file mode 100644 index 000000000..772ae4805 --- /dev/null +++ b/docs/modules/ROOT/pages/sql/sql-ddl.adoc @@ -0,0 +1,243 @@ += DDL (Data Definition Language) Abstraction + +`Laminas\Db\Sql\Ddl` is a sub-component of `Laminas\Db\Sql` allowing creation of DDL (Data Definition Language) SQL statements. +When combined with a platform specific `Laminas\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 + +== Creating Tables + +Like `Laminas\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 Laminas\Db\Sql\Ddl; +use Laminas\Db\Sql\TableIdentifier; + +$table = new Ddl\CreateTable(); + +// With a table name: +$table = new Ddl\CreateTable('bar'); + +// With a schema name "foo": +$table = new Ddl\CreateTable(new TableIdentifier('bar', 'foo')); + +// Optionally, as a temporary table: +$table = new Ddl\CreateTable('bar', true); +---- + +You can also set the table after instantiation: + +[,php] +---- +$table->setTable('bar'); +---- + +Currently, columns are added by creating a column object (described in the <>): + +[,php] +---- +use Laminas\Db\Sql\Ddl\Column; + +$table->addColumn(new Column\Integer('id')); +$table->addColumn(new Column\Varchar('name', 255)); +---- + +Beyond adding columns to a table, you may also add constraints: + +[,php] +---- +use Laminas\Db\Sql\Ddl\Constraint; + +$table->addConstraint(new Constraint\PrimaryKey('id')); +$table->addConstraint( + new Constraint\UniqueKey(['name', 'foo'], 'my_unique_key') +); +---- + +You can also use the `AUTO_INCREMENT` attribute for MySQL: + +[,php] +---- +use Laminas\Db\Sql\Ddl\Column; + +$column = new Column\Integer('id'); +$column->setOption('AUTO_INCREMENT', true); +---- + +== Altering Tables + +Similar to `CreateTable`, you may also use `AlterTable` instances: + +[,php] +---- +use Laminas\Db\Sql\Ddl; +use Laminas\Db\Sql\TableIdentifier; + +$table = new Ddl\AlterTable(); + +// With a table name: +$table = new Ddl\AlterTable('bar'); + +// With a schema name "foo": +$table = new Ddl\AlterTable(new TableIdentifier('bar', 'foo')); + +// 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 have the ability to _alter_ existing columns: + +[,php] +---- +use Laminas\Db\Sql\Ddl\Column; + +$table->changeColumn('name', Column\Varchar('new_name', 50)); +---- + +You may also _drop_ existing columns or constraints: + +[,php] +---- +$table->dropColumn('foo'); +$table->dropConstraint('my_index'); +---- + +== Dropping Tables + +To drop a table, create a `DropTable` instance: + +[,php] +---- +use Laminas\Db\Sql\Ddl; +use Laminas\Db\Sql\TableIdentifier; + +// With a table name: +$drop = new Ddl\DropTable('bar'); + +// With a schema name "foo": +$drop = new Ddl\DropTable(new TableIdentifier('bar', 'foo')); +---- + +== Executing DDL Statements + +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: + +[,php] +---- +use Laminas\Db\Sql\Sql; + +// Existence of $adapter is assumed. +$sql = new Sql($adapter); + +$adapter->query( + $sql->buildSqlString($ddl), + $adapter::QUERY_MODE_EXECUTE +); +---- + +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 `Laminas\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 `Laminas\Db\Sql\Ddl\Column` namespace. +Data types must implement `Laminas\Db\Sql\Ddl\Column\ColumnInterface`. + +In alphabetical order: + +|=== +| Type | Arguments For Construction + +| BigInteger +| `$name`, `$nullable = false`, `$default = null`, `array $options = array()` + +| Binary +| `$name`, `$length`, `nullable = false`, `$default = null`, `array $options = array()` + +| Blob +| `$name`, `$length`, `nullable = false`, `$default = null`, `array $options = array()` + +| Boolean +| `$name` + +| Char +| `$name`, `length` + +| Column (generic) +| `$name = null` + +| Date +| `$name` + +| DateTime +| `$name` + +| Decimal +| `$name`, `$precision`, `$scale = null` + +| Float +| `$name`, `$digits`, `$decimal` (Note: this class is deprecated as of 2.4.0; +use Floating instead) + +| Floating +| `$name`, `$digits`, `$decimal` + +| Integer +| `$name`, `$nullable = false`, `default = null`, `array $options = array()` + +| Text +| `$name`, `$length`, `nullable = false`, `$default = null`, `array $options = array()` + +| Time +| `$name` + +| Timestamp +| `$name` + +| Varbinary +| `$name`, `$length` + +| Varchar +| `$name`, `$length` +|=== + +Each of the above types can be utilized in any place that accepts a `Column\ColumnInterface` instance. +Currently, this is primarily in `CreateTable::addColumn()` and ``AlterTable``'s `addColumn()` and `changeColumn()` methods. + +== Currently Supported Constraint Types + +These types exist in the `Laminas\Db\Sql\Ddl\Constraint` namespace. +Data types must implement `Laminas\Db\Sql\Ddl\Constraint\ConstraintInterface`. + +In alphabetical order: + +|=== +| Type | Arguments For Construction + +| Check +| `$expression`, `$name` + +| ForeignKey +| `$name`, `$column`, `$referenceTable`, `$referenceColumn`, `$onDeleteRule = null`, `$onUpdateRule = null` + +| 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()`. diff --git a/docs/modules/ROOT/pages/sql/sql-dml.adoc b/docs/modules/ROOT/pages/sql/sql-dml.adoc new file mode 100644 index 000000000..2a8dd5d32 --- /dev/null +++ b/docs/modules/ROOT/pages/sql/sql-dml.adoc @@ -0,0 +1,544 @@ += DML (Data Manipulation Language) Abstraction +:sql-select: https://en.wikipedia.org/wiki/Select_(SQL) +:select-table-name: users +:select-table-alias: u +:concat_ws-function: https://www.geeksforgeeks.org/postgresql/postgresql-concat_ws-function/ + +There are four primary tasks associated with interacting with a database defined by DML: + +* xref:_select_queries[Selecting] +* xref:_insert_queries[Inserting] +* xref:_update_queries[Updating] +* xref:_delete_queries[Deleting] + +As such, there are four primary classes that developers can use to build queries in the {project-name} namespace: `Select`, `Insert`, `Update`, and `Delete`. + +Since these four tasks are so closely related and generally used together within the same application, the `Laminas\Db\Sql\Sql` class helps you create them and produce four accompanying objects that directly model the four respective operations: + +- `\Laminas\Db\Sql\Select` +- `\Laminas\Db\Sql\Insert` +- `\Laminas\Db\Sql\Update` +- `\Laminas\Db\Sql\Delete` + +You can see an example of how to instantiate them in the follow example: + +[source,php] +---- +use Laminas\Db\Sql\Sql; + +/** @param \Laminas\Db\Adapter\Adapter $adapter */ +$sql = new Sql($adapter); + +// Returns a Laminas\Db\Sql\Select instance +$select = $sql->select(); + +// Returns a Laminas\Db\Sql\Insert instance +$insert = $sql->insert(); + +// Returns a Laminas\Db\Sql\Update instance +$update = $sql->update(); + +// Returns a Laminas\Db\Sql\Delete instance +$delete = $sql->delete(); +---- + +As a developer, you can now interact with these objects, as described in the sections below, to customize each query. +Then, once they have been populated with values, they are ready to either be prepared or executed, as you can see in the two examples below. + +// Need to figure out the difference between calling execute and query. +== Creating and executing a prepared statement + +[source,php,subs="attributes+"] +---- +use Laminas\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); +$select->from('{select-table-name}'); +$select->where(['id' => 1]); + +$statement = $sql->prepareStatementForSqlObject($select); +$results = $statement->execute(); +---- + +To execute (using a Select object) + +[source,php,subs="attributes+"] +---- +use Laminas\Db\Sql\Sql; + +$sql = new Sql($adapter); +$select = $sql->select(); +$select->from('{select-table-name}'); +$select->where(['id' => 1]); + +$selectString = $sql->buildSqlString($select); +$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE); +---- + +== Binding objects to tables + +When instantiating `Sql` objects, you can choose to bind it to a particular table, as in the following example. +When you do, the `Select`, `Insert`, `Update`, or `Delete` instance retrieved from it it will also be bound to that table. + +[source,php,subs="attributes+"] +---- +use Laminas\Db\Sql\Sql; + +$sql = new Sql($adapter, '{select-table-name}'); +$select = $sql->select(); +$select->where(['id' => 1]); // $select already has from('{select-table-name}') applied +---- + +== Select Queries + +The `Laminas\Db\Sql\Select` class allows you to build platform-specific {sql-select}[SQL select queries]. +These can be instantiated using a `Sql` object, as seen earlier, or directly, as in the examples below. + +[source,php,subs="attributes+"] +---- +use Laminas\Db\Sql\Select; + +// This Select object is not bound to a table +$select = new Select(); + +// This Select object is bound to the "{select-table-name}" table +$select = new Select('{select-table-name}'); +---- + +[IMPORTANT] +==== +If a table name is provided to the `Select` object's constructor, the table name cannot be changed. +==== + +=== Binding objects to tables + +If a `Select` object is not bound to a table at initialization, there a number of ways to do so once constructed. + +[source,php,subs="attributes+"] +---- +// Set the table name with the "from" method +$select->from('{select-table-name}'); + +// Set the table name with the "from" method along with a table alias +$select->from(['f' => '{select-table-name}']); + +// Set the table name using a Sql\TableIdentifier: +$select->from(['f' => new TableIdentifier('{select-table-name}')]); +---- + +The above three examples produce the following SQL queries: + +[source,sql,subs="attributes+"] +---- +SELECT * FROM "{select-table-name}"; + +SELECT "{select-table-alias}".* FROM "{select-table-name}" AS "{select-table-alias}"; + +SELECT "{select-table-alias}".* FROM "{select-table-name}" AS "{select-table-alias}"; +---- + +=== Specifying the columns returned + +It's unlikely that you want to return every column in the selected table, which is the default. +So, to specify the list of columns, provide them as an array to `Select`'s `column()` method. + +[source,php,subs="attributes+"] +---- +$select->columns([ + 'foo', + 'bar' +]); +---- + +However, you may also want to alias one or more of the columns. +To do that, provide an associative array instead. +Each element's key will be the alias and its value will be the column name to return. + +[source,php,subs="attributes+"] +---- +$select->columns([ + 'foo' => 'bar', + 'baz' => 'bax' +]); +---- + +Assuming that we're querying the "{select-table-name}" table, as before, the above object will produce the following SQL: + +[source,sql,subs="attributes+"] +---- +SELECT 'bar' AS 'foo', 'bax' AS 'baz' +FROM {select-table-name} +---- + +==== Calling database functions + +Sometimes, a column needs to be the result of a native database function (or series of functions). +In these cases, you can't pass the function call as a string value in the array to the `columns()` method. +Rather, you need to use a `\Laminas\Db\Sql\Expression` to specify the column value. + +For example, let's say that you need to call {concat_ws-function}[the CONCAT_WS function], which adds two or more expressions together with a separator. +Here's how you'd do so: + +[source,php,subs="attributes+"] +---- +use \Laminas\Db\Sql\Expression; + +$select->columns([ + 'foo' => new \Laminas\Db\Sql\Expression("CONCAT_WS('/', 'bar', 'bax')") +]); +---- + +Here's the SQL that would be generated: + +[source,sql,subs="attributes+"] +---- +use \Laminas\Db\Sql\Expression; + +SELECT CONCAT_WS('/', 'bar', 'bax') AS 'foo' +FROM {select-table-name} +---- + +=== Joining tables to one another + +[source,php,subs="attributes+"] +---- +$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, full outer, left, right also represented by constants in the API +); + +$select + ->from(['u' => '{select-table-name}']) // base table + ->join( + ['b' => 'bar'], // join table with alias + 'f.foo_id = b.foo_id' // join expression + ); +---- + +=== Building where and having clauses + +`Laminas\Db\Sql\Select` provides bit of flexibility as it regards to what kind of parameters are acceptable when calling `where()` or `having()`. + +If you provide a `Laminas\Db\Sql\Where` instance to `where()` or a `Laminas\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: + +[source,php,subs="attributes+"] +---- +$select->where(function (Where $where) { + $where->like('username', 'ralph%'); +}); +---- + +If you provide a _string_, this string will be used to create a `Laminas\Db\Sql\Predicate\Expression` instance, and its contents will be applied as-is, with no quoting: + +[source,php,subs="attributes+"] +---- +// SELECT "foo".* FROM "foo" WHERE x = 4 +$select->from('foo')->where('x = 4'); +---- + +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: + +[source,php,subs="attributes+"] +---- +// SELECT "foo".* FROM "foo" WHERE x = 4 AND y = z +$select->from('foo')->where(['x = 4', '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: + +[source,php,subs="attributes+"] +---- +// SELECT "foo".* FROM "foo" WHERE "c0" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL +$select->from('foo')->where([ + 'c0' => null, + 'c1' => [1, 2, 3], + new \Laminas\Db\Sql\Predicate\IsNotNull('c2'), +]); +---- + +As another example of complex queries with nested conditions e.g. + +[source,sql,subs="attributes+"] +---- +SELECT * WHERE (column0 is null or column1 = 2) AND (column2 = 3) +---- + +you need to use the `nest()` and `unnest()` methods, as follows: + +[source,php,subs="attributes+"] +---- +$select->where->nest() // bracket opened + ->isNull('column0') + ->or + ->equalTo('column0', '2') + ->unnest(); // bracket closed + ->equalTo('column1', '3'); +---- + +=== Setting the sort order + +[source,php,subs="attributes+"] +---- +$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 +---- + +=== Setting query limits and offsets + +[source,php,subs="attributes+"] +---- +$select = new Select; +$select->limit(4); // always takes an integer/numeric +$select->offset(9); // similarly takes an integer/numeric +---- + +== Insert Queries + +The Insert API: + +[source,php,subs="attributes+"] +---- +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() + +[source,php,subs="attributes+"] +---- +$insert->columns(['foo', 'bar']); // set the valid columns +---- + +=== values() + +The default behavior of values is to set the values. +Successive calls will not preserve values from previous calls. + +[source,php,subs="attributes+"] +---- +$insert->values([ + 'col_0' => 'value1', + 'col_1' => 'value2', +]); +---- + +To merge values with previous calls, provide the appropriate flag: `Laminas\Db\Sql\Insert::VALUES_MERGE` + +[source,php,subs="attributes+"] +---- +$insert->values(['col_1' => 'value2'], $insert::VALUES_MERGE); +---- + +== Update Queries + +=== set() + +[source,php,subs="attributes+"] +---- +$update->set(['foo' => 'bar', 'baz' => 'bax']); +---- + +=== where() + +See the <>. + +== Delete Queries + +=== where() + +See the <>. + +== Building sophisticated where and having clauses + +[NOTE] +==== +In the following, we will talk about `Where`; note, however, that `Having` utilizes the same API. +==== + +Effectively, the `Where` and `Having` classes extend from the same base object, `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_. +Then, 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 (parameterized) or executed. +In parameterization, the parameters are 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 `Laminas\Db\Sql\ExpressionInterface` interface. + +[NOTE] +.Literals +==== +In Laminas 1.1, an actual `Literal` type was added. +`Laminas\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`, `Laminas\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`. +Each method in the API will produce a corresponding `Predicate` object of a similarly named type, as described below. + +=== equalTo(), lessThan(), greaterThan(), lessThanOrEqualTo(), greaterThanOrEqualTo() + +[source,php,subs="attributes+"] +---- +$where->equalTo('id', 4); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Operator($left, Operator::OPERATOR_EQUAL_TO, $right, $leftType, $rightType) +); +---- + +=== like($identifier, $like), notLike($identifier, $notLike) + +[source,php,subs="attributes+"] +---- +$where->like($identifier, $like): + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Like($identifier, $like) +); +---- + +=== literal($literal) + +[source,php,subs="attributes+"] +---- +$where->literal($literal); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Literal($literal) +); +---- + +=== expression($expression, $parameter) + +[source,php,subs="attributes+"] +---- +$where->expression($expression, $parameter); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Expression($expression, $parameter) +); +---- + +Expression parameters can be supplied either as a single scalar, an array of values, or as an array of value/types for more granular escaping. + +[source,php,subs="attributes+"] +---- +$select + ->from('foo') + ->columns([ + new Expression( + '(COUNT(?) + ?) AS ?', + [ + ['some_column' => ExpressionInterface::TYPE_IDENTIFIER], + [4 => ExpressionInterface::TYPE_VALUE], + ['bar' => ExpressionInterface::TYPE_IDENTIFIER], + ], + ), + ]); + +// Produces SELECT (COUNT("some_column") + '4') AS "bar" FROM "foo" +---- + +=== isNull($identifier) + +[source,php,subs="attributes+"] +---- +$where->isNull($identifier); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\IsNull($identifier) +); +---- + +=== isNotNull($identifier) + +[source,php,subs="attributes+"] +---- +$where->isNotNull($identifier); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\IsNotNull($identifier) +); +---- + +=== in($identifier, $valueSet), notIn($identifier, $valueSet) + +[source,php,subs="attributes+"] +---- +$where->in($identifier, $valueSet); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\In($identifier, $valueSet) +); +---- + +=== between($identifier, $minValue, $maxValue), notBetween($identifier, $minValue, $maxValue) + +[source,php,subs="attributes+"] +---- +$where->between($identifier, $minValue, $maxValue); + +// The above is equivalent to: +$where->addPredicate( + new Predicate\Between($identifier, $minValue, $maxValue) +); +---- \ No newline at end of file diff --git a/docs/modules/ROOT/pages/table-gateway.adoc b/docs/modules/ROOT/pages/table-gateway.adoc new file mode 100644 index 000000000..2b9f6aa87 --- /dev/null +++ b/docs/modules/ROOT/pages/table-gateway.adoc @@ -0,0 +1,257 @@ += Table Data Gateways + +The Table Gateway sub-component provides an object-oriented representation of a database table; +its methods mirror the most common table operations. +In code, the interface resembles: + +[,php] +---- +namespace Laminas\Db\TableGateway; + +use Laminas\Db\ResultSet\ResultSetInterface; +use Laminas\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 `Laminas\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 `Laminas\Db\TableGateway\TableGateway`, which defines the following API: + +[,php] +---- +namespace Laminas\Db\TableGateway; + +use Laminas\Db\Adapter\AdapterInterface; +use Laminas\Db\ResultSet\ResultSet; +use Laminas\Db\ResultSet\ResultSetInterface; +use Laminas\Db\Sql; +use Laminas\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 Laminas\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 `Laminas\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 Laminas\Db\TableGateway\TableGateway; +use Laminas\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 through 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 laminas-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 Laminas\Db\TableGateway\AbstractTableGateway; + use Laminas\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 + Laminas\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 https://github.com/laminas/laminas-eventmanager[laminas-eventmanager] `EventManager` instance within your `TableGateway` instance, and attach listeners to the various events of its lifecycle. +See the <> for more information on available events and the parameters they compose. ++ +[,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(); +---- + +== TableGateway LifeCycle Events + +When the `EventFeature` is enabled on the `TableGateway` instance, you may attach to any of the following events, which provide access to the parameters listed. + +* `preInitialize` (no parameters) +* `postInitialize` (no parameters) +* `preSelect`, with the following parameters: + ** `select`, with type `Laminas\Db\Sql\Select` +* `postSelect`, with the following parameters: + ** `statement`, with type `Laminas\Db\Adapter\Driver\StatementInterface` + ** `result`, with type `Laminas\Db\Adapter\Driver\ResultInterface` + ** `resultSet`, with type `Laminas\Db\ResultSet\ResultSetInterface` +* `preInsert`, with the following parameters: + ** `insert`, with type `Laminas\Db\Sql\Insert` +* `postInsert`, with the following parameters: + ** `statement` with type `Laminas\Db\Adapter\Driver\StatementInterface` + ** `result` with type `Laminas\Db\Adapter\Driver\ResultInterface` +* `preUpdate`, with the following parameters: + ** `update`, with type `Laminas\Db\Sql\Update` +* `postUpdate`, with the following parameters: + ** `statement`, with type `Laminas\Db\Adapter\Driver\StatementInterface` + ** `result`, with type `Laminas\Db\Adapter\Driver\ResultInterface` +* `preDelete`, with the following parameters: + ** `delete`, with type `Laminas\Db\Sql\Delete` +* `postDelete`, with the following parameters: + ** `statement`, with type `Laminas\Db\Adapter\Driver\StatementInterface` + ** `result`, with type `Laminas\Db\Adapter\Driver\ResultInterface` + +Listeners receive a `Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent` instance as an argument. +Within the listener, you can retrieve a parameter by name from the event using the following syntax: + +[,php] +---- +$parameter = $event->getParam($paramName); +---- + +As an example, you might attach a listener on the `postInsert` event as follows: + +[,php] +---- +use Laminas\Db\Adapter\Driver\ResultInterface; +use Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent; +use Laminas\EventManager\EventManager; + +/** @var EventManager $eventManager */ +$eventManager->attach('postInsert', function (TableGatewayEvent $event) { + /** @var ResultInterface $result */ + $result = $event->getParam('result'); + $generatedId = $result->getGeneratedValue(); + + // do something with the generated identifier... +}); +---- diff --git a/docs/modules/ROOT/partials/.gitkeep b/docs/modules/ROOT/partials/.gitkeep new file mode 100644 index 000000000..e69de29bb