diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d240d02 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = true + +[*.{neon,neon.dist}] +indent_style = tab + +[**.{jshintrc,json,scss-lint,yml}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..ac84e89 --- /dev/null +++ b/.env.testing @@ -0,0 +1,57 @@ +# This file will be consumed by both the CI and the tests. +# Some environment variables might not apply to one but might apply to the other: modify with care. + +# What version of WordPress we want to install and test against. +# This has to be compatible with the `wp core download` command, see https://developer.wordpress.org/cli/commands/core/download/. +WP_VERSION=latest + +# This is where, in the context of the CI, we'll install and configure WordPress. +# See `.travis.yml` for more information. +WP_ROOT_FOLDER=/tmp/wordpress + +# The WordPress installation will be served from the Docker container. +# See `dev/docker/ci-compose.yml` for more information. +WP_URL=http://localhost:8080 +WP_DOMAIN=localhost:8080 + +# The credentials that will be used to access the site in acceptance tests +# in methods like `$I->loginAsAdmin();`. +WP_ADMIN_USERNAME=admin +WP_ADMIN_PASSWORD=password + +WP_DB_PORT=4306 + +# The databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TABLE_PREFIX=wp_ +WP_DB_HOST=127.0.0.1:4306 +WP_DB_NAME=wordpress +WP_DB_USER=root +WP_DB_PASSWORD= + +# The test databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TEST_DB_HOST=127.0.0.1:4306 +WP_TEST_DB_NAME=test +WP_TEST_DB_USER=root +WP_TEST_DB_PASSWORD= + +# We're using Selenium and Chrome for acceptance testing. +# In CI context we're starting a Docker container to handle that. +# See the `dev/docker/ci-compose.yml` file. +CHROMEDRIVER_HOST=localhost +CHROMEDRIVER_PORT=4444 + +# The URL of the WordPress installation from the point of view of the Chromedriver container. +# Why not just use `wordpress`? While Chrome will accept an `http://wordpress` address WordPress +# will not, we call the WordPress container with a seemingly looking legit URL and leverage the +# lines that, in the `wp-config.php` file, will make it so that WordPress will use as its home +# URL whatever URL we reach it with. +# See the `dev/docker/wp-config.php` template for more information. +WP_CHROMEDRIVER_URL="wp.test" + +# To run the tests let's force the background-processing lib to run in synchronous (single PHP thread) mode. +TRIBE_NO_ASYNC=1 + +# We're using Docker to run the tests. +USING_CONTAINERS=1 diff --git a/.env.testing.tric b/.env.testing.tric new file mode 100644 index 0000000..20de04b --- /dev/null +++ b/.env.testing.tric @@ -0,0 +1,57 @@ +# This file will be consumed by both the CI and the tests. +# Some environment variables might not apply to one but might apply to the other: modify with care. + +# What version of WordPress we want to install and test against. +# This has to be compatible with the `wp core download` command, see https://developer.wordpress.org/cli/commands/core/download/. +WP_VERSION=latest + +# This is where, in the context of the CI, we'll install and configure WordPress. +# See `.travis.yml` for more information. +WP_ROOT_FOLDER=/var/www/html + +# The WordPress installation will be served from the Docker container. +# See `dev/docker/ci-compose.yml` for more information. +WP_URL=http://wordpress.test +WP_DOMAIN=wordpress.test + +# The credentials that will be used to access the site in acceptance tests +# in methods like `$I->loginAsAdmin();`. +WP_ADMIN_USERNAME=admin +WP_ADMIN_PASSWORD=password + +WP_DB_PORT=3306 + +# The databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TABLE_PREFIX=wp_ +WP_DB_HOST=db +WP_DB_NAME=test +WP_DB_USER=root +WP_DB_PASSWORD=password + +# The test databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TEST_DB_HOST=db +WP_TEST_DB_NAME=test +WP_TEST_DB_USER=root +WP_TEST_DB_PASSWORD=password + +# We're using Selenium and Chrome for acceptance testing. +# In CI context we're starting a Docker container to handle that. +# See the `dev/docker/ci-compose.yml` file. +CHROMEDRIVER_HOST=chrome +CHROMEDRIVER_PORT=4444 + +# The URL of the WordPress installation from the point of view of the Chromedriver container. +# Why not just use `wordpress`? While Chrome will accept an `http://wordpress` address WordPress +# will not, we call the WordPress container with a seemingly looking legit URL and leverage the +# lines that, in the `wp-config.php` file, will make it so that WordPress will use as its home +# URL whatever URL we reach it with. +# See the `dev/docker/wp-config.php` template for more information. +WP_CHROMEDRIVER_URL=http://wordpress.test + +# To run the tests let's force the background-processing lib to run in synchronous (single PHP thread) mode. +TRIBE_NO_ASYNC=1 + +# We're using Docker to run the tests. +USING_CONTAINERS=1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..633e694 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text +.* export-ignore +composer.lock text -diff +phpstan.* export-ignore +phpunit.* export-ignore +tests export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17a68b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +files/ +repo/ +vendor/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5ff5ee2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Change Log + +All notable changes to this project will be documented in this file. This project adhere to the [Semantic Versioning](http://semver.org/) standard. + +## [unreleased] Unreleased + +## [1.0.0] 2022-08-17 + +### Added + +- Initial version +- Documentation +- Automated tests + +[1.0.0]: https://github.com/stellarwp/schema/releases/tag/1.0.0 diff --git a/README.md b/README.md index 66326ad..4f52070 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,856 @@ -# db -A WPDB wrapper and query builder library. +# Query Builder + +Query Builder helper class is used to write SQL queries + +- [DB](#db) + +- [Select statements](#select-statements) + +- [From Clause](#from-clause) + +- [Joins](#joins) + - [LEFT Join](#left-join) + - [RIGHT Join](#right-join) + - [INNER Join](#inner-join) + - [Join Raw](#join-raw) + - [Advanced Join Clauses](#advanced-join-clauses) + +- [Unions](#unions) + +- [Where Clauses](#where-clauses) + - [Where](#where-clauses) + - [Where IN](#where-in-clauses) + - [Where BETWEEN](#where-between-clauses) + - [Where LIKE](#where-like-clauses) + - [Where IS NULL](#where-is-null-clauses) + - [Where EXISTS](#where-exists-clauses) + - [Subquery Where Clauses](#subquery-where-clauses) + - [Nested Where Clauses](#nested-where-clauses) + +- [Ordering, Grouping, Limit & Offset](#ordering-grouping-limit--offset) + - [Ordering](#ordering) + - [Grouping](#grouping) + - [Limit & Offset](#limit--offset) + +- [Special methods for working with meta tables](#special-methods-for-working-with-meta-tables) + - [attachMeta](#attachmeta) + - [configureMetaTable](#configuremetatable) + +- [CRUD](#crud) + - [Insert](#insert) + - [Update](#update) + - [Delete](#delete) + - [Get](#get) + +- [Aggregate Functions](#aggregate-functions) + - [Count](#count) + - [Sum](#sum) + - [Avg](#avg) + - [Min](#min) + - [Max](#max) + +## DB + +`DB` class is a static decorator for the `$wpdb` class, but it has a few methods that are exceptions to that. +Methods `DB::table()` and `DB::raw()`. + +`DB::table()` is a static facade for the `QueryBuilder` class, and it accepts two string arguments, `$tableName` +and `$tableAlias`. + +Under the hood, `DB::table()` will create a new `QueryBuilder` instance, and it will use `QueryBuilder::from` method to set the table name. Calling `QueryBuilder::from` when using `DB::table` method will return an unexpected result. Basically, we are telling the `QueryBuilder` that we want to select data from two tables. + +### Important + +When using `DB::table(tableName)` method, the `tableName` is prefixed with `$wpdb->prefix`. To bypass that, you can +use `DB::raw` method which will tell `QueryBuilder` not to prefix the table name. + +```php +DB::table(DB::raw('posts')); +``` + +## Select statements + +#### Available methods - select / selectRaw / distinct + +By using the `QueryBuilder::select` method, you can specify a custom `SELECT` statement for the query. + +```php +DB::table('posts')->select('ID', 'post_title', 'post_date'); +``` + +Generated SQL + +```sql +SELECT ID, post_title, post_date FROM wp_posts +``` + +You can also specify the column alias by providing an array _[column, alias]_ to the `QueryBuilder::select` method. + +```php +DB::table('posts')->select( + ['ID', 'post_id'], + ['post_status', 'status'], + ['post_date', 'createdAt'] +); +``` + +Generated SQL: + +```sql +SELECT ID AS post_id, post_status AS status, post_date AS createdAt FROM wp_posts +``` + +The distinct method allows you to force the query to return distinct results: + +```php +DB::table('posts')->select('post_status')->distinct(); +``` + +You can also specify a custom `SELECT` statement with `QueryBuilder::selectRaw` method. This method accepts an optional array of +bindings as its second argument. + +```php +DB::table('posts') + ->select('ID') + ->selectRaw('(SELECT ID from wp_posts WHERE post_status = %s) AS subscriptionId', 'give_subscription'); +``` + +Generated SQL + +```sql +SELECT ID, (SELECT ID from wp_posts WHERE post_status = 'give_subscription') AS subscriptionId FROM wp_posts +``` + +By default, all columns will be selected from a database table. + +```php +DB::table('posts'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts +``` + +## From clause + +By using the `QueryBuilder::from()` method, you can specify a custom `FROM` clause for the query. + + +```php +$builder = new QueryBuilder(); +$builder->from('posts'); +``` + +Set multiple `FROM` clauses + +```php +$builder = new QueryBuilder(); +$builder->from('posts'); +$builder->from('postmeta'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts, wp_postmeta +``` + +### Important + +Table name is prefixed with `$wpdb->prefix`. To bypass that, you can +use `DB::raw` method which will tell `QueryBuilder` not to prefix the table name. + +```php +$builder = new QueryBuilder(); +$builder->from(DB::raw('posts')); +``` + +## Joins + +The Query Builder may also be used to add `JOIN` clauses to your queries. + +#### Available methods - leftJoin / rightJoin / innerJoin / joinRaw / join + +### LEFT Join + +`LEFT JOIN` clause. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->leftJoin('give_donationmeta', 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable LEFT JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### RIGHT Join + +`RIGHT JOIN` clause. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->rightJoin('give_donationmeta', 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable RIGHT JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### INNER Join + +`INNER JOIN` clause. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->innerJoin('give_donationmeta', 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable INNER JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### Join Raw + +Insert a raw expression into query. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->joinRaw('LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### Advanced Join Clauses + +**The closure will receive a `Give\Framework\QueryBuilder\JoinQueryBuilder` instance** + +```php +DB::table('posts') + ->select('donationsTable.*', 'metaTable.*') + ->join(function (JoinQueryBuilder $builder) { + $builder + ->leftJoin('give_donationmeta', 'metaTable') + ->on('donationsTable.ID', 'metaTable.donation_id') + ->andOn('metaTable.meta_key', 'some_key', $qoute = true); + }); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts LEFT JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id AND metaTable.meta_key = 'some_key' +``` + +## Unions + +The Query Builder also provides a convenient method to "union" two or more queries together. + +#### Available methods - union / unionAll + +### Union + +```php +$donations = DB::table('give_donations')->where('author_id', 10); + +DB::table('give_subscriptions') + ->select('ID') + ->where('ID', 100, '>') + ->union($donations); +``` + +Generated SQL: + +```sql +SELECT ID FROM wp_give_subscriptions WHERE ID > '100' UNION SELECT * FROM wp_give_donations WHERE author_id = '10' +``` + +## Where Clauses + +You may use the Query Builder's `where` method to add `WHERE` clauses to the query. + +### Where + +#### Available methods - where / orWhere + +```php +DB::table('posts')->where('ID', 5); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID = '5' +``` + +Using `where` multiple times. + +```php +DB::table('posts') + ->where('ID', 5) + ->where('post_author', 10); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID = '5' AND post_author = '10' +``` + +### Where IN Clauses + +#### Available methods - whereIn / orWhereIn / whereNotIn / orWhereNotIn + +The `QueryBuilder::whereIn` method verifies that a given column's value is contained within the given array: + +```php +DB::table('posts')->whereIn('ID', [1, 2, 3]); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID IN ('1','2','3') +``` + +You can also pass a closure as the second argument which will generate a subquery. + +**The closure will receive a `Give\Framework\QueryBuilder\QueryBuilder` instance** + +```php +DB::table('posts') + ->whereIn('ID', function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from('give_donationmeta') + ->where('meta_key', 'donation_id'); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID IN (SELECT meta_value AS donation_id FROM wp_give_donationmeta WHERE meta_key = 'donation_id') +``` + +### Where BETWEEN Clauses + +The `QueryBuilder::whereBetween` method verifies that a column's value is between two values: + +#### Available methods - whereBetween / orWhereBetween / whereNotBetween / orWhereNotBetween + +```php +DB::table('posts')->whereBetween('ID', 0, 100); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID BETWEEN '0' AND '100' +``` + +### Where LIKE Clauses + +The `QueryBuilder::whereLike` method searches for a specified pattern in a column. + +#### Available methods - whereLike / orWhereLike / whereNotLike / orWhereNotLike + +```php +DB::table('posts')->whereLike('post_title', 'Donation'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_title LIKE '%Donation%' +``` + +### Where IS NULL Clauses + +The `QueryBuilder::whereIsNull` method verifies that a column's value is `NULL` + +#### Available methods - whereIsNull / orWhereIsNull / whereIsNotNull / orWhereIsNotNull + +```php +DB::table('posts')->whereIsNull('post_author'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_author IS NULL +``` + +### Where EXISTS Clauses + +The `QueryBuilder::whereExists` method allows you to write `WHERE EXISTS` SQL clauses. The `QueryBuilder::whereExists` method accepts a closure which will receive a `QueryBuilder` instance. + +#### Available methods - whereExists / whereNotExists + +```php +DB::table('give_donationmeta') + ->whereExists(function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->where('meta_key', 'donation_id'); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_give_donationmeta WHERE EXISTS (SELECT meta_value AS donation_id WHERE meta_key = 'donation_id') +``` + +### Subquery Where Clauses + +Sometimes you may need to construct a `WHERE` clause that compares the results of a subquery to a given value. + +```php +DB::table('posts') + ->where('post_author', function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'author_id']) + ->from('postmeta') + ->where('meta_key', 'donation_id') + ->where('meta_value', 10); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_author = (SELECT meta_value AS author_id FROM wp_postmeta WHERE meta_key = 'donation_id' AND meta_value = '10') +``` + +### Nested Where Clauses + +Sometimes you may need to construct a `WHERE` clause that has nested WHERE clauses. + +**The closure will receive a `Give\Framework\QueryBuilder\WhereQueryBuilder` instance** + +```php +DB::table('posts') + ->where('post_author', 10) + ->where(function (WhereQueryBuilder $builder) { + $builder + ->where('post_status', 'published') + ->orWhere('post_status', 'donation') + ->whereIn('ID', [1, 2, 3]); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_author = '10' AND ( post_status = 'published' OR post_status = 'donation' AND ID IN ('1','2','3')) +``` + +## Ordering, Grouping, Limit & Offset + +### Ordering + +The `QueryBuilder::orderBy` method allows you to sort the results of the query by a given column. + +```php +DB::table('posts')->orderBy('ID'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts ORDER BY ID ASC +``` + +Sorting result by multiple columns + +```php +DB::table('posts') + ->orderBy('ID') + ->orderBy('post_date', 'DESC'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts ORDER BY ID ASC, post_date DESC +``` + +### Grouping + +The `QueryBuilder::groupBy` and `QueryBuilder::having*` methods are used to group the query results. + +#### Available methods - groupBy / having / orHaving / havingCount / orHavingCount / havingMin / orHavingMin / havingMax / orHavingMax / havingAvg / orHavingAvg / havingSum / orHavingSum / havingRaw + +```php +DB::table('posts') + ->groupBy('id') + ->having('id', '>', 10); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE GROUP BY id HAVING 'id' > '10' +``` + +### Limit & Offset + +Limit the number of results returned from the query. + +#### Available methods - limit / offset + +```php +DB::table('posts') + ->limit(10) + ->offset(20); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts LIMIT 10 OFFSET 20 +``` + +## Special methods for working with meta tables + +Query Builder has a few special methods for abstracting the work with meta tables. + + +### attachMeta + +`attachMeta` is used to include meta table _meta_key_ column values as columns in the `SELECT` statement. + +Under the hood `QueryBuilder::attachMeta` will add join clause for each defined `meta_key` column. And each column will be +added in select statement as well, which means the meta columns will be returned in query result. Aliasing meta columns +is recommended when using `QueryBuilder::attachMeta` method. + +```php +DB::table('posts') + ->select( + ['ID', 'id'], + ['post_date', 'createdAt'], + ['post_modified', 'updatedAt'], + ['post_status', 'status'], + ['post_parent', 'parentId'] + ) + ->attachMeta('give_donationmeta', 'ID', 'donation_id', + ['_give_payment_total', 'amount'], + ['_give_payment_currency', 'paymentCurrency'], + ['_give_payment_gateway', 'paymentGateway'], + ['_give_payment_donor_id', 'donorId'], + ['_give_donor_billing_first_name', 'firstName'], + ['_give_donor_billing_last_name', 'lastName'], + ['_give_payment_donor_email', 'donorEmail'], + ['subscription_id', 'subscriptionId'] + ) + ->leftJoin('give_donationmeta', 'ID', 'donationMeta.donation_id', 'donationMeta') + ->where('post_type', 'give_payment') + ->where('post_status', 'give_subscription') + ->where('donationMeta.meta_key', 'subscription_id') + ->where('donationMeta.meta_value', 1) + ->orderBy('post_date', 'DESC'); +``` + +Generated SQL: + +```sql +SELECT ID AS id, + post_date AS createdAt, + post_modified AS updatedAt, + post_status AS status, + post_parent AS parentId, + give_donationmeta_attach_meta_0.meta_value AS amount, + give_donationmeta_attach_meta_1.meta_value AS paymentCurrency, + give_donationmeta_attach_meta_2.meta_value AS paymentGateway, + give_donationmeta_attach_meta_3.meta_value AS donorId, + give_donationmeta_attach_meta_4.meta_value AS firstName, + give_donationmeta_attach_meta_5.meta_value AS lastName, + give_donationmeta_attach_meta_6.meta_value AS donorEmail, + give_donationmeta_attach_meta_7.meta_value AS subscriptionId +FROM wp_posts + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_0 + ON ID = give_donationmeta_attach_meta_0.donation_id AND + give_donationmeta_attach_meta_0.meta_key = '_give_payment_total' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_1 + ON ID = give_donationmeta_attach_meta_1.donation_id AND + give_donationmeta_attach_meta_1.meta_key = '_give_payment_currency' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_2 + ON ID = give_donationmeta_attach_meta_2.donation_id AND + give_donationmeta_attach_meta_2.meta_key = '_give_payment_gateway' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_3 + ON ID = give_donationmeta_attach_meta_3.donation_id AND + give_donationmeta_attach_meta_3.meta_key = '_give_payment_donor_id' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_4 + ON ID = give_donationmeta_attach_meta_4.donation_id AND + give_donationmeta_attach_meta_4.meta_key = '_give_donor_billing_first_name' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_5 + ON ID = give_donationmeta_attach_meta_5.donation_id AND + give_donationmeta_attach_meta_5.meta_key = '_give_donor_billing_last_name' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_6 + ON ID = give_donationmeta_attach_meta_6.donation_id AND + give_donationmeta_attach_meta_6.meta_key = '_give_payment_donor_email' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_7 + ON ID = give_donationmeta_attach_meta_7.donation_id AND + give_donationmeta_attach_meta_7.meta_key = 'subscription_id' + LEFT JOIN wp_give_donationmeta donationMeta ON ID = donationMeta.donation_id +WHERE post_type = 'give_payment' + AND post_status = 'give_subscription' + AND donationMeta.meta_key = 'subscription_id' + AND donationMeta.meta_value = '1' +ORDER BY post_date DESC +``` + +Returned result: + +``` +stdClass Object +( + [id] => 93 + [createdAt] => 2022-02-21 00:00:00 + [updatedAt] => 2022-01-21 11:08:09 + [status] => give_subscription + [parentId] => 92 + [amount] => 100.000000 + [paymentCurrency] => USD + [paymentGateway] => manual + [donorId] => 1 + [firstName] => Ante + [lastName] => Laca + [donorEmail] => dev-email@flywheel.local + [subscriptionId] => 1 +) +``` + +#### Fetch multiple instances of the same meta key + +Sometimes we need to fetch multiple instances of the same meta key. This is possible by setting the third parameter to `true`, example `['additional_email', 'additionalEmails', true]` + +```php +DB::table('give_donors') + ->select( + 'id', + 'email', + 'name' + ) + ->attachMeta( + 'give_donormeta', + 'id', + 'donor_id', + ['additional_email', 'additionalEmails', true] + ); + +``` + +Generated SQL: + +```sql +SELECT id, email, name, GROUP_CONCAT(DISTINCT give_donormeta_attach_meta_0.meta_value) AS additionalEmails +FROM wp_give_donors + LEFT JOIN wp_give_donormeta give_donormeta_attach_meta_0 ON id = give_donormeta_attach_meta_0.donor_id AND give_donormeta_attach_meta_0.meta_key = 'additional_email' +GROUP BY id +``` + +Returned result: + +Instances with the same key, in this case `additional_email`, will be concatenated into JSON array string. + +```php +Array +( + [0] => stdClass Object + ( + [id] => 1 + [email] => bill@flywheel.local + [name] => Bill Murray + [additionalEmails] => ["email1@lywheel.local","email2@lywheel.local"] + ) + + [1] => stdClass Object + ( + [id] => 2 + [email] => jon@flywheel.local + [name] => Jon Waldstein + [additionalEmails] => ["email3@lywheel.local","email4@lywheel.local","email5@lywheel.local"] + ) + + [2] => stdClass Object + ( + [id] => 3 + [email] => ante@flywheel.local + [name] => Ante laca + [additionalEmails] => + ) + +) +``` + +### configureMetaTable + +By default, `QueryBuilder::attachMeta` will use `meta_key`, and `meta_value` as meta table column names, but that sometimes might not be the case. + +With `QueryBuilder::configureMetaTable` you can define a custom `meta_key` and `meta_value` column names. + +```php +DB::table('posts') + ->select( + ['ID', 'id'], + ['post_date', 'createdAt'] + ) + ->configureMetaTable( + 'give_donationmeta', + 'custom_meta_key', + 'custom_meta_value' + ) + ->attachMeta( + 'give_donationmeta', + 'ID', + 'donation_id', + ['_give_payment_total', 'amount'] + ) + ->leftJoin('give_donationmeta', 'ID', 'donationMeta.donation_id', 'donationMeta') + ->where('post_type', 'give_payment') + ->where('post_status', 'give_subscription') + ->where('donationMeta.custom_meta_key', 'subscription_id') + ->where('donationMeta.custom_meta_value', 1); +``` + +Generated SQL + +```sql +SELECT ID AS id, post_date AS createdAt, give_donationmeta_attach_meta_0.custom_meta_value AS amount +FROM wp_posts + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_0 + ON ID = give_donationmeta_attach_meta_0.donation_id AND + give_donationmeta_attach_meta_0.custom_meta_key = '_give_payment_total' + LEFT JOIN wp_give_donationmeta donationMeta ON ID = donationMeta.donation_id +WHERE post_type = 'give_payment' + AND post_status = 'give_subscription' + AND donationMeta.custom_meta_key = 'subscription_id' + AND donationMeta.custom_meta_value = '1' +``` + +## CRUD + +### Insert + +The QueryBuilder also provides `QueryBuilder::insert` method that may be used to insert records into the database table. + +```php +DB::table('posts') + ->insert([ + 'post_title' => 'Post Title', + 'post_author' => 1, + 'post_content' => 'Post Content' + ]); +``` + + +### Update + +In addition to inserting records into the database, the QueryBuilder can also update existing records using the `QueryBuilder::update` method. + +```php +DB::table('posts') + ->where('post_author', 1) + ->update([ + 'post_title' => 'Post Title 2', + 'post_content' => 'Post Content 2' + ]); +``` + +### Delete + +The `QueryBuilder::delete` method may be used to delete records from the table. + +```php +DB::table('posts') + ->where('post_author', 1) + ->delete(); +``` + + +### Get + +#### Available methods - get / getAll + +Get single row + +```php +$post = DB::table('posts')->where('post_author', 1)->get(); +``` + +Get all rows + +```php +$posts = DB::table('posts')->where('post_status', 'published')->getAll(); +``` + + + + +## Aggregate Functions + +The Query Builder also provides a variety of methods for retrieving aggregate values like `count`, `sum`, `avg`, `min` and `max`. + +### Count + +```php +$count = DB::table('posts') + ->where('post_type', 'published') + ->count(); +``` + +Count rows where provided column is not null. + +```php +$count = DB::table('donations')->count('not_null_value_column'); +``` + +### Sum + +```php +$sum = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->sum('meta_value'); +``` + +### Avg + +```php +$avg = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->avg('meta_value'); +``` + +### Min + +```php +$min = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->min('meta_value'); +``` + +### Max + +```php +$max = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->max('meta_value'); +``` diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100644 index 0000000..dc50b32 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,14 @@ +actor: Tester +bootstrap: _bootstrap.php +paths: + tests: tests + log: tests/_output + data: tests/_data + helpers: tests/_support + wp_root: "%WP_ROOT_FOLDER%" +settings: + colors: true + memory_limit: 1024M +params: + # read dynamic configuration parameters from the .env file + - .env.testing diff --git a/codeception.tric.yml b/codeception.tric.yml new file mode 100644 index 0000000..8a383c7 --- /dev/null +++ b/codeception.tric.yml @@ -0,0 +1,3 @@ +params: + # read dynamic configuration parameters from the .env file + - .env.testing.tric diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b982088 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "stellarwp/db", + "description": "A WPDB wrapper and query builder library.", + "type": "library", + "license": "GPL-2.0", + "autoload": { + "psr-4": { + "StellarWP\\DB\\": "src/DB/" + } + }, + "autoload-dev": { + "psr-4": { + "StellarWP\\DB\\Tests\\": "tests/_support/Helper/" + } + }, + "authors": [ + { + "name": "StellarWP", + "email": "dev@stellarwp.com" + } + ], + "minimum-stability": "stable", + "require": { + "lucatume/di52": "^3.0" + }, + "require-dev": { + "codeception/module-asserts": "^1.0", + "codeception/module-cli": "^1.0", + "codeception/module-db": "^1.0", + "codeception/module-filesystem": "^1.0", + "codeception/module-phpbrowser": "^1.0", + "codeception/module-rest": "^1.0", + "codeception/module-webdriver": "^1.0", + "codeception/util-universalframework": "^1.0", + "lucatume/wp-browser": "^3.0.14", + "phpunit/phpunit": "~6.0", + "szepeviktor/phpstan-wordpress": "^1.1", + "symfony/event-dispatcher-contracts": "^2.5.1", + "symfony/string": "^5.4" + }, + "scripts": { + "test:analysis": [ + "phpstan analyse -c phpstan.neon.dist --memory-limit=512M" + ] + }, + "scripts-descriptions": { + "test:analysis": "Run static code analysis." + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f3cd934 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5964 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "be30bdcb08d7290aba934f26408da912", + "packages": [ + { + "name": "lucatume/di52", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/lucatume/di52.git", + "reference": "5af2320885de6fa352dbaeecba87cd8df16d6db1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lucatume/di52/zipball/5af2320885de6fa352dbaeecba87cd8df16d6db1", + "reference": "5af2320885de6fa352dbaeecba87cd8df16d6db1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6", + "psr/container": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "files": [ + "aliases.php" + ], + "psr-4": { + "lucatume\\DI52\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0" + ], + "authors": [ + { + "name": "Luca Tumedei", + "email": "luca@theaveragedev.com" + } + ], + "description": "A PHP 5.2 compatible dependency injection container.", + "time": "2022-02-09T16:16:09+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2021-11-05T16:50:12+00:00" + } + ], + "packages-dev": [ + { + "name": "antecedent/patchwork", + "version": "2.1.21", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "homepage": "http://patchwork2.org/", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "time": "2022-02-07T07:28:34+00:00" + }, + { + "name": "behat/gherkin", + "version": "v4.9.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", + "shasum": "" + }, + "require": { + "php": "~7.2|~8.0" + }, + "require-dev": { + "cucumber/cucumber": "dev-gherkin-22.0.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2021-10-12T13:05:09+00:00" + }, + { + "name": "bordoni/phpass", + "version": "0.3.6", + "source": { + "type": "git", + "url": "https://github.com/bordoni/phpass.git", + "reference": "12f8f5cc03ebb7efd69554f104afe9aa1aa46e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bordoni/phpass/zipball/12f8f5cc03ebb7efd69554f104afe9aa1aa46e1a", + "reference": "12f8f5cc03ebb7efd69554f104afe9aa1aa46e1a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "replace": { + "hautelook/phpass": "0.3.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Hautelook": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Public Domain" + ], + "authors": [ + { + "name": "Solar Designer", + "email": "solar@openwall.com", + "homepage": "http://openwall.com/phpass/" + }, + { + "name": "Gustavo Bordoni", + "email": "gustavo@bordoni.me", + "homepage": "https://bordoni.me" + } + ], + "description": "Portable PHP password hashing framework", + "homepage": "http://github.com/bordoni/phpass/", + "keywords": [ + "blowfish", + "crypt", + "password", + "security" + ], + "time": "2012-08-31T00:00:00+00:00" + }, + { + "name": "codeception/codeception", + "version": "4.2.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Codeception.git", + "reference": "b88014f3348c93f3df99dc6d0967b0dbfa804474" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b88014f3348c93f3df99dc6d0967b0dbfa804474", + "reference": "b88014f3348c93f3df99dc6d0967b0dbfa804474", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.4.0", + "codeception/lib-asserts": "^1.0 | 2.0.*@dev", + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0", + "codeception/stub": "^2.0 | ^3.0 | ^4.0", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "^1.4 | ^2.0", + "php": ">=5.6.0 <9.0", + "symfony/console": ">=2.7 <6.0", + "symfony/css-selector": ">=2.7 <6.0", + "symfony/event-dispatcher": ">=2.7 <6.0", + "symfony/finder": ">=2.7 <6.0", + "symfony/yaml": ">=2.7 <6.0" + }, + "require-dev": { + "codeception/module-asserts": "^1.0 | 2.0.*@dev", + "codeception/module-cli": "^1.0 | 2.0.*@dev", + "codeception/module-db": "^1.0 | 2.0.*@dev", + "codeception/module-filesystem": "^1.0 | 2.0.*@dev", + "codeception/module-phpbrowser": "^1.0 | 2.0.*@dev", + "codeception/specify": "~0.3", + "codeception/util-universalframework": "*@dev", + "monolog/monolog": "~1.8", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <6.0", + "vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0 | ^5.0" + }, + "suggest": { + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "hoa/console": "For interactive console functionality", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" + }, + "bin": [ + "codecept" + ], + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Codeception\\": "src/Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "https://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "https://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "funding": [ + { + "url": "https://opencollective.com/codeception", + "type": "open_collective" + } + ], + "time": "2022-08-13T13:28:25+00:00" + }, + { + "name": "codeception/lib-asserts", + "version": "1.13.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/lib-asserts.git", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/184231d5eab66bc69afd6b9429344d80c67a33b6", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6", + "shasum": "" + }, + "require": { + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3 | ^9.0", + "ext-dom": "*", + "php": ">=5.6.0 <9.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Assertion methods used by Codeception core and Asserts module", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "time": "2020-10-21T16:26:20+00:00" + }, + { + "name": "codeception/lib-innerbrowser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/lib-innerbrowser.git", + "reference": "31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2", + "reference": "31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2", + "shasum": "" + }, + "require": { + "codeception/codeception": "4.*@dev", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.6.0 <9.0", + "symfony/browser-kit": ">=2.7 <6.0", + "symfony/dom-crawler": ">=2.7 <6.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "require-dev": { + "codeception/util-universalframework": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Parent library for all Codeception framework modules and PhpBrowser", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "time": "2021-08-30T15:21:42+00:00" + }, + { + "name": "codeception/module-asserts", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-asserts.git", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/59374f2fef0cabb9e8ddb53277e85cdca74328de", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "codeception/lib-asserts": "^1.13.1", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Codeception module containing various assertions", + "homepage": "https://codeception.com/", + "keywords": [ + "assertions", + "asserts", + "codeception" + ], + "time": "2020-10-21T16:48:15+00:00" + }, + { + "name": "codeception/module-cli", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-cli.git", + "reference": "1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-cli/zipball/1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f", + "reference": "1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + } + ], + "description": "Codeception module for testing basic shell commands and shell output", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception" + ], + "time": "2020-12-26T16:56:19+00:00" + }, + { + "name": "codeception/module-db", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-db.git", + "reference": "04c3e66fbd3a3ced17fcccc49627f6393a97b04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-db/zipball/04c3e66fbd3a3ced17fcccc49627f6393a97b04b", + "reference": "04c3e66fbd3a3ced17fcccc49627f6393a97b04b", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "DB module for Codeception", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "database-testing", + "db-testing" + ], + "time": "2022-03-05T19:38:40+00:00" + }, + { + "name": "codeception/module-filesystem", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-filesystem.git", + "reference": "781be167fb1557bfc9b61e0a4eac60a32c534ec1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-filesystem/zipball/781be167fb1557bfc9b61e0a4eac60a32c534ec1", + "reference": "781be167fb1557bfc9b61e0a4eac60a32c534ec1", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "php": ">=5.6.0 <9.0", + "symfony/finder": ">=2.7 <6.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Codeception module for testing local filesystem", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "filesystem" + ], + "time": "2020-10-24T14:46:40+00:00" + }, + { + "name": "codeception/module-phpbrowser", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-phpbrowser.git", + "reference": "8ba6bede11d0914e74d98691f427fd8f397f192e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-phpbrowser/zipball/8ba6bede11d0914e74d98691f427fd8f397f192e", + "reference": "8ba6bede11d0914e74d98691f427fd8f397f192e", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.1", + "codeception/lib-innerbrowser": "^1.3", + "guzzlehttp/guzzle": "^6.3|^7.0", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "require-dev": { + "codeception/module-rest": "^1.0" + }, + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Codeception module for testing web application over HTTP", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "functional-testing", + "http" + ], + "time": "2022-05-21T13:50:41+00:00" + }, + { + "name": "codeception/module-rest", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-rest.git", + "reference": "9cd7a87fd9343494e7782f7bdb51687c25046917" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-rest/zipball/9cd7a87fd9343494e7782f7bdb51687c25046917", + "reference": "9cd7a87fd9343494e7782f7bdb51687c25046917", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "justinrainbow/json-schema": "~5.2.9", + "php": ">=5.6.6 <9.0", + "softcreatr/jsonpath": "^0.5 || ^0.7" + }, + "require-dev": { + "codeception/lib-innerbrowser": "^1.0", + "codeception/util-universalframework": "^1.0" + }, + "suggest": { + "aws/aws-sdk-php": "For using AWS Auth" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gintautas Miselis" + } + ], + "description": "REST module for Codeception", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "rest" + ], + "time": "2021-11-18T18:58:15+00:00" + }, + { + "name": "codeception/module-webdriver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-webdriver.git", + "reference": "baa18b7bf70aa024012f967b5ce5021e1faa9151" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-webdriver/zipball/baa18b7bf70aa024012f967b5ce5021e1faa9151", + "reference": "baa18b7bf70aa024012f967b5ce5021e1faa9151", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "php": ">=5.6.0 <9.0", + "php-webdriver/webdriver": "^1.8.0" + }, + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Zaahid Bateson" + } + ], + "description": "WebDriver module for Codeception", + "homepage": "http://codeception.com/", + "keywords": [ + "acceptance-testing", + "browser-testing", + "codeception" + ], + "time": "2021-09-02T12:01:02+00:00" + }, + { + "name": "codeception/phpunit-wrapper", + "version": "6.8.4", + "source": { + "type": "git", + "url": "https://github.com/Codeception/phpunit-wrapper.git", + "reference": "6267fb4e647da0e708b3aef181ff679a64433d67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/6267fb4e647da0e708b3aef181ff679a64433d67", + "reference": "6267fb4e647da0e708b3aef181ff679a64433d67", + "shasum": "" + }, + "require": { + "phpunit/php-code-coverage": ">=4.0.4 <6.0", + "phpunit/phpunit": ">=6.5.13 <7.0", + "sebastian/comparator": ">=1.2.4 <3.0", + "sebastian/diff": ">=1.4 <4.0" + }, + "require-dev": { + "codeception/specify": "*", + "vlucas/phpdotenv": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\PHPUnit\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "PHPUnit classes used by Codeception", + "time": "2022-05-23T05:56:13+00:00" + }, + { + "name": "codeception/stub", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "eea518711d736eab838c1274593c4568ec06b23d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/eea518711d736eab838c1274593c4568ec06b23d", + "reference": "eea518711d736eab838c1274593c4568ec06b23d", + "shasum": "" + }, + "require": { + "codeception/phpunit-wrapper": "^6.6.1 | ^7.7.1 | ^8.0.3", + "phpunit/phpunit": ">=6.5 <9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "time": "2019-08-10T16:20:53+00:00" + }, + { + "name": "codeception/util-universalframework", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/util-universalframework.git", + "reference": "cc381f364c6d24f9b9c7b70a4c724949725f491a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/util-universalframework/zipball/cc381f364c6d24f9b9c7b70a4c724949725f491a", + "reference": "cc381f364c6d24f9b9c7b70a4c724949725f491a", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gintautas Miselis" + } + ], + "description": "Mock framework module used in internal Codeception tests", + "homepage": "http://codeception.com/", + "time": "2019-09-22T06:06:49+00:00" + }, + { + "name": "dg/mysql-dump", + "version": "v1.5.1", + "source": { + "type": "git", + "url": "https://github.com/dg/MySQL-dump.git", + "reference": "e0e287b715b43293773a8b0edf8514f606e01780" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dg/MySQL-dump/zipball/e0e287b715b43293773a8b0edf8514f606e01780", + "reference": "e0e287b715b43293773a8b0edf8514f606e01780", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "http://davidgrudl.com" + } + ], + "description": "MySQL database dump.", + "homepage": "https://github.com/dg/MySQL-dump", + "keywords": [ + "mysql" + ], + "time": "2019-09-10T21:36:25+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-20T22:16:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "13388f00956b1503577598873fffb5ae994b5737" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737", + "reference": "13388f00956b1503577598873fffb5ae994b5737", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-06-20T21:43:11+00:00" + }, + { + "name": "illuminate/collections", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^5.4)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "time": "2022-06-23T15:29:49+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "time": "2022-01-13T14:47:47+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "aed81891a6e046fdee72edd497f822190f61c162" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/aed81891a6e046fdee72edd497f822190f61c162", + "reference": "aed81891a6e046fdee72edd497f822190f61c162", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "time": "2021-11-16T13:57:03+00:00" + }, + { + "name": "illuminate/support", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "c3d643e77082786ae8a51502c757e9b1a3ee254e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/c3d643e77082786ae8a51502c757e9b1a3ee254e", + "reference": "c3d643e77082786ae8a51502c757e9b1a3ee254e", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "nesbot/carbon": "^2.53.1", + "php": "^7.3|^8.0", + "voku/portable-ascii": "^1.6.1" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^8.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^1.3|^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.2.2).", + "symfony/process": "Required to use the composer class (^5.4).", + "symfony/var-dumper": "Required to use the dd function (^5.4).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "time": "2022-06-27T13:26:30+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.12", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2022-04-13T08:02:27+00:00" + }, + { + "name": "lucatume/wp-browser", + "version": "3.1.6", + "source": { + "type": "git", + "url": "https://github.com/lucatume/wp-browser.git", + "reference": "5cefdab50d16f69447c48e5a8668d67d4892d6ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/5cefdab50d16f69447c48e5a8668d67d4892d6ef", + "reference": "5cefdab50d16f69447c48e5a8668d67d4892d6ef", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "^2.0", + "bordoni/phpass": "^0.3", + "codeception/codeception": "^2.5 || ^3.0 || ^4.0", + "dg/mysql-dump": "^1.3", + "ext-fileinfo": "*", + "ext-json": "*", + "ext-pdo": "*", + "mikehaertl/php-shellcommand": "^1.6", + "mikemclin/laravel-wp-password": "~2.0.0", + "php": ">=5.6.0", + "vria/nodiacritic": "^0.1.2", + "wp-cli/wp-cli": ">=2.0 <3.0.0", + "zordius/lightncandy": "^1.2" + }, + "conflict": { + "codeception/module-asserts": ">=2.0", + "codeception/module-cli": ">=2.0", + "codeception/module-db": ">=2.0", + "codeception/module-filesystem": ">=2.0", + "codeception/module-phpbrowser": ">=2.0", + "codeception/module-webdriver": ">=2.0", + "codeception/util-universalframework": ">=2.0" + }, + "require-dev": { + "erusev/parsedown": "^1.7", + "ext-pcntl": "*", + "ext-sockets": "*", + "gumlet/php-image-resize": "^1.6", + "lucatume/codeception-snapshot-assertions": "^0.2", + "mikey179/vfsstream": "^1.6", + "victorjonsson/markdowndocs": "dev-master", + "vlucas/phpdotenv": "^3.0", + "wp-cli/wp-cli-bundle": "*" + }, + "suggest": { + "codeception/module-asserts:^1.0": "Codeception 4.0 compatibility.", + "codeception/module-cli:^1.0": "Codeception 4.0 compatibility; required by the WPCLI module.", + "codeception/module-db:^1.0": "Codeception 4.0 compatibility; required by the WPDb module, PHP 5.6 compatible version.", + "codeception/module-filesystem:^1.0": "Codeception 4.0 compatibility; required by the WPFilesystem module.", + "codeception/module-phpbrowser:^1.0": "Codeception 4.0 compatibility; required by the WPBrowser module.", + "codeception/module-webdriver:^1.0": "Codeception 4.0 compatibility; required by the WPWebDriver module.", + "codeception/util-universalframework:^1.0": "Codeception 4.0 compatibility; required by the WordPress framework module.", + "gumlet/php-image-resize": "To handle runtime image modification in the WPDb::haveAttachmentInDatabase method.", + "vlucas/phpdotenv:^4.0": "To manage more complex environment file based configuration of the suites." + }, + "type": "library", + "extra": { + "_hash": "484f861f69198089cab0e642f27e5653" + }, + "autoload": { + "files": [ + "src/tad/WPBrowser/utils.php", + "src/tad/WPBrowser/wp-polyfills.php" + ], + "psr-4": { + "tad\\": "src/tad", + "Codeception\\": "src/Codeception", + "lucatume\\WPBrowser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "theAverageDev (Luca Tumedei)", + "email": "luca@theaveragedev.com", + "homepage": "http://theaveragedev.com", + "role": "Developer" + } + ], + "description": "WordPress extension of the PhpBrowser class.", + "homepage": "http://github.com/lucatume/wp-browser", + "keywords": [ + "codeception", + "wordpress" + ], + "funding": [ + { + "url": "https://github.com/lucatume", + "type": "github" + } + ], + "time": "2022-04-28T06:45:08+00:00" + }, + { + "name": "mikehaertl/php-shellcommand", + "version": "1.6.4", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-shellcommand.git", + "reference": "3488d7803df1e8f1a343d3d0ca452d527ad8d5e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-shellcommand/zipball/3488d7803df1e8f1a343d3d0ca452d527ad8d5e5", + "reference": "3488d7803df1e8f1a343d3d0ca452d527ad8d5e5", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0 <=9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\shellcommand\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Härtl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "An object oriented interface to shell commands", + "keywords": [ + "shell" + ], + "time": "2021-03-17T06:54:33+00:00" + }, + { + "name": "mikemclin/laravel-wp-password", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/mikemclin/laravel-wp-password.git", + "reference": "5225c95f75aa0a5ad4040ec2074d1c8d7f10b5f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikemclin/laravel-wp-password/zipball/5225c95f75aa0a5ad4040ec2074d1c8d7f10b5f4", + "reference": "5225c95f75aa0a5ad4040ec2074d1c8d7f10b5f4", + "shasum": "" + }, + "require": { + "bordoni/phpass": "0.3.*", + "illuminate/support": ">=4.0.0", + "php": ">=5.3.0" + }, + "replace": { + "mikemclin/laravel-wp-password": "self.version" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "^2.2" + }, + "type": "laravel-package", + "extra": { + "laravel": { + "providers": [ + "MikeMcLin\\WpPassword\\WpPasswordProvider" + ], + "aliases": { + "WpPassword": "MikeMcLin\\WpPassword\\Facades\\WpPassword" + } + } + }, + "autoload": { + "psr-4": { + "MikeMcLin\\WpPassword\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike McLin", + "email": "mike@mikemclin.com", + "homepage": "http://mikemclin.net" + } + ], + "description": "Laravel package that checks and creates WordPress password hashes", + "homepage": "https://github.com/mikemclin/laravel-wp-password", + "keywords": [ + "hashing", + "laravel", + "password", + "wordpress" + ], + "time": "2021-09-30T13:48:57+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.2", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e62b7c3849d22ec55f3ec425507bf7968193a6cb", + "reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "time": "2022-08-23T13:07:01+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.61.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "bdf4f4fe3a3eac4de84dbec0738082a862c68ba6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bdf4f4fe3a3eac4de84dbec0738082a862c68ba6", + "reference": "bdf4f4fe3a3eac4de84dbec0738082a862c68ba6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2022-08-06T12:41:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "e04781a84e364615a7b5f70fdc345c8253ca5b8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/e04781a84e364615a7b5f70fdc345c8253ca5b8f", + "reference": "e04781a84e364615a7b5f70fdc345c8253ca5b8f", + "shasum": "" + }, + "replace": { + "giacocorsiglia/wordpress-stubs": "*" + }, + "require-dev": { + "nikic/php-parser": "< 4.12.0", + "php": "~7.3 || ~8.0", + "php-stubs/generator": "^0.8.1", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpstan": "^1.2" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "time": "2022-07-15T11:10:45+00:00" + }, + { + "name": "php-webdriver/webdriver", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/b27ddf458d273c7d4602106fcaf978aa0b7fe15a", + "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" + }, + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Exception/TimeoutException.php" + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "time": "2022-05-03T12:16:34+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c53312ecc575caf07b0e90dee43883fdf90ca67c", + "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-07-20T09:57:31+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "rmccue/requests", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/Requests.git", + "reference": "82e6936366eac3af4d836c18b9d8c31028fe4cd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/Requests/zipball/82e6936366eac3af4d836c18b9d8c31028fe4cd5", + "reference": "82e6936366eac3af4d836c18b9d8c31028fe4cd5", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcompatibility/php-compatibility": "^9.0", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5", + "requests/test-server": "dev-master", + "squizlabs/php_codesniffer": "^3.5", + "wp-coding-standards/wpcs": "^2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/WordPress/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "time": "2021-06-04T09:56:25+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T13:51:24+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "softcreatr/jsonpath", + "version": "0.7.5", + "source": { + "type": "git", + "url": "https://github.com/SoftCreatR/JSONPath.git", + "reference": "008569bf80aa3584834f7890781576bc7b65afa7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/008569bf80aa3584834f7890781576bc7b65afa7", + "reference": "008569bf80aa3584834f7890781576bc7b65afa7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.1" + }, + "replace": { + "flow/jsonpath": "*" + }, + "require-dev": { + "phpunit/phpunit": ">=7.0", + "roave/security-advisories": "dev-master", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Flow\\JSONPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Frank", + "email": "stephen@flowsa.com", + "homepage": "https://prismaticbytes.com", + "role": "Developer" + }, + { + "name": "Sascha Greuel", + "email": "hello@1-2.dev", + "homepage": "http://1-2.dev", + "role": "Developer" + } + ], + "description": "JSONPath implementation for parsing, searching and flattening arrays", + "funding": [ + { + "url": "https://github.com/softcreatr", + "type": "github" + } + ], + "time": "2021-06-02T22:15:26+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "081fe28a26b6bd671dea85ef3a4b5003f3c88027" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/081fe28a26b6bd671dea85ef3a4b5003f3c88027", + "reference": "081fe28a26b6bd671dea85ef3a4b5003f3c88027", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-27T15:50:05+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-22T10:42:43+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "c1681789f059ab756001052164726ae88512ae3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", + "reference": "c1681789f059ab756001052164726ae88512ae3d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0b900ca5576ecd59e08c76127e616667cfe427a7", + "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-05T16:45:39+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:37:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-24T16:15:25+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21", + "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/console": "<5.3", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T13:00:38+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.3" + }, + "require-dev": { + "symfony/console": "^5.3|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "e487dc725845ac914681e148d0e8ac479ad887ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/e487dc725845ac914681e148d0e8ac479ad887ec", + "reference": "e487dc725845ac914681e148d0e8ac479ad887ec", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", + "phpstan/phpstan": "^1.6", + "symfony/polyfill-php73": "^1.12.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^8 || ^9", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "funding": [ + { + "url": "https://www.paypal.me/szepeviktor", + "type": "custom" + } + ], + "time": "2022-06-21T10:51:00+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/87337c91b9dfacee02452244ee14ab3c43bc485a", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-01-24T18:55:24+00:00" + }, + { + "name": "vria/nodiacritic", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/vria/nodiacritic.git", + "reference": "3efeb60fb2586fe3ce8ff0f3c122d380717b8b07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vria/nodiacritic/zipball/3efeb60fb2586fe3ce8ff0f3c122d380717b8b07", + "reference": "3efeb60fb2586fe3ce8ff0f3c122d380717b8b07", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "VRia\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0" + ], + "authors": [ + { + "name": "Riabchenko Vlad", + "email": "contact@vria.eu", + "homepage": "http://vria.eu" + } + ], + "description": "Tiny helper function that removes all diacritical signs from characters", + "homepage": "https://github.com/vria/nodiacritic", + "keywords": [ + "accent", + "diacritic", + "filter", + "string", + "text" + ], + "time": "2016-09-17T22:03:11+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "wp-cli/mustangostang-spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/spyc.git", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/spyc/zipball/6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "includes/functions.php" + ], + "psr-4": { + "Mustangostang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", + "homepage": "https://github.com/mustangostang/spyc/", + "time": "2017-04-25T11:26:20+00:00" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.11.14", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "f8f340e4a87687549d046e2da516242f7f36c934" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/f8f340e4a87687549d046e2da516242f7f36c934", + "reference": "f8f340e4a87687549d046e2da516242f7f36c934", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/cli/cli.php" + ], + "psr-0": { + "cli": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + }, + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "time": "2022-07-04T21:44:34+00:00" + }, + { + "name": "wp-cli/wp-cli", + "version": "v2.6.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli.git", + "reference": "dee13c2baf6bf972484a63f8b8dab48f7220f095" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/dee13c2baf6bf972484a63f8b8dab48f7220f095", + "reference": "dee13c2baf6bf972484a63f8b8dab48f7220f095", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "mustache/mustache": "^2.14.1", + "php": "^5.6 || ^7.0 || ^8.0", + "rmccue/requests": "^1.8", + "symfony/finder": ">2.7", + "wp-cli/mustangostang-spyc": "^0.6.3", + "wp-cli/php-cli-tools": "~0.11.2" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.2 || ^2", + "wp-cli/extension-command": "^1.1 || ^2", + "wp-cli/package-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^3.1.3" + }, + "suggest": { + "ext-readline": "Include for a better --prompt implementation", + "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates" + }, + "bin": [ + "bin/wp", + "bin/wp.bat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "WP_CLI\\": "php/" + }, + "classmap": [ + "php/class-wp-cli.php", + "php/class-wp-cli-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI framework", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "time": "2022-01-25T16:31:27+00:00" + }, + { + "name": "zordius/lightncandy", + "version": "v1.2.6", + "source": { + "type": "git", + "url": "https://github.com/zordius/lightncandy.git", + "reference": "b451f73e8b5c73e62e365997ba3c993a0376b72a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zordius/lightncandy/zipball/b451f73e8b5c73e62e365997ba3c993a0376b72a", + "reference": "b451f73e8b5c73e62e365997ba3c993a0376b72a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": ">=7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.5-dev" + } + }, + "autoload": { + "psr-4": { + "LightnCandy\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Zordius Chen", + "email": "zordius@gmail.com" + } + ], + "description": "An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).", + "homepage": "https://github.com/zordius/lightncandy", + "keywords": [ + "handlebars", + "logicless", + "mustache", + "php", + "template" + ], + "time": "2021-07-11T04:52:41+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..98a5aea --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,28 @@ +# Configuration for PHPStan +# https://phpstan.org/config-reference + +includes: + # @see https://github.com/phpstan/phpstan-src/blob/master/conf/bleedingEdge.neon + - phar://phpstan.phar/conf/bleedingEdge.neon + # Include this extension + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + parallel: + jobSize: 10 + maximumNumberOfProcesses: 32 + minimumNumberOfJobsPerProcess: 2 + level: 5 + inferPrivatePropertyTypeFromConstructor: true + reportUnmatchedIgnoredErrors: false + checkGenericClassInNonGenericObjectType: false + + # Paths to be analyzed. + paths: + - %currentWorkingDirectory%/src + + ignoreErrors: + # Uses func_get_args() + - '#^Function add_query_arg invoked with [123] parameters?, 0 required\.$#' + # Uses func_get_args() + - '#^Function apply_filters(_ref_array)? invoked with [34567] parameters, 2 required\.$#' diff --git a/src/DB/DB.php b/src/DB/DB.php new file mode 100644 index 0000000..882a94e --- /dev/null +++ b/src/DB/DB.php @@ -0,0 +1,295 @@ +<?php + +namespace StellarWP\DB; + +use Exception; +use lucatume\DI52\App; +use StellarWP\DB\Database\Exceptions\DatabaseQueryException; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use WP_Error; + +/** + * Class DB + * + * A static decorator for the $wpdb class and decorator function which does SQL error checking when performing queries. + * If a SQL error occurs a DatabaseQueryException is thrown. + * + * @method static int|bool query(string $query) + * @method static int|false insert(string $table, array $data, array|string $format) + * @method static int|false delete(string $table, array $where, array|string $where_format) + * @method static int|false update(string $table, array $data, array $where, array|string $format, array|string $where_format) + * @method static int|false replace(string $table, array $data, array|string $format) + * @method static null|string get_var(string $query = null, int $x = 0, int $y = 0) + * @method static array|object|null|void get_row(string $query = null, string $output = OBJECT, int $y = 0) + * @method static array get_col(string $query = null, int $x = 0) + * @method static array|object|null get_results(string $query = null, string $output = OBJECT) + * @method static string get_charset_collate() + * @method static string esc_like(string $text) + * @method static string remove_placeholder_escape(string $text) + */ +class DB { + /** + * Initializes the service provider. + * + * @since 1.0.0 + */ + public static function init(): void { + $container = App::container(); + + if ( $container->getVar( 'stellarwp_db_initialized', false ) ) { + return; + } + + $container->register( Database\Provider::class ); + $container->setVar( 'stellarwp_db_initialized', true ); + } + + /** + * Runs the dbDelta function and returns a WP_Error with any errors that occurred during the process + * + * @see dbDelta() for parameter and return details + * + * @since 2.9.2 + * + * @param $delta + * + * @return array + * @throws DatabaseQueryException + */ + public static function delta( $delta ) { + return self::runQueryWithErrorChecking( + function () use ( $delta ) { + return dbDelta( $delta ); + } + ); + } + + /** + * A convenience method for the $wpdb->prepare method + * + * @see WPDB::prepare() for usage details + * + * @since 2.9.6 + * + * @param string $query + * @param mixed ...$args + * + * @return false|mixed + */ + public static function prepare( $query, ...$args ) { + global $wpdb; + + return $wpdb->prepare( $query, ...$args ); + } + + /** + * Magic method which calls the static method on the $wpdb while performing error checking + * + * @since 2.22.0 add givewp_db_pre_query action + * @since 2.9.6 + * + * @param $name + * @param $arguments + * + * @return mixed + * @throws DatabaseQueryException + */ + public static function __callStatic( $name, $arguments ) { + return self::runQueryWithErrorChecking( + static function () use ( $name, $arguments ) { + global $wpdb; + + if ( in_array( $name, [ 'get_row', 'get_col', 'get_results', 'query' ], true) ) { + /** + * Allow for hooking just before query execution. + * + * @since 1.0.0 + * + * @param string $argument + */ + do_action( 'stellarwp_db_pre_query', current( $arguments ) ); + } + + return call_user_func_array( [ $wpdb, $name ], $arguments ); + } + ); + } + + /** + * Get last insert ID + * + * @since 2.10.0 + * @return int + */ + public static function last_insert_id() { + global $wpdb; + + return $wpdb->insert_id; + } + + /** + * Prefix given table name with $wpdb->prefix + * + * @param string $tableName + * + * @return string + */ + public static function prefix( $tableName ) { + global $wpdb; + + return $wpdb->prefix . $tableName; + } + + /** + * Create QueryBuilder instance + * + * @param string $table + * @param null|string $alias + * + * @return QueryBuilder + */ + public static function table( $table, $alias = null ) { + $builder = new QueryBuilder(); + $builder->from( $table, $alias ); + + return $builder; + } + + /** + * Runs a transaction. If the callable works then the transaction is committed. If the callable throws an exception + * then the transaction is rolled back. + * + * @since 2.19.6 + * + * @param callable $callback + * + * @return void + * @throws Exception + */ + public static function transaction( callable $callback ) { + self::beginTransaction(); + + try { + $callback(); + } catch (Exception $e) { + self::rollback(); + throw $e; + } + + self::commit(); + } + + /** + * Manually starts a transaction + * + * @since 2.19.6 + * + * @return void + */ + public static function beginTransaction() { + global $wpdb; + $wpdb->query( 'START TRANSACTION' ); + } + + /** + * Manually rolls back a transaction + * + * @since 2.19.6 + * + * @return void + */ + public static function rollback() { + global $wpdb; + $wpdb->query( 'ROLLBACK' ); + } + + /** + * Manually commits a transaction + * + * @since 2.19.6 + * + * @return void + */ + public static function commit() { + global $wpdb; + $wpdb->query( 'COMMIT' ); + } + + /** + * Used as a flag to tell QueryBuilder not to process the provided SQL + * If $args are provided, we will assume that dev wants to use DB::prepare method with raw SQL + * + * @param string $sql + * @param array ...$args + * + * @return RawSQL + */ + public static function raw( $sql, ...$args ) { + return new RawSQL( $sql, ...$args ); + } + + /** + * Runs a query callable and checks to see if any unique SQL errors occurred when it was run + * + * @since 2.9.2 + * + * @param Callable $queryCaller + * + * @return mixed + * @throws DatabaseQueryException + */ + private static function runQueryWithErrorChecking( $queryCaller ) { + global $wpdb, $EZSQL_ERROR; + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + + $errorCount = is_array( $EZSQL_ERROR ) ? count( $EZSQL_ERROR ) : 0; + $hasShowErrors = $wpdb->hide_errors(); + + $output = $queryCaller(); + + if ( $hasShowErrors ) { + $wpdb->show_errors(); + } + + $wpError = self::getQueryErrors( $errorCount ); + + if ( ! empty( $wpError->errors ) ) { + throw new DatabaseQueryException( $wpdb->last_query, $wpError->errors ); + } + + return $output; + } + + /** + * Retrieves the SQL errors stored by WordPress + * + * @since 2.9.2 + * + * @param int $initialCount + * + * @return WP_Error + */ + private static function getQueryErrors( $initialCount = 0 ) { + global $EZSQL_ERROR; + + $wpError = new WP_Error(); + + if ( is_array( $EZSQL_ERROR ) ) { + for ( $index = $initialCount, $indexMax = count( $EZSQL_ERROR ); $index < $indexMax; $index++ ) { + $error = $EZSQL_ERROR[ $index ]; + + if ( empty( $error['error_str'] ) || empty( $error['query'] ) || 0 === strpos( + $error['query'], + 'DESCRIBE ' + ) ) { + continue; + } + + $wpError->add( 'db_delta_error', $error['error_str'] ); + } + } + + return $wpError; + } +} diff --git a/src/DB/Database/Actions/EnableBigSqlSelects.php b/src/DB/Database/Actions/EnableBigSqlSelects.php new file mode 100644 index 0000000..bf7c7d6 --- /dev/null +++ b/src/DB/Database/Actions/EnableBigSqlSelects.php @@ -0,0 +1,32 @@ +<?php + +declare( strict_types=1 ); + +namespace StellarWP\DB\Database\Actions; + +class EnableBigSqlSelects { + /** + * @since 1.0.0 + * + * Enables mysql big selects for the session using a session system variable. + * + * This is necessary for hosts that have an arbitrary MAX_JOIN_SIZE limit, which prevents more complex queries from + * running properly. Setting SQL_BIG_SELECTS ignores this limit. This is also done by WooCommerce, supporting the + * idea that this is a viable option. There also doesn't seem to be a way for hosts to prevent this. + * + * @see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_big_selects + * @see https://dev.mysql.com/doc/refman/5.7/en/system-variable-privileges.html + * + */ + public function set_var() { + static $bigSelects = false; + + if ( ! $bigSelects ) { + global $wpdb; + + $wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1;' ); + + $bigSelects = true; + } + } +} diff --git a/src/DB/Database/Exceptions/DatabaseQueryException.php b/src/DB/Database/Exceptions/DatabaseQueryException.php new file mode 100644 index 0000000..ac8f5de --- /dev/null +++ b/src/DB/Database/Exceptions/DatabaseQueryException.php @@ -0,0 +1,56 @@ +<?php + +namespace StellarWP\DB\Database\Exceptions; + +use Throwable; + +/** + * Class DatabaseQueryException + * + * An exception for when errors occurred within the database while performing a query, which stores the SQL errors the + * database returned + * + * @since 1.0.0 + */ +class DatabaseQueryException extends \Exception { + /** + * @var string[] + */ + private $queryErrors; + + /** + * @var string + */ + private $query; + + /** + * @since 1.0.0 + */ + public function __construct( + string $query, + array $queryErrors, + string $message = 'Database Query', + $code = 0, + Throwable $previous = null + ) { + $this->query = $query; + $this->queryErrors = $queryErrors; + + parent::__construct( $message, $code, $previous ); + } + + /** + * Returns the query errors + * + * @since 1.0.0 + * + * @return string[] + */ + public function getQueryErrors(): array { + return $this->queryErrors; + } + + public function getQuery(): string { + return $this->query; + } +} diff --git a/src/DB/Database/Provider.php b/src/DB/Database/Provider.php new file mode 100644 index 0000000..09e1b8a --- /dev/null +++ b/src/DB/Database/Provider.php @@ -0,0 +1,29 @@ +<?php + +namespace StellarWP\DB\Database; + +use lucatume\DI52\App; +use lucatume\DI52\ServiceProvider; + +class Provider extends ServiceProvider { + + /** + * Binds and sets up implementations. + * + * @since 1.0.0 + */ + public function register() { + $this->container->singleton( static::class, $this ); + $this->container->singleton( Actions\EnableBigSqlSelects::class, Actions\EnableBigSqlSelects::class ); + $this->register_hooks(); + } + + /** + * Registers all hooks. + * + * @since 1.0.0 + */ + private function register_hooks() : void { + add_action( 'stellarwp_db_pre_query', App::callback( Actions\EnableBigSqlSelects::class, 'set_var' ) ); + } +} diff --git a/src/DB/Provider.php b/src/DB/Provider.php new file mode 100644 index 0000000..e69de29 diff --git a/src/DB/QueryBuilder/Clauses/From.php b/src/DB/QueryBuilder/Clauses/From.php new file mode 100644 index 0000000..607c88f --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/From.php @@ -0,0 +1,29 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\QueryBuilder; + +/** + * @since 2.19.0 + */ +class From { + /** + * @var string|RawSQL + */ + public $table; + + /** + * @var string + */ + public $alias; + + /** + * @param string|RawSQL $table + * @param string|null $alias + */ + public function __construct( $table, $alias = null ) { + $this->table = QueryBuilder::prefixTable( $table ); + $this->alias = trim( $alias ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/Having.php b/src/DB/QueryBuilder/Clauses/Having.php new file mode 100644 index 0000000..d914e4b --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Having.php @@ -0,0 +1,121 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\Types\Math; +use StellarWP\DB\QueryBuilder\Types\Operator; +use InvalidArgumentException; + +/** + * @since 2.19.0 + */ +class Having { + /** + * @var string + */ + public $column; + + /** + * @var string + */ + public $comparisonOperator; + + /** + * @var string|int + */ + public $value; + + /** + * @var string + */ + public $logicalOperator; + + /** + * @var string|null + */ + public $mathFunction; + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * @param string|null $logicalOperator + * @param string $mathFunction + */ + public function __construct( $column, $comparisonOperator, $value, $logicalOperator, $mathFunction = null ) { + $this->column = trim( $column ); + $this->comparisonOperator = $this->getComparisonOperator( $comparisonOperator ); + $this->value = $value; + $this->logicalOperator = $logicalOperator ? $this->getLogicalOperator( $logicalOperator ) : ''; + $this->mathFunction = $this->getMathFunction( $mathFunction ); + } + + /** + * @param string $logicalOperator + * + * @return string + */ + private function getLogicalOperator( $logicalOperator ) { + $operators = [ + Operator::_AND, + Operator::_OR + ]; + + $logicalOperator = strtoupper( $logicalOperator ); + + if ( ! in_array( $logicalOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported logical operator %s. Please use one of the supported operators (%s)', + $logicalOperator, + implode( ',', $operators ) + ) + ); + } + + return $logicalOperator; + } + + /** + * @param string $comparisonOperator + * + * @return string + */ + private function getComparisonOperator( $comparisonOperator ) { + $operators = [ + '<', + '<=', + '>', + '>=', + '<>', + '!=', + '=' + ]; + + if ( ! in_array( $comparisonOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported comparison operator %s. Please use one of the supported operators (%s)', + $comparisonOperator, + implode( ',', $operators ) + ) + ); + } + + return $comparisonOperator; + } + + + /** + * @param string $mathFunction + * + * @return string + */ + private function getMathFunction( $mathFunction ) { + if ( array_key_exists( $mathFunction, Math::getTypes() ) ) { + return $mathFunction; + } + + return null; + } +} diff --git a/src/DB/QueryBuilder/Clauses/Join.php b/src/DB/QueryBuilder/Clauses/Join.php new file mode 100644 index 0000000..137a70c --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Join.php @@ -0,0 +1,59 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\QueryBuilder\Types\JoinType; +use InvalidArgumentException; + +/** + * @since 2.19.0 + */ +class Join { + /** + * @var string + */ + public $table; + + /** + * @var string + */ + public $joinType; + + /** + * @var string|null + */ + public $alias; + + /** + * @param string $table + * @param string $joinType \StellarWP\DB\QueryBuilder\Types\JoinType + * @param string|null $alias + */ + public function __construct( $joinType, $table, $alias = null ) { + $this->table = QueryBuilder::prefixTable( $table ); + $this->joinType = $this->getJoinType( $joinType ); + $this->alias = trim( $alias ); + } + + /** + * @param string $type + * + * @return string + */ + private function getJoinType( $type ) { + $type = strtoupper( $type ); + + if ( array_key_exists( $type, JoinType::getTypes() ) ) { + return $type; + } + + throw new InvalidArgumentException( + sprintf( + 'Join type %s is not supported. Please provide one of the supported join types (%s)', + $type, + implode( ',', JoinType::getTypes() ) + ) + ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/JoinCondition.php b/src/DB/QueryBuilder/Clauses/JoinCondition.php new file mode 100644 index 0000000..b5f020e --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/JoinCondition.php @@ -0,0 +1,72 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\Types\Operator; +use InvalidArgumentException; + +/** + * @since 2.19.0 + */ +class JoinCondition { + /** + * @var string + */ + public $logicalOperator; + + /** + * @var string + */ + public $column1; + + /** + * @var mixed + */ + public $column2; + + /** + * @var bool + */ + public $quote; + + + /** + * @param string $logicalOperator + * @param string $column1 + * @param string $column2 + * @param bool $quote + */ + public function __construct( $logicalOperator, $column1, $column2, $quote = false ) { + $this->logicalOperator = $this->getLogicalOperator( $logicalOperator ); + $this->column1 = trim( $column1 ); + $this->column2 = trim( $column2 ); + $this->quote = $quote; + } + + /** + * @param string $operator + * + * @return string + */ + private function getLogicalOperator( $operator ) { + $operator = strtoupper( $operator ); + + $supportedOperators = [ + Operator::ON, + Operator::_AND, + Operator::_OR + ]; + + if ( ! in_array( $operator, $supportedOperators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported logical operator %s. Please provide one of the supported operators (%s)', + $operator, + implode( ',', $supportedOperators ) + ) + ); + } + + return $operator; + } +} diff --git a/src/DB/QueryBuilder/Clauses/MetaTable.php b/src/DB/QueryBuilder/Clauses/MetaTable.php new file mode 100644 index 0000000..49a617c --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/MetaTable.php @@ -0,0 +1,36 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\QueryBuilder; + +/** + * @since 2.19.0 + */ +class MetaTable { + /** + * @var string + */ + public $tableName; + + /** + * @var string + */ + public $keyColumnName; + + /** + * @var string + */ + public $valueColumnName; + + /** + * @param string $table + * @param string $metaKeyColumnName + * @param string $metaValueColumnName + */ + public function __construct( $table, $metaKeyColumnName, $metaValueColumnName ) { + $this->tableName = QueryBuilder::prefixTable( $table ); + $this->keyColumnName = trim( $metaKeyColumnName ); + $this->valueColumnName = trim( $metaValueColumnName ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/OrderBy.php b/src/DB/QueryBuilder/Clauses/OrderBy.php new file mode 100644 index 0000000..e228e91 --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/OrderBy.php @@ -0,0 +1,51 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use InvalidArgumentException; + +/** + * @since 2.19.0 + */ +class OrderBy { + /** + * @var string + */ + public $column; + + /** + * @var string + */ + public $direction; + + /** + * @param $column + * @param $direction + */ + public function __construct( $column, $direction ) { + $this->column = trim( $column ); + $this->direction = $this->getSortDirection( $direction ); + } + + /** + * @param string $direction + * + * @return string + */ + private function getSortDirection( $direction ) { + $direction = strtoupper( $direction ); + $directions = ['ASC', 'DESC']; + + if ( ! in_array( $direction, $directions, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported sort direction %s. Please use one of the (%s)', + $direction, + implode( ',', $directions ) + ) + ); + } + + return $direction; + } +} diff --git a/src/DB/QueryBuilder/Clauses/RawSQL.php b/src/DB/QueryBuilder/Clauses/RawSQL.php new file mode 100644 index 0000000..27650f5 --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/RawSQL.php @@ -0,0 +1,23 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\DB; + +/** + * @since 2.19.0 + */ +class RawSQL { + /** + * @var string + */ + public $sql; + + /** + * @param string $sql + * @param null $args + */ + public function __construct( $sql, $args = null ) { + $this->sql = $args ? DB::prepare( $sql, $args ) : $sql; + } +} diff --git a/src/DB/QueryBuilder/Clauses/Select.php b/src/DB/QueryBuilder/Clauses/Select.php new file mode 100644 index 0000000..500838f --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Select.php @@ -0,0 +1,27 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +/** + * @since 2.19.0 + */ +class Select { + /** + * @var string + */ + public $column; + + /** + * @var string + */ + public $alias; + + /** + * @param string $column + * @param string|null $alias + */ + public function __construct( $column, $alias = null ) { + $this->column = trim( $column ); + $this->alias = trim( $alias ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/Union.php b/src/DB/QueryBuilder/Clauses/Union.php new file mode 100644 index 0000000..8d5f663 --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Union.php @@ -0,0 +1,29 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\QueryBuilder; + +/** + * @since 2.19.0 + */ +class Union { + /** + * @var QueryBuilder + */ + public $builder; + + /** + * @var bool + */ + public $all = false; + + /** + * @param QueryBuilder $builder + * @param bool $all + */ + public function __construct( $builder, $all = false ) { + $this->builder = $builder; + $this->all = $all; + } +} diff --git a/src/DB/QueryBuilder/Clauses/Where.php b/src/DB/QueryBuilder/Clauses/Where.php new file mode 100644 index 0000000..d113f2c --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Where.php @@ -0,0 +1,112 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Clauses; + +use StellarWP\DB\QueryBuilder\Types\Operator; +use InvalidArgumentException; + +/** + * @since 2.19.0 + */ +class Where { + /** + * @var string + */ + public $column; + + /** + * @var mixed + */ + public $value; + + /** + * @var string + */ + public $comparisonOperator; + + /** + * @var string + */ + public $logicalOperator; + + /** + * @var string|null + */ + public $type; + + /** + * @param string $column + * @param string $value + * @param string $comparisonOperator + * @param string|null $logicalOperator + */ + public function __construct( $column, $value, $comparisonOperator, $logicalOperator ) { + $this->column = trim( $column ); + $this->value = $value; + $this->comparisonOperator = $this->getComparisonOperator( $comparisonOperator ); + $this->logicalOperator = $logicalOperator ? $this->getLogicalOperator( $logicalOperator ) : ''; + } + + /** + * @param string $comparisonOperator + * + * @return string + */ + private function getComparisonOperator( $comparisonOperator ) { + $operators = [ + '<', + '<=', + '>', + '>=', + '<>', + '!=', + '=', + Operator::LIKE, + Operator::NOTLIKE, + Operator::IN, + Operator::NOTIN, + Operator::BETWEEN, + Operator::NOTBETWEEN, + Operator::ISNULL, + Operator::NOTNULL + ]; + + if ( ! in_array( $comparisonOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported comparison operator %s. Please use one of the supported operators (%s)', + $comparisonOperator, + implode( ',', $operators ) + ) + ); + } + + return $comparisonOperator; + } + + /** + * @param string $logicalOperator + * + * @return string + */ + private function getLogicalOperator( $logicalOperator ) { + $operators = [ + Operator::_AND, + Operator::_OR + ]; + + $logicalOperator = strtoupper( $logicalOperator ); + + if ( ! in_array( $logicalOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported logical operator %s. Please use one of the supported operators (%s)', + $logicalOperator, + implode( ',', $operators ) + ) + ); + } + + return $logicalOperator; + } +} diff --git a/src/DB/QueryBuilder/Concerns/Aggregate.php b/src/DB/QueryBuilder/Concerns/Aggregate.php new file mode 100644 index 0000000..7c58b37 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/Aggregate.php @@ -0,0 +1,82 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; + +/** + * @since 2.19.0 + */ +trait Aggregate { + /** + * Returns the number of rows returned by a query + * + * @since 2.19.0 + * @param null|string $column + * + * @return int + */ + public function count( $column = null ) { + $column = ( ! $column || $column === '*' ) ? '1' : trim( $column ); + + $this->selects[] = new RawSQL( 'SELECT COUNT(%1s) AS count', $column ); + + return +$this->get()->count; + } + + /** + * Returns the total sum in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function sum( $column ) { + $this->selects[] = new RawSQL( 'SELECT SUM(%1s) AS sum', $column ); + + return +$this->get()->sum; + } + + /** + * Get the average value in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function avg( $column ) { + $this->selects[] = new RawSQL( 'SELECT AVG(%1s) AS avg', $column ); + + return +$this->get()->avg; + } + + /** + * Returns the minimum value in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function min( $column ) { + $this->selects[] = new RawSQL( 'SELECT MIN(%1s) AS min', $column ); + + return +$this->get()->min; + } + + /** + * Returns the maximum value in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function max( $column ) { + $this->selects[] = new RawSQL( 'SELECT MAX(%1s) AS max', $column ); + + return +$this->get()->max; + } +} diff --git a/src/DB/QueryBuilder/Concerns/CRUD.php b/src/DB/QueryBuilder/Concerns/CRUD.php new file mode 100644 index 0000000..4ca1fc0 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/CRUD.php @@ -0,0 +1,115 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\DB; + +/** + * @since 2.19.0 + */ +trait CRUD { + /** + * @see https://developer.wordpress.org/reference/classes/wpdb/insert/ + * + * @since 2.19.0 + * + * @param array|string $format + * + * @param array $data + * @return false|int + * + */ + public function insert( $data, $format = null ) { + return DB::insert( + $this->getTable(), + $data, + $format + ); + } + + /** + * @see https://developer.wordpress.org/reference/classes/wpdb/update/ + * + * @since 2.19.0 + * + * @param null $format + * + * @param array $data + * @return false|int + * + */ + public function update( $data, $format = null ) { + return DB::update( + $this->getTable(), + $data, + $this->getWhere(), + $format, + null + ); + } + + /** + * @since 2.19.0 + * + * @return false|int + * + * @see https://developer.wordpress.org/reference/classes/wpdb/delete/ + */ + public function delete() { + return DB::delete( + $this->getTable(), + $this->getWhere(), + null + ); + } + + /** + * Get results + * + * @since 2.19.0 + * + * @param string ARRAY_A|ARRAY_N|OBJECT|OBJECT_K $output + * + * @return array|object|null + */ + public function getAll( $output = OBJECT ) { + return DB::get_results( $this->getSQL(), $output ); + } + + /** + * Get row + * + * @since 2.19.0 + * + * @param string ARRAY_A|ARRAY_N|OBJECT|OBJECT_K $output + * + * @return array|object|null + */ + public function get( $output = OBJECT ) { + return DB::get_row( $this->getSQL(), $output ); + } + + /** + * @since 2.19.0 + * + * @return string + */ + private function getTable() { + return $this->froms[0]->table; + } + + /** + * @since 2.19.0 + * + * @return array[] + */ + private function getWhere() { + $wheres = []; + + foreach ( $this->wheres as $where ) { + $wheres[ $where->column ] = $where->value; + } + + return $wheres; + } +} diff --git a/src/DB/QueryBuilder/Concerns/FromClause.php b/src/DB/QueryBuilder/Concerns/FromClause.php new file mode 100644 index 0000000..c3eb80d --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/FromClause.php @@ -0,0 +1,55 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\Clauses\From; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; + +/** + * @since 2.19.0 + */ +trait FromClause { + /** + * @var From[] + */ + protected $froms = []; + + /** + * @param string|RawSQL $table + * @param string|null $alias + * + * @return $this + */ + public function from( $table, $alias = null ) { + $this->froms[] = new From( $table, $alias ); + + return $this; + } + + /** + * @return array|string[] + */ + protected function getFromSQL() { + if ( empty( $this->froms ) ) { + return []; + } + + return [ + 'FROM ' . implode( + ', ', + array_map( function ( From $from ) { + if ( $from->alias ) { + return DB::prepare( + '%1s AS %2s', + $from->table, + $from->alias + ); + } + + return DB::prepare( '%1s', $from->table ); + }, $this->froms ) + ) + ]; + } +} diff --git a/src/DB/QueryBuilder/Concerns/GroupByStatement.php b/src/DB/QueryBuilder/Concerns/GroupByStatement.php new file mode 100644 index 0000000..e0293b6 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/GroupByStatement.php @@ -0,0 +1,32 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\DB; + +/** + * @since 2.19.0 + */ +trait GroupByStatement { + /** + * @var string + */ + protected $groupByColumns = []; + + /** + * @return $this + */ + public function groupBy( $tableColumn ) { + if ( ! in_array( $tableColumn, $this->groupByColumns, true ) ) { + $this->groupByColumns[] = DB::prepare( '%1s', $tableColumn ); + } + + return $this; + } + + protected function getGroupBySQL() { + return ! empty( $this->groupByColumns ) + ? [ 'GROUP BY ' . implode( ',', $this->groupByColumns ) ] + : []; + } +} diff --git a/src/DB/QueryBuilder/Concerns/HavingClause.php b/src/DB/QueryBuilder/Concerns/HavingClause.php new file mode 100644 index 0000000..ad03695 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/HavingClause.php @@ -0,0 +1,296 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\Clauses\Having; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; +use StellarWP\DB\QueryBuilder\Types\Math; +use StellarWP\DB\QueryBuilder\Types\Operator; + +/** + * @since 2.19.0 + */ +trait HavingClause { + /** + * @var Having[]|RawSQL[] + */ + protected $havings = []; + + /** + * @var bool + */ + private $includeHavingKeyword = true; + + /** + * @param string $column + * @param string $comparisonOperator + * @param string $value + * @param null|string $mathFunction \StellarWP\DB\QueryBuilder\Types\Math + * + * @return $this + * + */ + public function having( $column, $comparisonOperator, $value, $mathFunction = null ) { + $this->havings[] = new Having( + $column, + $comparisonOperator, + $value, + empty( $this->havings ) ? null : Operator::_AND, + $mathFunction + ); + + return $this; + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string $value + * @param null|string $mathFunction \StellarWP\DB\QueryBuilder\Types\Math + * + * @return $this + */ + public function orHaving( $column, $comparisonOperator, $value, $mathFunction = null ) { + $this->havings[] = new Having( + $column, + $comparisonOperator, + $value, + empty( $this->havings ) ? null : Operator::_OR, + $mathFunction + ); + + return $this; + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingCount( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::COUNT + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingCount( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::COUNT + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingMin( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::MIN + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingMin( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::MIN + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingMax( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::MAX + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingMax( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::MAX + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingAvg( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::AVG + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingAvg( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::AVG + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingSum( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::SUM + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingSum( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::SUM + ); + } + + /** + * Add raw SQL HAVING clause + * + * @param string $sql + * @param ...$args + * + * @return $this + */ + public function havingRaw( $sql, ...$args ) { + $this->havings[] = new RawSQL( $sql, $args ); + + return $this; + } + + /** + * @return string[] + */ + protected function getHavingSQL() { + if ( empty( $this->havings ) ) { + return []; + } + + $havings = []; + + foreach ( $this->havings as $i => $having ) { + if ( $having instanceof RawSQL ) { + if ( $i === 0 ) { + // If the first element is an instance of RawSQL + // then we don't need the starting HAVING keyword because we assume that the dev will include that in RawSQL + $this->includeHavingKeyword = false; + } + $havings[] = $having->sql; + continue; + } + + $havings[] = $this->buildHavingSQL( $having ); + } + + if ( $this->includeHavingKeyword ) { + return array_merge( ['HAVING'], $havings ); + } + + return $havings; + } + + /** + * @param Having $having + * + * @return string + */ + private function buildHavingSQL( Having $having ) { + if ( $having->mathFunction ) { + return DB::prepare( + "%1s %2s(%3s) %4s %s", + $having->logicalOperator, + $having->mathFunction, + $having->column, + $having->comparisonOperator, + $having->value + ); + } + + return DB::prepare( + "%1s %s %3s %s", + $having->logicalOperator, + $having->column, + $having->comparisonOperator, + $having->value + ); + } +} diff --git a/src/DB/QueryBuilder/Concerns/JoinClause.php b/src/DB/QueryBuilder/Concerns/JoinClause.php new file mode 100644 index 0000000..d6913c6 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/JoinClause.php @@ -0,0 +1,160 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use Closure; +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\JoinQueryBuilder; +use StellarWP\DB\QueryBuilder\Clauses\Join; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; + +/** + * @since 2.19.0 + */ +trait JoinClause { + + /** + * @var Closure[]|RawSQL[] + */ + protected $joins = []; + + /** + * Method used to build advanced JOIN queries, Check README.md for more info. + * If you need to perform only simple JOINs with only one JOIN condition, then you don't need this method. + * + * @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\JoinQueryBuilder instance + * + * @return $this + */ + public function join( $callback ) { + $this->joins[] = $callback; + + return $this; + } + + /** + * @param string|RawSQL $table + * @param string $column1 + * @param string $column2 + * @param string|null $alias + * + * @return $this + */ + public function leftJoin( $table, $column1, $column2, $alias = null ) { + $this->join( + function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) { + $builder + ->leftJoin( $table, $alias ) + ->on( $column1, $column2 ); + } + ); + + return $this; + } + + /** + * @param string|RawSQL $table + * @param string $column1 + * @param string $column2 + * @param string|null $alias + * + * @return $this + */ + public function innerJoin( $table, $column1, $column2, $alias = null ) { + $this->join( + function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) { + $builder + ->innerJoin( $table, $alias ) + ->on( $column1, $column2 ); + } + ); + + return $this; + } + + /** + * @param string|RawSQL $table + * @param string $column1 + * @param string $column2 + * @param string|null $alias + * + * @return $this + */ + public function rightJoin( $table, $column1, $column2, $alias = null ) { + $this->join( + function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) { + $builder + ->rightJoin( $table, $alias ) + ->on( $column1, $column2 ); + } + ); + + return $this; + } + + + /** + * Add raw SQL JOIN clause + * + * @param string $sql + * @param ...$args + * + * @return $this; + */ + public function joinRaw( $sql, ...$args ) { + $this->joins[] = new RawSQL( $sql, $args ); + + return $this; + } + + + /** + * @return string[] + */ + protected function getJoinSQL() { + return array_map(function ( $callback ) { + if ( $callback instanceof RawSQL ) { + return $callback->sql; + } + + $builder = new JoinQueryBuilder(); + + call_user_func( $callback, $builder ); + + $joins = array_map( function ( $join ) { + if ( $join instanceof RawSQL ) { + return $join->sql; + } + + if ( $join instanceof Join ) { + if ( $join->alias ) { + return DB::prepare( + '%1s JOIN %2s %3s', + $join->joinType, + $join->table, + $join->alias + ); + } + + return DB::prepare( + '%1s JOIN %2s', + $join->joinType, + $join->table + ); + } + + // JoinCondition + return DB::prepare( + $join->quote + ? ' %1s %2s = %s' + : ' %1s %2s = %3s', + $join->logicalOperator, + $join->column1, + $join->column2 + ); + }, $builder->getDefinedJoins() ); + + return implode( ' ', $joins ); + }, $this->joins ); + } +} diff --git a/src/DB/QueryBuilder/Concerns/LimitStatement.php b/src/DB/QueryBuilder/Concerns/LimitStatement.php new file mode 100644 index 0000000..44fda3c --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/LimitStatement.php @@ -0,0 +1,30 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +/** + * @since 2.19.0 + */ +trait LimitStatement { + /** + * @var int + */ + protected $limit; + + /** + * @param int $limit + * + * @return $this + */ + public function limit( $limit ) { + $this->limit = (int) $limit; + + return $this; + } + + protected function getLimitSQL() { + return $this->limit + ? [ "LIMIT {$this->limit}" ] + : []; + } +} diff --git a/src/DB/QueryBuilder/Concerns/MetaQuery.php b/src/DB/QueryBuilder/Concerns/MetaQuery.php new file mode 100644 index 0000000..2f88b8b --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/MetaQuery.php @@ -0,0 +1,135 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\QueryBuilder\Clauses\MetaTable; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; +use StellarWP\DB\QueryBuilder\JoinQueryBuilder; +use StellarWP\DB\QueryBuilder\QueryBuilder; + +/** + * @since 2.19.0 + */ +trait MetaQuery { + + /** + * @var MetaTable[] + */ + private $metaTablesConfigs = []; + + /** + * @var string + */ + private $defaultMetaKeyColumn = 'meta_key'; + + /** + * @var string + */ + private $defaultMetaValueColumn = 'meta_value'; + + /** + * @param string|RawSQL $table + * @param string $metaKeyColumn + * @param string $metaValueColumn + * + * @return $this + */ + public function configureMetaTable( $table, $metaKeyColumn, $metaValueColumn ) { + $this->metaTablesConfigs[] = new MetaTable( + $table, + $metaKeyColumn, + $metaValueColumn + ); + + return $this; + } + + /** + * @param string|RawSQL $table + * + * @return MetaTable + */ + protected function getMetaTable( $table ) { + $tableName = QueryBuilder::prefixTable( $table ); + + foreach ( $this->metaTablesConfigs as $metaTable ) { + if ( $metaTable->tableName === $tableName ) { + return $metaTable; + } + } + + return new MetaTable( + $table, + $this->defaultMetaKeyColumn, + $this->defaultMetaValueColumn + ); + } + + /** + * Select meta columns + * + * @since 2.21.1 optimize group concat functionality + * @since 2.19.6 add group concat functionality + * @since 2.19.0 + * + * @param string|RawSQL $table + * @param string $foreignKey + * @param string $primaryKey + * @param array $columns + * + * @return $this + */ + public function attachMeta( $table, $foreignKey, $primaryKey, ...$columns ) { + $metaTable = $this->getMetaTable( $table ); + + foreach ( $columns as $i => $definition ) { + if ( is_array( $definition ) ) { + list( $column, $columnAlias, $concat ) = array_pad( $definition, 3, false ); + } else { + $column = $definition; + $columnAlias = $concat = false; + } + + // Set dynamic alias + $tableAlias = sprintf('%s_%s_%d', ( $table instanceof RawSQL ) ? $table->sql : $table, 'attach_meta', $i); + + // Check if we have meta columns that dev wants to group concat + if ( $concat ) { + /** + * Include foreign key to prevent errors if sql_mode is only_full_group_by + * + * @see https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html + */ + $this->groupBy( $foreignKey ); + + // Group concat same key values into faux array + // @example key => ["value1", "value2"] + $this->selectRaw( + "CONCAT('[',GROUP_CONCAT(DISTINCT CONCAT('\"',%1s,'\"')),']') AS %2s", + $tableAlias . '.' . $metaTable->valueColumnName, + $columnAlias ?: $column + ); + } else { + $this->select( [ "{$tableAlias}.{$metaTable->valueColumnName}", $columnAlias ?: $column ] ); + } + + $this->join( + function ( JoinQueryBuilder $builder ) use ( + $table, + $foreignKey, + $primaryKey, + $tableAlias, + $column, + $metaTable + ) { + $builder + ->leftJoin( $table, $tableAlias ) + ->on( $foreignKey, "{$tableAlias}.{$primaryKey}" ) + ->andOn( "{$tableAlias}.{$metaTable->keyColumnName}", $column, true ); + } + ); + } + + return $this; + } +} diff --git a/src/DB/QueryBuilder/Concerns/OffsetStatement.php b/src/DB/QueryBuilder/Concerns/OffsetStatement.php new file mode 100644 index 0000000..97c53f9 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/OffsetStatement.php @@ -0,0 +1,30 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +/** + * @since 2.19.0 + */ +trait OffsetStatement { + /** + * @var int + */ + protected $offset; + + /** + * @param int $offset + * + * @return $this + */ + public function offset( $offset ) { + $this->offset = (int) $offset; + + return $this; + } + + protected function getOffsetSQL() { + return $this->limit && $this->offset + ? [ "OFFSET {$this->offset}" ] + : []; + } +} diff --git a/src/DB/QueryBuilder/Concerns/OrderByStatement.php b/src/DB/QueryBuilder/Concerns/OrderByStatement.php new file mode 100644 index 0000000..a2437c2 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/OrderByStatement.php @@ -0,0 +1,47 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\Clauses\OrderBy; + +/** + * @since 2.19.0 + */ +trait OrderByStatement { + /** + * @var OrderBy[] + */ + protected $orderBys = []; + + /** + * @param string $column + * @param string $direction ASC|DESC + * + * @return $this + */ + public function orderBy( $column, $direction = 'ASC' ) { + $this->orderBys[] = new OrderBy( $column, $direction ); + + return $this; + } + + /** + * @return array|string[] + */ + protected function getOrderBySQL() { + if ( empty( $this->orderBys ) ) { + return []; + } + + $orderBys = implode( + ', ', + array_map( function ( OrderBy $order ) { + return DB::prepare( '%1s %2s', $order->column, $order->direction ); + }, $this->orderBys ) + ); + + + return [ 'ORDER BY ' . $orderBys ]; + } +} diff --git a/src/DB/QueryBuilder/Concerns/SelectStatement.php b/src/DB/QueryBuilder/Concerns/SelectStatement.php new file mode 100644 index 0000000..d9820dd --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/SelectStatement.php @@ -0,0 +1,122 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; +use StellarWP\DB\QueryBuilder\Clauses\Select; + +/** + * @since 2.19.0 + */ +trait SelectStatement { + /** + * @var Select[]|RawSQL[] + */ + protected $selects = []; + + /** + * @var bool + */ + protected $distinct = false; + + /** + * @var bool + */ + private $includeSelectKeyword = true; + + /** + * @param array $columns + * + * @return $this + */ + public function select( ...$columns ) { + $selects = array_map(function ( $select ) { + if ( is_array( $select ) ) { + list( $column, $alias ) = $select; + + return new Select( $column, $alias ); + } + + return new Select( $select ); + }, $columns ); + + $this->selects = array_merge( $this->selects, $selects ); + + return $this; + } + + /** + * Add raw SQL SELECT statement + * + * @param string $sql + * @param ...$args + */ + public function selectRaw( $sql, ...$args ) { + $this->selects[] = new RawSQL( $sql, $args ); + + return $this; + } + + + /** + * Select distinct + * + * @return $this + */ + public function distinct() { + $this->distinct = true; + + return $this; + } + + /** + * @return string[] + */ + protected function getSelectSQL() { + // Select all by default + if ( empty( $this->selects ) ) { + $this->select( '*' ); + } + + $selects = []; + + foreach ( $this->selects as $i => $select ) { + if ( $select instanceof RawSQL ) { + if ( $i === 0 ) { + // If the first element is an instance of RawSQL + // then we don't need the starting SELECT keyword because we assume that the dev will include that in RawSQL + $this->includeSelectKeyword = false; + } + $selects[] = $select->sql; + continue; + } + + if ( $select->alias ) { + $selects[] = DB::prepare( + '%1s AS %2s', + $select->column, + $select->alias + ); + + continue; + } + + $selects[] = DB::prepare( '%1s', $select->column ); + } + + $selectStatements = implode( ', ', $selects ); + + if ( $this->includeSelectKeyword ) { + $keyword = 'SELECT '; + + if ( $this->distinct ) { + $keyword .= 'DISTINCT '; + } + + return [ $keyword . $selectStatements ]; + } + + return [ $selectStatements ]; + } +} diff --git a/src/DB/QueryBuilder/Concerns/TablePrefix.php b/src/DB/QueryBuilder/Concerns/TablePrefix.php new file mode 100644 index 0000000..458f2f2 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/TablePrefix.php @@ -0,0 +1,35 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; + +/** + * @since 2.19.0 + */ +trait TablePrefix { + /** + * @param string $table + * + * @return string + */ + public static function prefixTable( $table ) { + global $wpdb; + + // Shared tables in multisite environment + $sharedTables = [ + 'users' => $wpdb->users, + 'usermeta' => $wpdb->usermeta, + ]; + + if ( $table instanceof RawSQL ) { + return $table->sql; + } + + if ( array_key_exists( $table, $sharedTables ) ) { + return $sharedTables[ $table ]; + } + + return $wpdb->prefix . $table; + } +} diff --git a/src/DB/QueryBuilder/Concerns/UnionOperator.php b/src/DB/QueryBuilder/Concerns/UnionOperator.php new file mode 100644 index 0000000..6b0d364 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/UnionOperator.php @@ -0,0 +1,55 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use StellarWP\DB\QueryBuilder\Clauses\Union; +use StellarWP\DB\QueryBuilder\QueryBuilder; + +/** + * @since 2.19.0 + */ +trait UnionOperator { + /** + * @var int + */ + protected $unions = []; + + /** + * @param QueryBuilder $union + * + * @return $this + */ + public function union( ...$union ) { + $this->unions = array_map( function ( QueryBuilder $builder ) { + return new Union( $builder ); + }, $union ); + + return $this; + } + + /** + * @param QueryBuilder $union + * + * @return $this + */ + public function unionAll( ...$union ) { + $this->unions = array_map( function ( QueryBuilder $builder ) { + return new Union( $builder, true ); + }, $union ); + + return $this; + } + + /** + * @return array|string[] + */ + protected function getUnionSQL() { + if ( empty( $this->unions ) ) { + return []; + } + + return array_map( function ( Union $union ) { + return ( $union->all ? 'UNION ALL ' : 'UNION ' ) . $union->builder->getSQL(); + }, $this->unions ); + } +} diff --git a/src/DB/QueryBuilder/Concerns/WhereClause.php b/src/DB/QueryBuilder/Concerns/WhereClause.php new file mode 100644 index 0000000..ab947da --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/WhereClause.php @@ -0,0 +1,488 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Concerns; + +use Closure; +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; +use StellarWP\DB\QueryBuilder\Clauses\Where; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\QueryBuilder\Types\Operator; +use StellarWP\DB\QueryBuilder\WhereQueryBuilder; + + +/** + * @since 2.19.0 + */ +trait WhereClause { + + /** + * @var Where[]|RawSQL[]|string[] + */ + protected $wheres = []; + + + /** + * @var bool + */ + private $includeWhereKeyword = true; + + /** + * @param string|Closure|null $column The Closure will receive a StellarWP\DB\QueryBuilder\WhereQueryBuilder instance + * @param string|Closure|array|null $value The Closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * @param string $comparisonOperator + * @param string $logicalOperator + * + * @return $this + */ + private function setWhere( $column, $value, $comparisonOperator, $logicalOperator ) { + // If the columns is a Closure instance, we will assume the developer + // wants to begin a nested where statement which is wrapped in parentheses. + if ( $column instanceof Closure && is_null( $value ) ) { + $builder = new WhereQueryBuilder(); + call_user_func( $column, $builder ); + + // Since this is a nested where statement, we have to remove the starting WHERE keyword + // which is the first returned array element from the getWhereSQL method + $wheres = $builder->getSQL(); + array_shift( $wheres ); + + $this->wheres[] = sprintf( + "%s (%s)", + empty( $this->wheres ) ? null : $logicalOperator, + implode( ' ', $wheres ) + ); + } // If the value is a Closure instance, we will assume the developer is performing an entire sub-select within the query + elseif ( $value instanceof Closure ) { + $builder = new QueryBuilder(); + call_user_func( $value, $builder ); + + $this->wheres[] = sprintf( + "%s %s %s (%s)", + empty( $this->wheres ) ? null : $logicalOperator, + $column, + $comparisonOperator, + $builder->getSQL() + ); + } // Standard WHERE clause + else { + $this->wheres[] = new Where( + $column, + $value, + $comparisonOperator, + empty( $this->wheres ) ? null : $logicalOperator + ); + } + + return $this; + } + + /** + * @param string|Closure $column The closure will receive a StellarWP\DB\QueryBuilder\WhereQueryBuilder instance + * @param string|Closure|array|null $value The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * @param string $comparisonOperator + * + * @return $this + */ + public function where( $column, $value = null, $comparisonOperator = '=' ) { + return $this->setWhere( + $column, + $value, + $comparisonOperator, + Operator::_AND + ); + } + + /** + * @param string|Closure $column + * @param string|Closure|array|null $value + * @param string $comparisonOperator + * + * @return $this + */ + public function orWhere( $column, $value = null, $comparisonOperator = '=' ) { + return $this->setWhere( + $column, + $value, + $comparisonOperator, + Operator::_OR + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function whereIn( $column, $value ) { + return $this->where( + $column, + $value, + Operator::IN + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function orWhereIn( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::IN + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function whereNotIn( $column, $value ) { + return $this->where( + $column, + $value, + Operator::NOTIN + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function orWhereNotIn( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::NOTIN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function whereBetween( $column, $min, $max ) { + return $this->where( + $column, + [ $min, $max ], + Operator::BETWEEN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function whereNotBetween( $column, $min, $max ) { + return $this->where( + $column, + [ $min, $max ], + Operator::NOTBETWEEN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function orWhereBetween( $column, $min, $max ) { + return $this->orWhere( + $column, + [ $min, $max ], + Operator::BETWEEN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function orWhereNotBetween( $column, $min, $max ) { + return $this->orWhere( + $column, + [ $min, $max ], + Operator::NOTBETWEEN + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function whereLike( $column, $value ) { + return $this->where( + $column, + $value, + Operator::LIKE + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function whereNotLike( $column, $value ) { + return $this->where( + $column, + $value, + Operator::NOTLIKE + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function orWhereLike( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::LIKE + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function orWhereNotLike( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::NOTLIKE + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function whereIsNull( $column ) { + return $this->where( + $column, + null, + Operator::ISNULL + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function orWhereIsNull( $column ) { + return $this->orWhere( + $column, + null, + Operator::ISNULL + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function whereIsNotNull( $column ) { + return $this->where( + $column, + null, + Operator::NOTNULL + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function orWhereIsNotNull( $column ) { + return $this->orWhere( + $column, + null, + Operator::NOTNULL + ); + } + + + /** + * @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * + * @return QueryBuilder|WhereQueryBuilder + */ + public function whereExists( $callback ) { + return $this->where( + null, + $callback, + Operator::EXISTS + ); + } + + /** + * @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * + * @return QueryBuilder|WhereQueryBuilder + */ + public function whereNotExists( $callback ) { + return $this->where( + null, + $callback, + Operator::NOTEXISTS + ); + } + + /** + * Add raw SQL WHERE clause + * + * @param $sql + * @param ...$args + * + * @return $this + */ + public function whereRaw( $sql, ...$args ) { + $this->wheres[] = new RawSQL( $sql, $args ); + + return $this; + } + + /** + * @return string[] + */ + protected function getWhereSQL() { + // Bailout + if ( empty( $this->wheres ) ) { + return []; + } + + $wheres = []; + + foreach ( $this->wheres as $i => $where ) { + if ( $where instanceof RawSQL ) { + if ( $i === 0 ) { + // If the first element is an instance of RawSQL + // then we don't need the starting WHERE keyword because we assume that the dev will include that in RawSQL + $this->includeWhereKeyword = false; + } + $wheres[] = $where->sql; + continue; + } + + if ( $where instanceof Where ) { + $wheres[] = $this->buildWhereSQL( $where ); + continue; + } + + // If the variable $where is not an instance of the Where class + // it means the SQL is already generated by the Query Builder, so we just return that + $wheres[] = $where; + } + + + if ( $this->includeWhereKeyword ) { + return array_merge( ['WHERE'], $wheres ); + } + + return $wheres; + } + + /** + * @param Where $where + * + * @return string + */ + private function buildWhereSQL( Where $where ) { + switch ( $where->comparisonOperator ) { + // Handle membership conditions + case Operator::IN: + case Operator::NOTIN: + + return DB::prepare( + "%1s %2s %3s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator + ) . ' (' . implode( + ',', + array_map( function ( $where ) { + return DB::prepare( '%s', $where ); + }, $where->value ) + ) . ')'; + + // Handle BETWEEN conditions + case Operator::BETWEEN: + case Operator::NOTBETWEEN: + list( $min, $max ) = $where->value; + + return DB::prepare( + "%1s %2s %3s %s AND %s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator, + $min, + $max + ); + + // Handle LIKE conditions + case Operator::LIKE: + case Operator::NOTLIKE: + return DB::prepare( + "%1s %2s %3s %s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator, + strpos( $where->value, '%' ) !== false + ? $where->value + : sprintf( '%%%s%%', $where->value ) + ); + + // Handle NULL conditions + case Operator::ISNULL: + case Operator::NOTNULL: + return DB::prepare( + "%1s %2s %3s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator + ); + + // Standard WHERE clause + default: + return DB::prepare( + "%1s %2s %3s %s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator, + $where->value + ); + } + } +} diff --git a/src/DB/QueryBuilder/JoinQueryBuilder.php b/src/DB/QueryBuilder/JoinQueryBuilder.php new file mode 100644 index 0000000..81c8c9c --- /dev/null +++ b/src/DB/QueryBuilder/JoinQueryBuilder.php @@ -0,0 +1,166 @@ +<?php + +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\QueryBuilder\Clauses\Join; +use StellarWP\DB\QueryBuilder\Clauses\JoinCondition; +use StellarWP\DB\QueryBuilder\Clauses\RawSQL; +use StellarWP\DB\QueryBuilder\Types\JoinType; +use StellarWP\DB\QueryBuilder\Types\Operator; + +/** + * @since 2.19.0 + */ +class JoinQueryBuilder { + /** + * @var Join[]|JoinCondition[]|RawSQL[] + */ + private $joins = []; + + /** + * @param string|RawSQL $table + * @param null|string $alias + * + * @return $this + */ + public function leftJoin( $table, $alias = null ) { + return $this->join( + JoinType::LEFT, + $table, + $alias + ); + } + + /** + * @param string|RawSQL $table + * @param null|string $alias + * + * @return $this + */ + public function rightJoin( $table, $alias = null ) { + return $this->join( + JoinType::RIGHT, + $table, + $alias + ); + } + + /** + * @param string|RawSQL $table + * @param null|string $alias + * + * @return $this + */ + public function innerJoin( $table, $alias = null ) { + return $this->join( + JoinType::INNER, + $table, + $alias + ); + } + + /** + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + public function on( $column1, $column2, $quote = false ) { + return $this->joinCondition( + Operator::ON, + $column1, + $column2, + $quote + ); + } + + /** + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + public function andOn( $column1, $column2, $quote = null ) { + return $this->joinCondition( + Operator::_AND, + $column1, + $column2, + $quote + ); + } + + /** + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + public function orOn( $column1, $column2, $quote = null ) { + return $this->joinCondition( + Operator::_OR, + $column1, + $column2, + $quote + ); + } + + /** + * Add raw SQL JOIN clause + * + * @param string $sql + * @param ...$args + */ + public function joinRaw( $sql, ...$args ) { + $this->joins[] = new RawSQL( $sql, $args ); + } + + /** + * Add Join + * + * @param string $joinType + * @param string|RawSQL $table + * @param string $alias + * + * @return $this + */ + private function join( $joinType, $table, $alias ) { + $this->joins[] = new Join( + $joinType, + $table, + $alias + ); + + return $this; + } + + /** + * Add JoinCondition + * + * @param string $operator + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + private function joinCondition( $operator, $column1, $column2, $quote ) { + $this->joins[] = new JoinCondition( + $operator, + $column1, + $column2, + $quote + ); + + return $this; + } + + /** + * @return Join[]|JoinCondition[] + */ + public function getDefinedJoins() { + return $this->joins; + } +} diff --git a/src/DB/QueryBuilder/QueryBuilder.php b/src/DB/QueryBuilder/QueryBuilder.php new file mode 100644 index 0000000..89e9b84 --- /dev/null +++ b/src/DB/QueryBuilder/QueryBuilder.php @@ -0,0 +1,63 @@ +<?php + +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\QueryBuilder\Concerns\Aggregate; +use StellarWP\DB\QueryBuilder\Concerns\CRUD; +use StellarWP\DB\QueryBuilder\Concerns\FromClause; +use StellarWP\DB\QueryBuilder\Concerns\GroupByStatement; +use StellarWP\DB\QueryBuilder\Concerns\HavingClause; +use StellarWP\DB\QueryBuilder\Concerns\JoinClause; +use StellarWP\DB\QueryBuilder\Concerns\LimitStatement; +use StellarWP\DB\QueryBuilder\Concerns\MetaQuery; +use StellarWP\DB\QueryBuilder\Concerns\OffsetStatement; +use StellarWP\DB\QueryBuilder\Concerns\OrderByStatement; +use StellarWP\DB\QueryBuilder\Concerns\SelectStatement; +use StellarWP\DB\QueryBuilder\Concerns\TablePrefix; +use StellarWP\DB\QueryBuilder\Concerns\UnionOperator; +use StellarWP\DB\QueryBuilder\Concerns\WhereClause; + +/** + * @since 2.19.0 + */ +class QueryBuilder { + use Aggregate; + use CRUD; + use FromClause; + use GroupByStatement; + use HavingClause; + use JoinClause; + use LimitStatement; + use MetaQuery; + use OffsetStatement; + use OrderByStatement; + use SelectStatement; + use TablePrefix; + use UnionOperator; + use WhereClause; + + /** + * @return string + */ + public function getSQL() { + $sql = array_merge( + $this->getSelectSQL(), + $this->getFromSQL(), + $this->getJoinSQL(), + $this->getWhereSQL(), + $this->getGroupBySQL(), + $this->getHavingSQL(), + $this->getOrderBySQL(), + $this->getLimitSQL(), + $this->getOffsetSQL(), + $this->getUnionSQL() + ); + + // Trim double spaces added by DB::prepare + return str_replace( + [ ' ', ' ' ], + ' ', + implode( ' ', $sql ) + ); + } +} diff --git a/src/DB/QueryBuilder/Types/JoinType.php b/src/DB/QueryBuilder/Types/JoinType.php new file mode 100644 index 0000000..89d5406 --- /dev/null +++ b/src/DB/QueryBuilder/Types/JoinType.php @@ -0,0 +1,12 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Types; + +/** + * @since 2.19.0 + */ +class JoinType extends Type { + const INNER = 'INNER'; + const LEFT = 'LEFT'; + const RIGHT = 'RIGHT'; +} diff --git a/src/DB/QueryBuilder/Types/Math.php b/src/DB/QueryBuilder/Types/Math.php new file mode 100644 index 0000000..3044207 --- /dev/null +++ b/src/DB/QueryBuilder/Types/Math.php @@ -0,0 +1,14 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Types; + +/** + * @since 2.19.0 + */ +class Math extends Type { + const SUM = 'SUM'; + const MIN = 'MIN'; + const MAX = 'MAX'; + const COUNT = 'COUNT'; + const AVG = 'AVG'; +} diff --git a/src/DB/QueryBuilder/Types/Operator.php b/src/DB/QueryBuilder/Types/Operator.php new file mode 100644 index 0000000..343fdd0 --- /dev/null +++ b/src/DB/QueryBuilder/Types/Operator.php @@ -0,0 +1,24 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Types; + +/** + * @since 2.19.0 + */ +class Operator extends Type { + // _AND and _OR constants are prefixed with underscore to be compatible with PHP 5.6 + const _AND = 'AND'; + const _OR = 'OR'; + const ON = 'ON'; + const BETWEEN = 'BETWEEN'; + const NOTBETWEEN = 'NOT BETWEEN'; + const EXISTS = 'EXISTS'; + const NOTEXISTS = 'NOT EXISTS'; + const IN = 'IN'; + const NOTIN = 'NOT IN'; + const LIKE = 'LIKE'; + const NOTLIKE = 'NOT LIKE'; + const NOT = 'NOT'; + const ISNULL = 'IS NULL'; + const NOTNULL = 'IS NOT NULL'; +} diff --git a/src/DB/QueryBuilder/Types/Type.php b/src/DB/QueryBuilder/Types/Type.php new file mode 100644 index 0000000..a7fb72d --- /dev/null +++ b/src/DB/QueryBuilder/Types/Type.php @@ -0,0 +1,19 @@ +<?php + +namespace StellarWP\DB\QueryBuilder\Types; + +use ReflectionClass; + +/** + * @since 2.19.0 + */ +abstract class Type { + /** + * Get Defined Types + * + * @return array + */ + public static function getTypes() { + return ( new ReflectionClass( static::class ) )->getConstants(); + } +} diff --git a/src/DB/QueryBuilder/WhereQueryBuilder.php b/src/DB/QueryBuilder/WhereQueryBuilder.php new file mode 100644 index 0000000..55ae9c2 --- /dev/null +++ b/src/DB/QueryBuilder/WhereQueryBuilder.php @@ -0,0 +1,19 @@ +<?php + +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\QueryBuilder\Concerns\WhereClause; + +/** + * @since 2.19.0 + */ +class WhereQueryBuilder { + use WhereClause; + + /** + * @return string[] + */ + public function getSQL() { + return $this->getWhereSQL(); + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1 @@ +<?php diff --git a/tests/_support/Helper/DBTestCase.php b/tests/_support/Helper/DBTestCase.php new file mode 100644 index 0000000..6613b7b --- /dev/null +++ b/tests/_support/Helper/DBTestCase.php @@ -0,0 +1,18 @@ +<?php + +namespace StellarWP\DB\Tests; + +use lucatume\DI52\App; +use StellarWP\DB\DB; + +class DBTestCase extends \Codeception\Test\Unit { + protected $backupGlobals = false; + + public function setUp() { + // before + parent::setUp(); + + DB::init(); + } +} + diff --git a/tests/wpunit.suite.dist.yml b/tests/wpunit.suite.dist.yml new file mode 100644 index 0000000..c1b264b --- /dev/null +++ b/tests/wpunit.suite.dist.yml @@ -0,0 +1,17 @@ +# Codeception Test Suite Configuration + +# suite for WordPress functional tests. +# Emulate web requests and make application process them. +class_name: WpunitTester +bootstrap: _bootstrap.php +modules: + enabled: + - WPLoader + - WPQueries + config: + WPLoader: + wpRootFolder: %WP_ROOT_FOLDER% + dbName: %WP_TEST_DB_NAME% + dbHost: %WP_TEST_DB_HOST% + dbUser: %WP_TEST_DB_USER% + dbPassword: %WP_TEST_DB_PASSWORD% diff --git a/tests/wpunit/QueryBuilder/AggregateTest.php b/tests/wpunit/QueryBuilder/AggregateTest.php new file mode 100644 index 0000000..b9a63a6 --- /dev/null +++ b/tests/wpunit/QueryBuilder/AggregateTest.php @@ -0,0 +1,243 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\Tests\DBTestCase; + +/** + * @since 2.19.0 + * + * @covers Aggregate + */ +final class AggregateTest extends DBTestCase +{ + /** + * Truncate posts and give_donationmeta table to avoid duplicate records + * + * @since 2.19.0 + * + * @return void + */ + public function tearDown() { + parent::tearDown(); + + $posts = DB::prefix('posts'); + $donationMeta = DB::prefix('give_donationmeta'); + + DB::query("TRUNCATE TABLE $posts"); + DB::query("TRUNCATE TABLE $donationMeta"); + } + + /** + * + * @since 2.19.0 + * + * @return void + */ + public function testCountShouldReturnTheTotalNumberOfRecords() { + $data = [ + [ + 'post_title' => 'Query Builder Aggregate test 0', + ], + [ + 'post_title' => 'Query Builder Aggregate test 1', + ], + [ + 'post_title' => 'Query Builder Aggregate test 2', + ] + ]; + + foreach ($data as $row) { + DB::table('posts')->insert($row); + } + + $postsCount = DB::table('posts')->count(); + + $this->assertEquals(3, $postsCount); + + $nonExistentPostTypeCount = DB::table('posts') + ->where('post_type', 'dummy') + ->count(); + + $this->assertEquals(0, $nonExistentPostTypeCount); + } + + + /** + * @since 2.19.0 + * + * @return void + */ + public function testCountByColumnShouldReturnTheTotalNumberOfRecords() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => null, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $postsCount = DB::table('give_donationmeta')->count('meta_key'); + + $this->assertEquals(3, $postsCount); + + $postsCountWithNullColumn = DB::table('give_donationmeta')->count('meta_value'); + + $this->assertEquals(2, $postsCountWithNullColumn); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testSumShouldReturnTheSumOfColumns() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $totalSum = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->sum('meta_value'); + + $this->assertEquals(60, $totalSum); + } + + + /** + * @since 2.19.0 + * + * @return void + */ + public function testAvgShouldReturnTheAvgColumnValue() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $avgValue = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->avg('meta_value'); + + $this->assertEquals(20, $avgValue); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testMinShouldReturnTheMinimumColumnValue() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $minValue = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->min('meta_value'); + + $this->assertEquals(10, $minValue); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testMaxShouldReturnTheMaximumColumnValue() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $maxValue = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->max('meta_value'); + + $this->assertEquals(30, $maxValue); + } +} diff --git a/tests/wpunit/QueryBuilder/AttachMetaTest.php b/tests/wpunit/QueryBuilder/AttachMetaTest.php new file mode 100644 index 0000000..509f007 --- /dev/null +++ b/tests/wpunit/QueryBuilder/AttachMetaTest.php @@ -0,0 +1,109 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +/** + * @since 2.19.0 + * + * @covers AttachMeta + */ +final class AttachMetaTest extends DBTestCase +{ + /** + * @since 2.19.0 + */ + public function testAttachMeta() + { + $builder = new QueryBuilder(); + + $builder + ->from(DB::raw('wp_posts'), 'posts') + ->select( + ['posts.ID', 'id'], + ['posts.post_date', 'createdAt'] + ) + ->attachMeta( + DB::raw('wp_give_donationmeta'), + 'posts.ID', + 'donation_id', + ['_give_payment_total', 'amount'] + ) + ->leftJoin(DB::raw('wp_give_donationmeta'), 'posts.ID', 'donationMeta.donation_id', 'donationMeta') + ->where('posts.post_type', 'give_payment') + ->where('posts.post_status', 'give_subscription') + ->where('donationMeta.meta_key', 'subscription_id') + ->where('donationMeta.meta_value', 1) + ->orderBy('posts.post_date', 'DESC'); + + $this->assertContains( + "SELECT posts.ID AS id, posts.post_date AS createdAt, wp_give_donationmeta_attach_meta_0.meta_value AS amount FROM wp_posts AS posts LEFT JOIN wp_give_donationmeta wp_give_donationmeta_attach_meta_0 ON posts.ID = wp_give_donationmeta_attach_meta_0.donation_id AND wp_give_donationmeta_attach_meta_0.meta_key = '_give_payment_total' LEFT JOIN wp_give_donationmeta donationMeta ON posts.ID = donationMeta.donation_id WHERE posts.post_type = 'give_payment' AND posts.post_status = 'give_subscription' AND donationMeta.meta_key = 'subscription_id' AND donationMeta.meta_value = '1' ORDER BY posts.post_date DESC", + $builder->getSQL() + ); + } + + /** + * @since 2.19.6 + */ + public function testAttachMetaGroupConcatQuery() + { + $builder = new QueryBuilder(); + + $builder + ->from(DB::raw('wp_give_donors')) + ->select( + 'id', + 'email', + 'name' + ) + ->attachMeta( + DB::raw('wp_give_donormeta'), + 'id', + 'donor_id', + ['additional_email', 'additionalEmails', true] + ); + + $this->assertContains( + "SELECT id, email, name, CONCAT('[',GROUP_CONCAT(DISTINCT CONCAT('\"',wp_give_donormeta_attach_meta_0.meta_value,'\"')),']') AS additionalEmails FROM wp_give_donors LEFT JOIN wp_give_donormeta wp_give_donormeta_attach_meta_0 ON id = wp_give_donormeta_attach_meta_0.donor_id AND wp_give_donormeta_attach_meta_0.meta_key = 'additional_email' GROUP BY id", + $builder->getSQL() + ); + } + + /** + * @since 2.19.0 + */ + public function testConfigureMeta() + { + $builder = new QueryBuilder(); + + $builder + ->from(DB::raw('wp_posts'), 'posts') + ->select( + ['posts.ID', 'id'], + ['posts.post_date', 'createdAt'] + ) + ->configureMetaTable( + DB::raw('wp_give_donationmeta'), + 'custom_meta_key', + 'custom_meta_value' + ) + ->attachMeta( + DB::raw('wp_give_donationmeta'), + 'posts.ID', + 'donation_id', + ['_give_payment_total', 'amount'] + ) + ->leftJoin(DB::raw('wp_give_donationmeta'), 'posts.ID', 'donationMeta.donation_id', 'donationMeta') + ->where('posts.post_type', 'give_payment') + ->where('posts.post_status', 'give_subscription') + ->where('donationMeta.custom_meta_key', 'subscription_id') + ->where('donationMeta.custom_meta_value', 1); + + $this->assertContains( + "SELECT posts.ID AS id, posts.post_date AS createdAt, wp_give_donationmeta_attach_meta_0.custom_meta_value AS amount FROM wp_posts AS posts LEFT JOIN wp_give_donationmeta wp_give_donationmeta_attach_meta_0 ON posts.ID = wp_give_donationmeta_attach_meta_0.donation_id AND wp_give_donationmeta_attach_meta_0.custom_meta_key = '_give_payment_total' LEFT JOIN wp_give_donationmeta donationMeta ON posts.ID = donationMeta.donation_id WHERE posts.post_type = 'give_payment' AND posts.post_status = 'give_subscription' AND donationMeta.custom_meta_key = 'subscription_id' AND donationMeta.custom_meta_value = '1'", + $builder->getSQL() + ); + } +} diff --git a/tests/wpunit/QueryBuilder/CRUDTest.php b/tests/wpunit/QueryBuilder/CRUDTest.php new file mode 100644 index 0000000..9f19144 --- /dev/null +++ b/tests/wpunit/QueryBuilder/CRUDTest.php @@ -0,0 +1,132 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use Give\Framework\QueryBuilder\Concerns\CRUD; +use StellarWP\DB\Tests\DBTestCase; + +/** + * @since 2.19.0 + * + * @covers CRUD + */ +final class CRUDTest extends DBTestCase +{ + /** + * Truncate posts table to avoid duplicate records + * + * @since 2.19.0 + * + * @return void + */ + public function tearDown() + { + parent::tearDown(); + + $posts = DB::prefix('posts'); + + DB::query("TRUNCATE TABLE $posts"); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testInsertShouldAddRowToDatabase() + { + $data = [ + 'post_title' => 'Query Builder CRUD test', + 'post_type' => 'crud_test', + 'post_content' => 'Hello World!', + ]; + + DB::table('posts')->insert($data); + + $id = DB::last_insert_id(); + + $post = DB::table('posts') + ->select('post_title', 'post_type', 'post_content') + ->where('ID', $id) + ->get(); + + $this->assertEquals($data['post_title'], $post->post_title); + $this->assertEquals($data['post_type'], $post->post_type); + $this->assertEquals($data['post_content'], $post->post_content); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testUpdateShouldUpdateRowValuesInDatabase() + { + $data = [ + 'post_title' => 'Query Builder CRUD test', + 'post_type' => 'crud_test', + 'post_content' => 'Hello World!', + ]; + + DB::table('posts')->insert($data); + + $id = DB::last_insert_id(); + + $updated = [ + 'post_title' => 'Query Builder CRUD test - UPDATED', + 'post_type' => 'crud_test-updated', + 'post_content' => 'Hello World! - UPDATED', + ]; + + DB::table('posts') + ->where('ID', $id) + ->update($updated); + + $post = DB::table('posts') + ->select('ID', 'post_title', 'post_type', 'post_content') + ->where('ID', $id) + ->get(); + + $this->assertEquals($id, $post->ID); + $this->assertEquals($updated['post_title'], $post->post_title); + $this->assertEquals($updated['post_type'], $post->post_type); + $this->assertEquals($updated['post_content'], $post->post_content); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testDeleteShouldDeleteRowInDatabase() + { + $data = [ + 'post_title' => 'Query Builder CRUD test', + 'post_type' => 'crud_test', + 'post_content' => 'Hello World!', + ]; + + DB::table('posts')->insert($data); + + $id = DB::last_insert_id(); + + $post = DB::table('posts') + ->where('ID', $id) + ->get(); + + $this->assertNotNull($post); + + DB::table('posts') + ->where('ID', $id) + ->delete(); + + $post = DB::table('posts') + ->where('ID', $id) + ->get(); + + $this->assertNull($post); + } + +} diff --git a/tests/wpunit/QueryBuilder/FromTest.php b/tests/wpunit/QueryBuilder/FromTest.php new file mode 100644 index 0000000..283f3c7 --- /dev/null +++ b/tests/wpunit/QueryBuilder/FromTest.php @@ -0,0 +1,52 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +final class FromTest extends DBTestCase +{ + + public function testFrom() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')); + + $this->assertContains( + 'SELECT * FROM posts', + $builder->getSQL() + ); + } + + + public function testFromAlias() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts'), 'donations'); + + $this->assertContains( + 'SELECT * FROM posts AS donations', + $builder->getSQL() + ); + } + + public function testMultipleFrom() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->from(DB::raw('postmeta')); + + $this->assertContains( + 'SELECT * FROM posts, postmeta', + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/GroupByTest.php b/tests/wpunit/QueryBuilder/GroupByTest.php new file mode 100644 index 0000000..c153ba8 --- /dev/null +++ b/tests/wpunit/QueryBuilder/GroupByTest.php @@ -0,0 +1,25 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +final class GroupByTest extends DBTestCase +{ + public function testGroupBy() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->groupBy('ID'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' GROUP BY ID", + $builder->getSQL() + ); + } +} diff --git a/tests/wpunit/QueryBuilder/HavingTest.php b/tests/wpunit/QueryBuilder/HavingTest.php new file mode 100644 index 0000000..60be1f8 --- /dev/null +++ b/tests/wpunit/QueryBuilder/HavingTest.php @@ -0,0 +1,259 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use InvalidArgumentException; +use StellarWP\DB\Tests\DBTestCase; + +final class HavingTest extends DBTestCase +{ + + public function testHaving() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('id') + ->having('id', '>', 10); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY id HAVING 'id' > '10'", + $builder->getSQL() + ); + } + + public function testHavingCount() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingCount('ID', '>', 10); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING COUNT( ID) > '10'", + $builder->getSQL() + ); + } + + + public function testHavingSum() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingSum('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING SUM(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testHavingSumAnd() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingSum('post_count', '>', 1000) + ->havingSum('post_count', '<', 5000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING SUM(post_count) > '1000' AND SUM(post_count) < '5000'", + $builder->getSQL() + ); + } + + + public function testOrHaving() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingSum('post_count', '>', 1000) + ->orHavingSum('post_count', '<', 100); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING SUM(post_count) > '1000' OR SUM(post_count) < '100'", + $builder->getSQL() + ); + } + + + public function testHavingMin() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMin('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MIN(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testOrHavingMin() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMin('post_count', '>', 1000) + ->orHavingMin('post_count', '<', 100); // Doesn't make sense, but hey... + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MIN(post_count) > '1000' OR MIN(post_count) < '100'", + $builder->getSQL() + ); + } + + + public function testHavingMax() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMax('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MAX(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testOrHavingMax() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMax('post_count', '>', 1000) + ->orHavingMax('post_count', '<', 100); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MAX(post_count) > '1000' OR MAX(post_count) < '100'", + $builder->getSQL() + ); + } + + + public function testHavingAvg() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingAvg('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING AVG(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testOrHavingAvg() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingAvg('post_count', '<', 1000) + ->orHavingAvg('post_count', '>', 10000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING AVG(post_count) < '1000' OR AVG(post_count) > '10000'", + $builder->getSQL() + ); + } + + public function testHavingRaw() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->groupBy('id') + ->havingRaw('HAVING COUNT(id) > %d', 1000); + + $this->assertContains( + "SELECT ID FROM give_donations GROUP BY id HAVING COUNT(id) > 1000", + $builder->getSQL() + ); + } + + + public function testHavingRawChain() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->groupBy('id') + ->havingRaw('HAVING COUNT(id) > %d', 1000) + ->orHavingAvg('id', '<', 400); + + $this->assertContains( + "SELECT ID FROM give_donations GROUP BY id HAVING COUNT(id) > 1000 OR AVG( id) < '400'", + $builder->getSQL() + ); + } + + + public function testReturnExceptionWhenBadComparisonArgumentIsUsed() { + $this->expectException( InvalidArgumentException::class ); + + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->groupBy('ID') + ->having('id', 'EQUALS TO', 10); + } +} diff --git a/tests/wpunit/QueryBuilder/JoinTest.php b/tests/wpunit/QueryBuilder/JoinTest.php new file mode 100644 index 0000000..2811e9f --- /dev/null +++ b/tests/wpunit/QueryBuilder/JoinTest.php @@ -0,0 +1,116 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use Give\Framework\QueryBuilder\JoinQueryBuilder; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +final class JoinTest extends DBTestCase +{ + + public function testLeftJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->leftJoin(DB::raw('give_donationmeta'), 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + + public function testRightJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->rightJoin(DB::raw('give_donationmeta'), 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable RIGHT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + public function testInnerJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->innerJoin(DB::raw('give_donationmeta'), 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable INNER JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + + public function testJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->join(function (JoinQueryBuilder $builder) { + $builder + ->leftJoin(DB::raw('give_donationmeta'), 'metaTable') + ->on('donationsTable.ID', 'metaTable.donation_id'); + }); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + + public function testAdvancedJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->join(function (JoinQueryBuilder $builder) { + $builder + ->leftJoin(DB::raw('give_donationmeta'), 'metaTable') + ->on('donationsTable.ID', 'metaTable.donation_id') + ->andOn('metaTable.meta_key', '_give_donor_billing_first_name', true) + ->orOn('metaTable.meta_key', '_give_donor_billing_last_name', true); + }); + + $this->assertContains( + "SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id AND metaTable.meta_key = '_give_donor_billing_first_name' OR metaTable.meta_key = '_give_donor_billing_last_name'", + $builder->getSQL() + ); + } + + + public function testJoinRaw() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->joinRaw('LEFT JOIN posts ON post_id = give_donations.id'); + + $this->assertContains( + "SELECT ID FROM give_donations LEFT JOIN posts ON post_id = give_donations.id", + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/LimitAndOffsetTest.php b/tests/wpunit/QueryBuilder/LimitAndOffsetTest.php new file mode 100644 index 0000000..9f431cc --- /dev/null +++ b/tests/wpunit/QueryBuilder/LimitAndOffsetTest.php @@ -0,0 +1,44 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +final class LimitAndOffsetTest extends DBTestCase +{ + public function testLimit() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->limit(5); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' LIMIT 5", + $builder->getSQL() + ); + } + + + public function testOffset() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->limit(5) + ->offset(10); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' LIMIT 5 OFFSET 10", + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/OrderByTest.php b/tests/wpunit/QueryBuilder/OrderByTest.php new file mode 100644 index 0000000..15f079b --- /dev/null +++ b/tests/wpunit/QueryBuilder/OrderByTest.php @@ -0,0 +1,56 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use InvalidArgumentException; +use StellarWP\DB\Tests\DBTestCase; + +final class OrderByTest extends DBTestCase +{ + public function testOrderByDesc() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orderBy('ID', 'DESC'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' ORDER BY ID DESC", + $builder->getSQL() + ); + } + + + public function testOrderByAsc() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orderBy('ID', 'ASC'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' ORDER BY ID ASC", + $builder->getSQL() + ); + } + + + public function testReturnExceptionWhenBadOrderByDirectionArgumentIsUsed() { + $this->expectException( InvalidArgumentException::class ); + + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orderBy('ID', 'BANANAS'); + } +} diff --git a/tests/wpunit/QueryBuilder/SelectTest.php b/tests/wpunit/QueryBuilder/SelectTest.php new file mode 100644 index 0000000..6735506 --- /dev/null +++ b/tests/wpunit/QueryBuilder/SelectTest.php @@ -0,0 +1,91 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +final class SelectTest extends DBTestCase +{ + + public function testSelect() + { + $builder = new QueryBuilder(); + $builder + ->select('ID') + ->from(DB::raw('posts')); + + $this->assertContains( + 'SELECT ID FROM posts', + $builder->getSQL() + ); + } + + public function testSelectAll() + { + $builder = new QueryBuilder(); + $builder->from(DB::raw('posts')); + + $this->assertContains( + 'SELECT * FROM posts', + $builder->getSQL() + ); + } + + + public function testSelectAlias() + { + $builder = new QueryBuilder(); + $builder + ->select(['ID', 'posts_id']) + ->from(DB::raw('posts')); + + + $this->assertContains( + 'SELECT ID AS posts_id FROM posts', + $builder->getSQL() + ); + } + + public function testSelectDistinct() + { + $builder = new QueryBuilder(); + $builder + ->select('ID', 'post_author') + ->from(DB::raw('posts')) + ->distinct(); + + $this->assertContains( + 'SELECT DISTINCT ID, post_author FROM posts', + $builder->getSQL() + ); + } + + public function testSelectRaw() + { + $builder = new QueryBuilder(); + + $builder->selectRaw('SELECT * FROM posts WHERE post_status = %s', 'give_subscription'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'give_subscription'", + $builder->getSQL() + ); + } + + public function testSelectRawChain() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID', 'post_title') + ->selectRaw('(SELECT COUNT(ID) FROM posts WHERE post_status = %s) as post_count', 'give_subscription') + ->from(DB::raw('posts')); + + $this->assertContains( + "SELECT ID, post_title, (SELECT COUNT(ID) FROM posts WHERE post_status = 'give_subscription') as post_count FROM posts", + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/UnionTest.php b/tests/wpunit/QueryBuilder/UnionTest.php new file mode 100644 index 0000000..0664fb6 --- /dev/null +++ b/tests/wpunit/QueryBuilder/UnionTest.php @@ -0,0 +1,58 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use StellarWP\DB\Tests\DBTestCase; + +final class UnionTest extends DBTestCase +{ + public function testUnion() + { + $builder1 = new QueryBuilder(); + $builder2 = new QueryBuilder(); + + $builder1 + ->select('ID') + ->from(DB::raw('give_donations')); + + $builder2 + ->select('ID') + ->from(DB::raw('give_subscriptions')) + ->where('ID', 100, '>') + ->union($builder1); + + $this->assertContains( + "SELECT ID FROM give_subscriptions WHERE ID > '100' UNION SELECT ID FROM give_donations", + $builder2->getSQL() + ); + } + + + public function testUnionAll() + { + $builder1 = new QueryBuilder(); + $builder2 = new QueryBuilder(); + $builder3 = new QueryBuilder(); + + $builder1 + ->select('ID') + ->from(DB::raw('give_donations')); + + $builder2 + ->select('ID') + ->from(DB::raw('give_subscriptions')) + ->where('ID', 100, '>'); + + $builder3 + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->unionAll($builder1, $builder2); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' UNION ALL SELECT ID FROM give_donations UNION ALL SELECT ID FROM give_subscriptions WHERE ID > '100'", + $builder3->getSQL() + ); + } +} diff --git a/tests/wpunit/QueryBuilder/WhereTest.php b/tests/wpunit/QueryBuilder/WhereTest.php new file mode 100644 index 0000000..cf86534 --- /dev/null +++ b/tests/wpunit/QueryBuilder/WhereTest.php @@ -0,0 +1,495 @@ +<?php +namespace StellarWP\DB\QueryBuilder; + +use StellarWP\DB\DB; +use StellarWP\DB\QueryBuilder\QueryBuilder; +use Give\Framework\QueryBuilder\WhereQueryBuilder; +use InvalidArgumentException; +use StellarWP\DB\Tests\DBTestCase; + +final class WhereTest extends DBTestCase +{ + public function testWhere() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('ID', 5); + + $this->assertContains( + "SELECT * FROM posts WHERE ID = '5'", + $builder->getSQL() + ); + } + + public function testAndWhere() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->where('post_title', 'Donation Form'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' AND post_title = 'Donation Form'", + $builder->getSQL() + ); + } + + public function testOrWhere() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orWhere('post_title', 'Donation Form'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' OR post_title = 'Donation Form'", + $builder->getSQL() + ); + } + + public function testNestedWhere() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->where(function (WhereQueryBuilder $builder) { + $builder + ->where('post_status', 'give_subscription') + ->orWhere('post_status', 'give_donation'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ( post_status = 'give_subscription' OR post_status = 'give_donation')", + $builder->getSQL() + ); + } + + + public function testSubSelectQuery() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->whereIn('ID', function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from(DB::raw('give_donationmeta')) + ->where('meta_key', 'donation_id'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ID IN (SELECT meta_value AS donation_id FROM give_donationmeta WHERE meta_key = 'donation_id')", + $builder->getSQL() + ); + } + + public function testWhereIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->whereIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ID IN ('1','2','3')", + $builder->getSQL() + ); + } + + public function testWhereNotIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->whereNotIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ID NOT IN ('1','2','3')", + $builder->getSQL() + ); + } + + + public function testOrWhereIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->orWhereIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' OR ID IN ('1','2','3')", + $builder->getSQL() + ); + } + + public function testOrWhereNotIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->orWhereNotIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' OR ID NOT IN ('1','2','3')", + $builder->getSQL() + ); + } + + + public function testWhereBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testWhereBetweenDates() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereBetween('post_date', '2021-11-22', '2022-11-22'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_date BETWEEN '2021-11-22' AND '2022-11-22'", + $builder->getSQL() + ); + } + + + public function testWhereNotBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereNotBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID NOT BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testOrWhereBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('ID', 222) + ->orWhereBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID = '222' OR ID BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testOrWhereNotBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('ID', 222) + ->orWhereNotBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID = '222' OR ID NOT BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testWhereLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + public function testWhereLikeWithWildCardPositionLeft() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', '%Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Donation'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testWhereLikeWithWildCardPositionRight() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Donation%'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE 'Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testWhereNotLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereNotLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title NOT LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testOrWhereLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Form') + ->orWhereLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Form%' OR post_title LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testOrWhereNotLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Form') + ->orWhereNotLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Form%' OR post_title NOT LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testWhereIsNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereIsNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent IS NULL", + $builder->getSQL() + ); + } + + + public function testWhereIsNotNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereIsNotNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent IS NOT NULL", + $builder->getSQL() + ); + } + + + public function testOrWhereIsNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5, '>') + ->orWhereIsNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent > '5' OR post_parent IS NULL", + $builder->getSQL() + ); + } + + + public function testOrWhereIsNotNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5, '>') + ->orWhereIsNotNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent > '5' OR post_parent IS NOT NULL", + $builder->getSQL() + ); + } + + + public function testWhereExists() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereExists(function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from(DB::raw('give_donationmeta')) + ->where('meta_key', 'donation_id'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE EXISTS (SELECT meta_value AS donation_id FROM give_donationmeta WHERE meta_key = 'donation_id')", + $builder->getSQL() + ); + } + + + public function testWhereNotExists() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereNotExists(function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from(DB::raw('give_donationmeta')) + ->where('meta_key', 'donation_id'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE NOT EXISTS (SELECT meta_value AS donation_id FROM give_donationmeta WHERE meta_key = 'donation_id')", + $builder->getSQL() + ); + } + + + public function testWhereRaw() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->whereRaw('WHERE post_id = %d AND post_title = %s', 5, 'Donation'); + + $this->assertContains( + "SELECT ID FROM give_donations WHERE post_id = 5 AND post_title = 'Donation'", + $builder->getSQL() + ); + } + + + public function testWhereRawChain() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->whereRaw('WHERE post_id = %d AND post_title = %s', 5, 'Donation') + ->orWhere('post_title', 'Form'); + + $this->assertContains( + "SELECT ID FROM give_donations WHERE post_id = 5 AND post_title = 'Donation' OR post_title = 'Form'", + $builder->getSQL() + ); + } + + + public function testReturnExceptionWhenBadComparisonArgumentIsUsed() { + $this->expectException( InvalidArgumentException::class ); + + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published', 'EQUALS TO'); + } + +} diff --git a/tests/wpunit/_bootstrap.php b/tests/wpunit/_bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/tests/wpunit/_bootstrap.php @@ -0,0 +1 @@ +<?php