diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 0000000..467daf9
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,22 @@
+engines:
+ phpmd:
+ enabled: true
+ checks:
+ Design/TooManyPublicMethods:
+ enabled: true
+ Naming/ShortVariable:
+ enabled: true
+ CleanCode/StaticAccess:
+ enabled: true
+ Controversial/CamelCaseMethodName:
+ enabled: true
+ fixme:
+ enabled: true
+ duplication:
+ enabled: true
+ config:
+ languages:
+ - php:
+ratings:
+ paths:
+ - src/**
diff --git a/.gitignore b/.gitignore
index 18bb066..233f5da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,86 @@
-vendor
composer.phar
-composer.lock
.DS_Store
-.idea
-test.php
-*~
\ No newline at end of file
+*~
+/devel/
+/coverage.clover
+.idea/
+
+### Composer template
+/vendor/
+
+# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# composer.lock
+### Windows template
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/dictionaries
+
+# Sensitive or high-churn files:
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.xml
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+
+# Gradle:
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# CMake
+cmake-build-debug/
+
+# Mongo Explorer plugin:
+.idea/**/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
new file mode 100644
index 0000000..0608e7a
--- /dev/null
+++ b/.scrutinizer.yml
@@ -0,0 +1,13 @@
+build:
+ tests:
+ override:
+ -
+ command: './vendor/bin/phpunit --coverage-clover=coverage.clover'
+ coverage:
+ file: 'coverage.clover'
+ format: 'clover'
+checks:
+ php:
+ code_rating: true
+ duplication: true
+
diff --git a/.travis.yml b/.travis.yml
index aa02544..ee4da66 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,6 @@ sudo: false
language: php
php:
- - 5.4
- - 5.5
- 7.0
before_script:
@@ -12,4 +10,4 @@ before_script:
- php composer.phar install --prefer-source --no-interaction
script:
- - ./vendor/bin/phpunit
\ No newline at end of file
+ - ./vendor/bin/phpunit
diff --git a/README.md b/README.md
index d8595d2..30d0086 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ This library is stable, maintained and are used by many sites, including:
- [BookAndBegin.com](https://bookandbegin.com)
**Requirements:**
-- PHP version 5.5 or higher is required.
+- PHP version 7.0 or higher is required.
#### Feedback and development
@@ -303,6 +303,21 @@ $result = $queryBuilder
### Select
+#### Select from query
+
+You can easily select items from another query by using
+
+```php
+$subQuery = $queryBuilder->table('person');
+$builder = $queryBuilder->table($queryBuilder->subQuery($subQuery))->where('id', '=', 2);
+```
+
+Will produce the following output:
+
+```sql
+SELECT * FROM (SELECT * FROM `person`) WHERE `id` = 2
+```
+
#### Select single field
```php
diff --git a/composer.json b/composer.json
index 0d35415..3734d78 100644
--- a/composer.json
+++ b/composer.json
@@ -34,12 +34,12 @@
}
],
"require": {
- "php": ">=5.5.0",
+ "php": ">=7.0",
"usmanhalalit/viocon": "1.0.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8",
- "mockery/mockery": "0.9.4"
+ "phpunit/phpunit": "^6.0",
+ "mockery/mockery": "^1"
},
"autoload": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..d03cd81
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,1639 @@
+{
+ "_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#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "431825fe68c6a6b27cb373d5aa657c4b",
+ "packages": [
+ {
+ "name": "usmanhalalit/viocon",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/usmanhalalit/viocon.git",
+ "reference": "0878afee16f15355971fb95bf3c6d297aceff35d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/usmanhalalit/viocon/zipball/0878afee16f15355971fb95bf3c6d297aceff35d",
+ "reference": "0878afee16f15355971fb95bf3c6d297aceff35d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Viocon": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Muhammad Usman",
+ "email": "hi@usman.it",
+ "role": "Developer"
+ }
+ ],
+ "description": "A simple and flexible Dependency Injection container for PHP.",
+ "homepage": "https://github.com/usmanhalalit/viocon",
+ "keywords": [
+ "container",
+ "di",
+ "ioc",
+ "test"
+ ],
+ "time": "2013-07-13T19:54:56+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
+ "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "^6.2.3",
+ "squizlabs/php_codesniffer": "^3.0.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "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": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2017-07-22T11:58:36+00:00"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad",
+ "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3|^7.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "1.3.3",
+ "phpunit/phpunit": "~4.0",
+ "satooshi/php-coveralls": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "time": "2016-01-20T08:20:44+00:00"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mockery/mockery.git",
+ "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/1bac8c362b12f522fdd1f1fa3556284c91affa38",
+ "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38",
+ "shasum": ""
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "~2.0",
+ "lib-pcre": ">=7.0",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5.7|~6.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Mockery": "library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "http://blog.astrumfutura.com"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "http://davedevelopment.co.uk"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
+ "homepage": "http://github.com/mockery/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "time": "2017-10-06T16:20:43+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+ "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^4.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "time": "2017-10-19T19:58:43+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": "phpdocumentor/reflection-common",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.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": "2017-09-11T18:02:19+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "4.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "66465776cfc249844bde6d117abff1d22e06c2da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da",
+ "reference": "66465776cfc249844bde6d117abff1d22e06c2da",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "phpdocumentor/reflection-common": "^1.0.0",
+ "phpdocumentor/type-resolver": "^0.4.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "doctrine/instantiator": "~1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.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": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2017-11-27T17:38:31+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "0.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5 || ^7.0",
+ "phpdocumentor/reflection-common": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^5.2||^4.8.24"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.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"
+ }
+ ],
+ "time": "2017-07-14T14:27:02+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "1.7.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
+ "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
+ "sebastian/comparator": "^1.1|^2.0",
+ "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^2.5|^3.2",
+ "phpunit/phpunit": "^4.8.35 || ^5.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "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": "2017-11-24T13:59:53+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "5.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "033ec97498cf530cc1be4199264cad568b19be26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/033ec97498cf530cc1be4199264cad568b19be26",
+ "reference": "033ec97498cf530cc1be4199264cad568b19be26",
+ "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": {
+ "ext-xdebug": "^2.5",
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-xdebug": "^2.5.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.2.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": "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": "2017-11-27T09:00:30+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"
+ ],
+ "time": "2017-11-27T05:48:46+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "6.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "24b708f2fd725bcef1c8153b366043381aa324f2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/24b708f2fd725bcef1c8153b366043381aa324f2",
+ "reference": "24b708f2fd725bcef1c8153b366043381aa324f2",
+ "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.2.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.4",
+ "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": "2017-12-02T05:36:24+00:00"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "5.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "16b50f4167e5e85e81ca8a3dd105d0a5fd32009a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/16b50f4167e5e85e81ca8a3dd105d0a5fd32009a",
+ "reference": "16b50f4167e5e85e81ca8a3dd105d0a5fd32009a",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.5",
+ "php": "^7.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "sebastian/exporter": "^3.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.5"
+ },
+ "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"
+ ],
+ "time": "2017-12-02T05:31:19+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7 || ^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": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "time": "2017-03-04T06:30:41+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "1174d9018191e93cb9d719edec01257fc05f8158"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158",
+ "reference": "1174d9018191e93cb9d719edec01257fc05f8158",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/diff": "^2.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": "2017-11-03T07:16:52+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.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
+ "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^6.0"
+ },
+ "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": "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"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2017-04-03T13:19:02+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.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "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/",
+ "time": "2017-08-03T12:35:26+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be",
+ "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/",
+ "time": "2017-03-29T09:07:27+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "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": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2017-03-03T06:23:57+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": "theseer/tokenizer",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
+ "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^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"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "time": "2017-04-07T12:08:54+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
+ "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6",
+ "sebastian/version": "^1.0.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-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": "2016-11-23T20:04:58+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=7.0"
+ },
+ "platform-dev": []
+}
diff --git a/phpunit.xml b/phpunit.xml
index 164567e..a8be32d 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -15,4 +15,9 @@
tests/Pecee/Pixie/
+
+
+ src
+
+
diff --git a/src/Pecee/Pixie/Connection.php b/src/Pecee/Pixie/Connection.php
index 7e36f22..c3df741 100644
--- a/src/Pecee/Pixie/Connection.php
+++ b/src/Pecee/Pixie/Connection.php
@@ -13,60 +13,58 @@
class Connection
{
+ /**
+ * @var Connection
+ */
+ protected static $storedConnection;
/**
* @var Container
*/
protected $container;
-
/**
* Name of DB adapter (i.e. Mysql, Pgsql, Sqlite)
+ *
* @var string
*/
protected $adapter;
-
/**
* @var array
*/
protected $adapterConfig;
-
/**
* @var \PDO
*/
protected $pdoInstance;
-
- /**
- * @var Connection
- */
- protected static $storedConnection;
-
/**
* @var EventHandler
*/
protected $eventHandler;
/**
- * @param string $adapter
- * @param array $adapterConfig
+ * @param string $adapter
+ * @param array $adapterConfig
* @param Container|null $container
*/
- public function __construct($adapter, array $adapterConfig, Container $container = null)
+ public function __construct(string $adapter, array $adapterConfig, Container $container = null)
{
- $container = $container ?: new Container();
+ $this->container = $container ?? new Container();
- $this->container = $container;
-
- $this->setAdapter($adapter)->setAdapterConfig($adapterConfig)->connect();
+ $this
+ ->setAdapter($adapter)
+ ->setAdapterConfig($adapterConfig)
+ ->connect()
+ ;
// Create event dependency
$this->eventHandler = $this->container->build(EventHandler::class);
}
/**
- * Returns an instance of Query Builder
+ * @return Connection
*/
- public function getQueryBuilder()
+ public static function getStoredConnection(): Connection
{
- return $this->container->build(QueryBuilderHandler::class, [$this]);
+ return static::$storedConnection;
}
/**
@@ -90,83 +88,88 @@ protected function connect()
}
/**
- * @param \PDO $pdo
- * @return static
+ * @return string
*/
- public function setPdoInstance(\PDO $pdo)
+ public function getAdapter(): string
{
- $this->pdoInstance = $pdo;
-
- return $this;
+ return $this->adapter;
}
/**
- * @return \PDO
+ * @return array
*/
- public function getPdoInstance()
+ public function getAdapterConfig(): array
{
- return $this->pdoInstance;
+ return $this->adapterConfig;
}
/**
- * @param string $adapter
- * @return static
+ * @return Container
*/
- public function setAdapter($adapter)
+ public function getContainer(): Container
{
- $this->adapter = $adapter;
-
- return $this;
+ return $this->container;
}
/**
- * @return string
+ * @return EventHandler
*/
- public function getAdapter()
+ public function getEventHandler(): EventHandler
{
- return $this->adapter;
+ return $this->eventHandler;
}
/**
- * @param array $adapterConfig
- * @return static
+ * @return \PDO
*/
- public function setAdapterConfig(array $adapterConfig)
+ public function getPdoInstance(): \PDO
{
- $this->adapterConfig = $adapterConfig;
-
- return $this;
+ return $this->pdoInstance;
}
/**
- * @return array
+ * Returns an instance of Query Builder
+ *
+ * @return QueryBuilderHandler
*/
- public function getAdapterConfig()
+ public function getQueryBuilder(): QueryBuilderHandler
{
- return $this->adapterConfig;
+ return $this->container->build(QueryBuilderHandler::class, [$this]);
}
/**
- * @return Container
+ * @param string $adapter
+ *
+ * @return \Pecee\Pixie\Connection
*/
- public function getContainer()
+ public function setAdapter(string $adapter): Connection
{
- return $this->container;
+ $this->adapter = $adapter;
+
+ return $this;
}
/**
- * @return EventHandler
+ * @param array $adapterConfig
+ *
+ * @return \Pecee\Pixie\Connection
*/
- public function getEventHandler()
+ public function setAdapterConfig(array $adapterConfig): Connection
{
- return $this->eventHandler;
+ $this->adapterConfig = $adapterConfig;
+
+ return $this;
}
/**
- * @return Connection
+ * @param \PDO $pdo
+ *
+ * @return \Pecee\Pixie\Connection
*/
- public static function getStoredConnection()
+ public function setPdoInstance(\PDO $pdo): Connection
{
- return static::$storedConnection;
+ $this->pdoInstance = $pdo;
+
+ return $this;
}
}
diff --git a/src/Pecee/Pixie/ConnectionAdapters/BaseAdapter.php b/src/Pecee/Pixie/ConnectionAdapters/BaseAdapter.php
index 81266dc..5cfb9d7 100644
--- a/src/Pecee/Pixie/ConnectionAdapters/BaseAdapter.php
+++ b/src/Pecee/Pixie/ConnectionAdapters/BaseAdapter.php
@@ -28,7 +28,7 @@ public function __construct(Container $container)
* @param $config
* @return \PDO
*/
- public function connect($config)
+ public function connect(array $config)
{
if (isset($config['options']) === false) {
$config['options'] = [];
diff --git a/src/Pecee/Pixie/ConnectionAdapters/Mysql.php b/src/Pecee/Pixie/ConnectionAdapters/Mysql.php
index ed19207..99b1edf 100644
--- a/src/Pecee/Pixie/ConnectionAdapters/Mysql.php
+++ b/src/Pecee/Pixie/ConnectionAdapters/Mysql.php
@@ -11,12 +11,13 @@ class Mysql extends BaseAdapter
{
/**
* @param array $config
+ *
* @return mixed
* @throws Exception
*/
protected function doConnect(array $config)
{
- if (extension_loaded('pdo_mysql') === false) {
+ if (\extension_loaded('pdo_mysql') === false) {
throw new Exception(sprintf('%s library not loaded', 'pdo_mysql'));
}
@@ -34,6 +35,9 @@ protected function doConnect(array $config)
$connectionString .= ";unix_socket={$config['unix_socket']}";
}
+ /**
+ * @var \PDO $connection
+ */
$connection = $this->container->build(
\PDO::class,
[$connectionString, $config['username'], $config['password'], $config['options']]
diff --git a/src/Pecee/Pixie/ConnectionAdapters/Pgsql.php b/src/Pecee/Pixie/ConnectionAdapters/Pgsql.php
index 3dbee8c..171007a 100644
--- a/src/Pecee/Pixie/ConnectionAdapters/Pgsql.php
+++ b/src/Pecee/Pixie/ConnectionAdapters/Pgsql.php
@@ -11,12 +11,13 @@ class Pgsql extends BaseAdapter
{
/**
* @param array $config
+ *
* @return mixed
* @throws Exception
*/
protected function doConnect(array $config)
{
- if (extension_loaded('pdo_pgsql') === false) {
+ if (\extension_loaded('pdo_pgsql') === false) {
throw new Exception(sprintf('%s library not loaded', 'pdo_pgsql'));
}
@@ -26,6 +27,9 @@ protected function doConnect(array $config)
$connectionString .= ";port={$config['port']}";
}
+ /**
+ * @var \PDO $connection
+ */
$connection = $this->container->build(
\PDO::class,
[$connectionString, $config['username'], $config['password'], $config['options']]
diff --git a/src/Pecee/Pixie/ConnectionAdapters/Sqlite.php b/src/Pecee/Pixie/ConnectionAdapters/Sqlite.php
index 9b8454d..25a783d 100644
--- a/src/Pecee/Pixie/ConnectionAdapters/Sqlite.php
+++ b/src/Pecee/Pixie/ConnectionAdapters/Sqlite.php
@@ -11,12 +11,13 @@ class Sqlite extends BaseAdapter
{
/**
* @param array $config
- * @return mixed
+ *
+ * @return \PDO
* @throws Exception
*/
public function doConnect(array $config)
{
- if (extension_loaded('pdo_sqlite') === false) {
+ if (\extension_loaded('pdo_sqlite') === false) {
throw new Exception(sprintf('%s library not loaded', 'pdo_sqlite'));
}
diff --git a/src/Pecee/Pixie/EventHandler.php b/src/Pecee/Pixie/EventHandler.php
index ed82776..f98588d 100644
--- a/src/Pecee/Pixie/EventHandler.php
+++ b/src/Pecee/Pixie/EventHandler.php
@@ -12,6 +12,10 @@
*/
class EventHandler
{
+ /**
+ * Fake table name for any table events
+ */
+ const TABLE_ANY = ':any';
/**
* @var array
*/
@@ -23,21 +27,53 @@ class EventHandler
protected $firedEvents = [];
/**
- * @return array
+ * @param QueryBuilderHandler $queryBuilder
+ * @param string $event
+ *
+ * @return mixed
*/
- public function getEvents()
+ public function fireEvents(QueryBuilderHandler $queryBuilder, string $event)
{
- return $this->events;
+ $statements = $queryBuilder->getStatements();
+ $tables = $statements['tables'] ?? [];
+
+ // Events added with :any will be fired in case of any table,
+ // we are adding :any as a fake table at the beginning.
+ array_unshift($tables, static::TABLE_ANY);
+
+ $handlerParams = \func_get_args();
+ unset($handlerParams[1]);
+
+ // Fire all events
+ foreach ($tables as $table) {
+ // Fire before events for :any table
+ $action = $this->getEvent($event, $table);
+ if ($action !== null) {
+
+ // Make an event id, with event type and table
+ $eventId = $event . $table;
+
+ // Fire event and add to fired list
+ $this->firedEvents[] = $eventId;
+ $result = \call_user_func_array($action, $handlerParams);
+ if ($result !== null) {
+ return $result;
+ }
+ }
+ }
+
+ return null;
}
/**
- * @param string $event
- * @param string|null $table
- * @return callable|null
+ * @param string $event
+ * @param string|Raw|null $table
+ *
+ * @return \Closure|null
*/
- public function getEvent($event, $table = null)
+ public function getEvent(string $event, $table = null)
{
- $table = $table ?: ':any';
+ $table = $table ?? static::TABLE_ANY;
if ($table instanceof Raw) {
return null;
@@ -55,18 +91,27 @@ public function getEvent($event, $table = null)
}
}
- return isset($this->events[$table][$event]) ? $this->events[$table][$event] : null;
+ return $this->events[$table][$event] ?? null;
}
/**
- * @param string $event
- * @param string $table
+ * @return array
+ */
+ public function getEvents(): array
+ {
+ return $this->events;
+ }
+
+ /**
+ * @param string $event
+ * @param string $table
* @param \Closure $action
+ *
* @return void
*/
- public function registerEvent($event, $table, \Closure $action)
+ public function registerEvent(string $event, string $table = null, \Closure $action)
{
- $table = $table ?: ':any';
+ $table = $table ?? static::TABLE_ANY;
$this->events[$table][$event] = $action;
}
@@ -74,49 +119,12 @@ public function registerEvent($event, $table, \Closure $action)
/**
* @param string $event
* @param string $table
+ *
* @return void
*/
public function removeEvent($event, $table = null)
{
- $table = $table ?: 'any';
+ $table = $table ?? static::TABLE_ANY;
unset($this->events[$table][$event]);
}
-
- /**
- * @param QueryBuilderHandler $queryBuilder
- * @param string $event
- * @return mixed|null
- */
- public function fireEvents($queryBuilder, $event)
- {
- $statements = $queryBuilder->getStatements();
- $tables = isset($statements['tables']) ? $statements['tables'] : [];
-
- // Events added with :any will be fired in case of any table,
- // we are adding :any as a fake table at the beginning.
- array_unshift($tables, ':any');
-
- $handlerParams = func_get_args();
- unset($handlerParams[1]);
-
- // Fire all events
- foreach ($tables as $table) {
- // Fire before events for :any table
- $action = $this->getEvent($event, $table);
- if ($action !== null) {
-
- // Make an event id, with event type and table
- $eventId = $event . $table;
-
- // Fire event and add to fired list
- $this->firedEvents[] = $eventId;
- $result = call_user_func_array($action, $handlerParams);
- if ($result !== null) {
- return $result;
- }
- }
- }
-
- return null;
- }
}
diff --git a/src/Pecee/Pixie/QueryBuilder/Adapters/BaseAdapter.php b/src/Pecee/Pixie/QueryBuilder/Adapters/BaseAdapter.php
index 25efdec..50ad1b9 100644
--- a/src/Pecee/Pixie/QueryBuilder/Adapters/BaseAdapter.php
+++ b/src/Pecee/Pixie/QueryBuilder/Adapters/BaseAdapter.php
@@ -37,109 +37,245 @@ abstract class BaseAdapter
public function __construct(Connection $connection)
{
$this->connection = $connection;
- $this->container = $this->connection->getContainer();
+ $this->container = $this->connection->getContainer();
}
/**
- * Build select query string and bindings
+ * Array concatenating method, like implode.
+ * But it does wrap sanitizer and trims last glue
+ *
+ * @param array $pieces
+ * @param string $glue
+ * @param bool $wrapSanitizer
+ *
+ * @return string
+ */
+ protected function arrayStr(array $pieces, $glue = ',', $wrapSanitizer = true): string
+ {
+ $str = '';
+ foreach ($pieces as $key => $piece) {
+ if ($wrapSanitizer === true) {
+ $piece = $this->wrapSanitizer($piece);
+ }
+
+ if (\is_int($key) === false) {
+ $piece = ($wrapSanitizer ? $this->wrapSanitizer($key) : $key) . ' AS ' . $piece;
+ }
+
+ $str .= $piece . $glue;
+ }
+
+ return trim($str, $glue);
+ }
+
+ /**
+ * Build generic criteria string and bindings from statements, like "a = b and c = ?"
*
* @param array $statements
+ * @param bool $bindValues
+ *
* @throws Exception
* @return array
*/
- public function select(array $statements)
+ protected function buildCriteria(array $statements, $bindValues = true)
{
- if (array_key_exists('selects', $statements) === false) {
- $statements['selects'][] = '*';
- }
+ $criteria = '';
+ $bindings = [[]];
- // From
- $fromEnabled = false;
- $tables = '';
+ foreach ($statements as $statement) {
- if (isset($statements['tables']) === true) {
- $tables = [];
+ $key = $this->wrapSanitizer($statement['key']);
+ $value = $statement['value'];
- foreach ((array)$statements['tables'] as $table) {
+ if ($value === null && $key instanceof \Closure) {
+
+ /**
+ * We have a closure, a nested criteria
+ * Build a new NestedCriteria class, keep it by reference so any changes made in the closure should reflect here
+ */
+
+ /* @var $nestedCriteria NestedCriteria */
+ $nestedCriteria = $this->container->build(
+ NestedCriteria::class,
+ [$this->connection]
+ );
+
+ // Call the closure with our new nestedCriteria object
+ $key($nestedCriteria);
+
+ // Get the criteria only query from the nestedCriteria object
+ $queryObject = $nestedCriteria->getQuery('criteriaOnly', true);
+
+ // Merge the bindings we get from nestedCriteria object
+ $bindings[] = $queryObject->getBindings();
+
+ // Append the sql we get from the nestedCriteria object
+ $criteria .= $statement['joiner'] . ' (' . $queryObject->getSql() . ') ';
- $prefix = isset($statements['aliases'][$table]) ? $statements['aliases'][$table] : null;
+ continue;
+ }
+
+ if (\is_array($value) === true) {
+
+ // Where in or between like query
+ $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'];
- if ($prefix !== null) {
- $t = ($table instanceof Raw) ? $table : '`' . $table . '` AS `' . strtolower($prefix) . '`';
+ if ($statement['operator'] === 'BETWEEN') {
+ $bindings[] = (array)$statement['value'];
+ $criteria .= ' ? AND ? ';
} else {
- $t = ($table instanceof Raw) ? $table : '`' . $table . '`';
+ $valuePlaceholder = '';
+ foreach ((array)$statement['value'] as $subValue) {
+ $valuePlaceholder .= '?, ';
+ $bindings[] = (array)$subValue;
+ }
+
+ $valuePlaceholder = trim($valuePlaceholder, ', ');
+ $criteria .= ' (' . $valuePlaceholder . ') ';
}
- $tables[] = $t;
+ continue;
+
}
- $tables = implode(',', $tables);
- $fromEnabled = true;
- }
+ if ($value instanceof Raw) {
+ $criteria .= "{$statement['joiner']} {$key} {$statement['operator']} $value ";
+ continue;
+ }
- // SELECT
- $selects = $this->arrayStr($statements['selects'], ', ');
- // WHERE
- list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
+ // Usual where like criteria
+ if ($bindValues === false) {
- // GROUP BY
- $groupBys = $this->arrayStr($statements['groupBys'], ', ');
- if ($groupBys !== '' && isset($statements['groupBys']) === true) {
- $groupBys = 'GROUP BY ' . $groupBys;
- }
+ // Specially for joins - we are not binding values, lets sanitize then
+ $value = $this->wrapSanitizer($value);
+ $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'] . ' ' . $value . ' ';
- // ORDER BY
- $orderBys = '';
- if (isset($statements['orderBys']) && is_array($statements['orderBys'])) {
- foreach ($statements['orderBys'] as $orderBy) {
- $orderBys .= $this->wrapSanitizer($orderBy['field']) . ' ' . $orderBy['type'] . ', ';
+ continue;
}
- if ($orderBys = trim($orderBys, ', ')) {
- $orderBys = 'ORDER BY ' . $orderBys;
+ if ($statement['key'] instanceof Raw) {
+
+ if ($statement['operator'] !== null) {
+ $criteria .= "{$statement['joiner']} {$key} {$statement['operator']} ? ";
+ $bindings[] = $statement['key']->getBindings();
+ $bindings[] = (array)$value;
+ } else {
+ $criteria .= $statement['joiner'] . ' ' . $key . ' ';
+ $bindings[] = $statement['key']->getBindings();
+ }
+
+ continue;
+
}
+
+ // WHERE
+ $valuePlaceholder = '?';
+ $bindings[] = [$value];
+ $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'] . ' ' . $valuePlaceholder . ' ';
}
- // LIMIT AND OFFSET
- $limit = isset($statements['limit']) ? 'LIMIT ' . $statements['limit'] : '';
- $offset = isset($statements['offset']) ? 'OFFSET ' . $statements['offset'] : '';
+ $bindings = \array_merge(...$bindings);
- // HAVING
- list($havingCriteria, $havingBindings) = $this->buildCriteriaWithType($statements, 'havings', 'HAVING');
+ // Clear all white spaces, and, or from beginning and white spaces from ending
+ $criteria = \preg_replace('/^(\s?AND ?|\s?OR ?)|\s$/i', '', $criteria);
- // JOINS
- $joinString = $this->buildJoin($statements);
+ return [$criteria, $bindings];
+ }
- $sqlArray = [
- 'SELECT' . (isset($statements['distinct']) ? ' DISTINCT' : ''),
- $selects,
- $fromEnabled ? 'FROM' : '',
- $tables,
- $joinString,
- $whereCriteria,
- $groupBys,
- $havingCriteria,
- $orderBys,
- $limit,
- $offset,
- ];
+ /**
+ * Build criteria string and binding with various types added, like WHERE and Having
+ *
+ * @param array $statements
+ * @param string $key
+ * @param string $type
+ * @param bool $bindValues
+ *
+ * @return array
+ * @throws Exception
+ */
+ protected function buildCriteriaWithType(array $statements, $key, $type, $bindValues = true)
+ {
+ $criteria = '';
+ $bindings = [];
- $sql = $this->concatenateQuery($sqlArray);
+ if (isset($statements[$key]) === true) {
+ // Get the generic/adapter agnostic criteria string from parent
+ list($criteria, $bindings) = $this->buildCriteria($statements[$key], $bindValues);
- $bindings = array_merge(
- $whereBindings,
- $havingBindings
- );
+ if ($criteria !== null) {
+ $criteria = $type . ' ' . $criteria;
+ }
+ }
- return compact('sql', 'bindings');
+ return [$criteria, $bindings];
+ }
+
+ /**
+ * Build join string
+ *
+ * @param array $statements
+ *
+ * @return string
+ * @throws Exception
+ */
+ protected function buildJoin(array $statements)
+ {
+ $sql = '';
+
+ if (\array_key_exists('joins', $statements) === false || \count($statements['joins']) === 0) {
+ return $sql;
+ }
+
+ foreach ((array)$statements['joins'] as $joinArr) {
+ if (\is_array($joinArr['table']) === true) {
+ list($mainTable, $aliasTable) = $joinArr['table'];
+ $table = $this->wrapSanitizer($mainTable) . ' AS ' . $this->wrapSanitizer($aliasTable);
+ } else {
+ $table = $joinArr['table'] instanceof Raw ? (string)$joinArr['table'] : $this->wrapSanitizer($joinArr['table']);
+ }
+
+ /* @var $joinBuilder \Pecee\Pixie\QueryBuilder\QueryBuilderHandler */
+ $joinBuilder = $joinArr['joinBuilder'];
+
+ $sqlArr = [
+ $sql,
+ strtoupper($joinArr['type']),
+ 'JOIN',
+ $table,
+ 'ON',
+ $joinBuilder->getQuery('criteriaOnly', false)->getSql(),
+ ];
+
+ $sql = $this->concatenateQuery($sqlArr);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Join different part of queries with a space.
+ *
+ * @param array $pieces
+ *
+ * @return string
+ */
+ protected function concatenateQuery(array $pieces)
+ {
+ $str = '';
+ foreach ($pieces as $piece) {
+ $str = trim($str) . ' ' . trim($piece);
+ }
+
+ return trim($str);
}
/**
* Build just criteria part of the query
*
* @param array $statements
- * @param bool $bindValues
+ * @param bool $bindValues
+ *
* @return array
* @throws Exception
*/
@@ -156,11 +292,34 @@ public function criteriaOnly(array $statements, $bindValues = true)
}
/**
- * Build a generic insert/ignore/replace query
+ * Build delete query
*
* @param array $statements
- * @param array $data
+ *
+ * @return array
+ * @throws Exception
+ */
+ public function delete(array $statements)
+ {
+ $table = end($statements['tables']);
+
+ // WHERE
+ list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
+
+ $sqlArray = ['DELETE FROM', $this->wrapSanitizer($table), $whereCriteria];
+ $sql = $this->concatenateQuery($sqlArray);
+ $bindings = $whereBindings;
+
+ return compact('sql', 'bindings');
+ }
+
+ /**
+ * Build a generic insert/ignore/replace query
+ *
+ * @param array $statements
+ * @param array $data
* @param string $type
+ *
* @return array
* @throws Exception
*/
@@ -175,7 +334,7 @@ private function doInsert(array $statements, array $data, $type)
if ($value instanceof Raw) {
$values[] = (string)$value;
} else {
- $values[] = '?';
+ $values[] = '?';
$bindings[] = $value;
}
}
@@ -190,13 +349,13 @@ private function doInsert(array $statements, array $data, $type)
if (isset($statements['onduplicate']) === true) {
- if (count($statements['onduplicate']) < 1) {
+ if (\count($statements['onduplicate']) < 1) {
throw new Exception('No data given.', 4);
}
list($updateStatement, $updateBindings) = $this->getUpdateStatement($statements['onduplicate']);
$sqlArray[] = 'ON DUPLICATE KEY UPDATE ' . $updateStatement;
- $bindings = array_merge($bindings, $updateBindings);
+ $bindings = array_merge($bindings, $updateBindings);
}
@@ -206,54 +365,16 @@ private function doInsert(array $statements, array $data, $type)
}
/**
- * Build insert query
+ * Build fields assignment part of SET ... or ON DUBLICATE KEY UPDATE ... statements
*
- * @param array $statements
* @param array $data
+ *
* @return array
- * @throws Exception
*/
- public function insert(array $statements, array $data)
+ private function getUpdateStatement(array $data)
{
- return $this->doInsert($statements, $data, 'INSERT');
- }
-
- /**
- * Build insert and ignore query
- *
- * @param array $statements
- * @param array $data
- * @return array
- * @throws Exception
- */
- public function insertIgnore(array $statements, array $data)
- {
- return $this->doInsert($statements, $data, 'INSERT IGNORE');
- }
-
- /**
- * Build replace query
- *
- * @param array $statements
- * @param array $data
- * @return array
- * @throws Exception
- */
- public function replace(array $statements, array $data)
- {
- return $this->doInsert($statements, $data, 'REPLACE');
- }
-
- /**
- * Build fields assignment part of SET ... or ON DUBLICATE KEY UPDATE ... statements
- *
- * @param array $data
- * @return array
- */
- private function getUpdateStatement(array $data)
- {
- $bindings = [];
- $statement = '';
+ $bindings = [];
+ $statement = '';
foreach ($data as $key => $value) {
@@ -262,7 +383,7 @@ private function getUpdateStatement(array $data)
if ($value instanceof Raw) {
$statement .= $value . ',';
} else {
- $statement .= '?,';
+ $statement .= '?,';
$bindings[] = $value;
}
}
@@ -273,224 +394,192 @@ private function getUpdateStatement(array $data)
}
/**
- * Build update query
+ * Build insert query
*
* @param array $statements
* @param array $data
+ *
* @return array
* @throws Exception
*/
- public function update(array $statements, array $data)
+ public function insert(array $statements, array $data)
{
- if (count($data) < 1) {
- throw new Exception('No data given.', 4);
- }
-
- $table = end($statements['tables']);
-
- // UPDATE
- list($updateStatement, $bindings) = $this->getUpdateStatement($data);
-
- // WHERE
- list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
-
- // LIMIT
- $limit = isset($statements['limit']) ? 'LIMIT ' . $statements['limit'] : '';
-
- $sqlArray = [
- 'UPDATE',
- $this->wrapSanitizer($table),
- 'SET ' . $updateStatement,
- $whereCriteria,
- $limit,
- ];
-
- $sql = $this->concatenateQuery($sqlArray);
-
- $bindings = array_merge($bindings, $whereBindings);
-
- return compact('sql', 'bindings');
+ return $this->doInsert($statements, $data, 'INSERT');
}
/**
- * Build delete query
+ * Build insert and ignore query
*
* @param array $statements
+ * @param array $data
+ *
* @return array
* @throws Exception
*/
- public function delete(array $statements)
+ public function insertIgnore(array $statements, array $data)
{
- $table = end($statements['tables']);
-
- // WHERE
- list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
-
- $sqlArray = ['DELETE FROM', $this->wrapSanitizer($table), $whereCriteria];
- $sql = $this->concatenateQuery($sqlArray);
- $bindings = $whereBindings;
-
- return compact('sql', 'bindings');
+ return $this->doInsert($statements, $data, 'INSERT IGNORE');
}
/**
- * Array concatenating method, like implode.
- * But it does wrap sanitizer and trims last glue
+ * Build replace query
*
- * @param array $pieces
- * @param string $glue
- * @param bool $wrapSanitizer
- * @return string
- */
- protected function arrayStr(array $pieces, $glue = ',', $wrapSanitizer = true)
- {
- $str = '';
- foreach ($pieces as $key => $piece) {
- if ($wrapSanitizer === true) {
- $piece = $this->wrapSanitizer($piece);
- }
-
- if (is_int($key) === false) {
- $piece = ($wrapSanitizer ? $this->wrapSanitizer($key) : $key) . ' AS ' . $piece;
- }
-
- $str .= $piece . $glue;
- }
-
- return trim($str, $glue);
- }
-
- /**
- * Join different part of queries with a space.
+ * @param array $statements
+ * @param array $data
*
- * @param array $pieces
- * @return string
+ * @return array
+ * @throws Exception
*/
- protected function concatenateQuery(array $pieces)
+ public function replace(array $statements, array $data)
{
- $str = '';
- foreach ($pieces as $piece) {
- $str = trim($str) . ' ' . trim($piece);
- }
-
- return trim($str);
+ return $this->doInsert($statements, $data, 'REPLACE');
}
/**
- * Build generic criteria string and bindings from statements, like "a = b and c = ?"
+ * Build select query string and bindings
*
* @param array $statements
- * @param bool $bindValues
+ *
* @throws Exception
* @return array
*/
- protected function buildCriteria(array $statements, $bindValues = true)
+ public function select(array $statements)
{
- $criteria = '';
- $bindings = [[]];
+ if (array_key_exists('selects', $statements) === false) {
+ $statements['selects'] = ['*'];
+ }
- foreach ($statements as $statement) {
+ // From
+ $fromEnabled = false;
+ $tables = '';
- $key = $this->wrapSanitizer($statement['key']);
- $value = $statement['value'];
+ if (isset($statements['tables']) === true) {
+ $tables = [];
- if ($value === null && $key instanceof \Closure) {
+ foreach ((array)$statements['tables'] as $table) {
+ if ($table instanceof Raw) {
+ $t = $table;
+ } else {
+ $prefix = $statements['aliases'][$table] ?? null;
- /**
- * We have a closure, a nested criteria
- * Build a new NestedCriteria class, keep it by reference so any changes made in the closure should reflect here
- */
+ if ($prefix !== null) {
+ $t = sprintf('`%s` AS `%s`', $table, strtolower($prefix));
+ } else {
+ $t = sprintf('`%s`', $table);
+ }
+ }
- /* @var $nestedCriteria NestedCriteria */
- $nestedCriteria = $this->container->build(
- NestedCriteria::class,
- [$this->connection]
- );
+ $tables[] = $t;
+ }
- // Call the closure with our new nestedCriteria object
- $key($nestedCriteria);
+ $tables = implode(',', $tables);
+ $fromEnabled = true;
+ }
- // Get the criteria only query from the nestedCriteria object
- $queryObject = $nestedCriteria->getQuery('criteriaOnly', true);
+ // SELECT
+ $selects = $this->arrayStr($statements['selects'], ', ');
- // Merge the bindings we get from nestedCriteria object
- $bindings[] = $queryObject->getBindings();
+ // WHERE
+ list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
- // Append the sql we get from the nestedCriteria object
- $criteria .= $statement['joiner'] . ' (' . $queryObject->getSql() . ') ';
+ // GROUP BY
+ $groupBys = $this->arrayStr($statements['groupBys'], ', ');
+ if ($groupBys !== '' && isset($statements['groupBys']) === true) {
+ $groupBys = 'GROUP BY ' . $groupBys;
+ }
- continue;
+ // ORDER BY
+ $orderBys = '';
+ if (isset($statements['orderBys']) && \is_array($statements['orderBys'])) {
+ foreach ($statements['orderBys'] as $orderBy) {
+ $orderBys .= $this->wrapSanitizer($orderBy['field']) . ' ' . $orderBy['type'] . ', ';
}
- if (is_array($value)) {
- // where_in or between like query
- $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'];
- if ($statement['operator'] === 'BETWEEN') {
- $bindings[] = (array)$statement['value'];
- $criteria .= ' ? AND ? ';
- } else {
- $valuePlaceholder = '';
- foreach ((array)$statement['value'] as $subValue) {
- $valuePlaceholder .= '?, ';
- $bindings[] = (array)$subValue;
- }
+ if ($orderBys = trim($orderBys, ', ')) {
+ $orderBys = 'ORDER BY ' . $orderBys;
+ }
+ }
- $valuePlaceholder = trim($valuePlaceholder, ', ');
- $criteria .= ' (' . $valuePlaceholder . ') ';
- }
+ // LIMIT AND OFFSET
+ $limit = isset($statements['limit']) ? 'LIMIT ' . $statements['limit'] : '';
+ $offset = isset($statements['offset']) ? 'OFFSET ' . $statements['offset'] : '';
- continue;
+ // HAVING
+ list($havingCriteria, $havingBindings) = $this->buildCriteriaWithType($statements, 'havings', 'HAVING');
- }
+ // JOINS
+ $joinString = $this->buildJoin($statements);
- if ($value instanceof Raw) {
- $criteria .= "{$statement['joiner']} {$key} {$statement['operator']} $value ";
- } else {
+ $sqlArray = [
+ 'SELECT' . (isset($statements['distinct']) ? ' DISTINCT' : ''),
+ $selects,
+ $fromEnabled ? 'FROM' : '',
+ $tables,
+ $joinString,
+ $whereCriteria,
+ $groupBys,
+ $havingCriteria,
+ $orderBys,
+ $limit,
+ $offset,
+ ];
- // Usual where like criteria
- if ($bindValues === false) {
+ $sql = $this->concatenateQuery($sqlArray);
- // Specially for joins - we are not binding values, lets sanitize then
- $value = $this->wrapSanitizer($value);
- $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'] . ' ' . $value . ' ';
+ $bindings = array_merge(
+ $whereBindings,
+ $havingBindings
+ );
- continue;
- }
+ return compact('sql', 'bindings');
+ }
- if ($statement['key'] instanceof Raw) {
+ /**
+ * Build update query
+ *
+ * @param array $statements
+ * @param array $data
+ *
+ * @return array
+ * @throws Exception
+ */
+ public function update(array $statements, array $data)
+ {
+ if (\count($data) < 1) {
+ throw new Exception('No data given.', 4);
+ }
- if ($statement['operator'] !== null) {
- $criteria .= "{$statement['joiner']} {$key} {$statement['operator']} ? ";
- $bindings[] = (array)$statement['key']->getBindings();
- $bindings[] = (array)$value;
- } else {
- $criteria .= $statement['joiner'] . ' ' . $key . ' ';
- $bindings[] = (array)$statement['key']->getBindings();
- }
+ $table = end($statements['tables']);
- continue;
+ // UPDATE
+ list($updateStatement, $bindings) = $this->getUpdateStatement($data);
- }
+ // WHERE
+ list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
- // WHERE
- $valuePlaceholder = '?';
- $bindings[] = (array)$value;
- $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'] . ' ' . $valuePlaceholder . ' ';
- }
- }
+ // LIMIT
+ $limit = isset($statements['limit']) ? 'LIMIT ' . $statements['limit'] : '';
- $bindings = array_merge(...$bindings);
+ $sqlArray = [
+ 'UPDATE',
+ $this->wrapSanitizer($table),
+ 'SET ' . $updateStatement,
+ $whereCriteria,
+ $limit,
+ ];
- // Clear all white spaces, and, or from beginning and white spaces from ending
- $criteria = preg_replace('/^(\s?AND ?|\s?OR ?)|\s$/i', '', $criteria);
+ $sql = $this->concatenateQuery($sqlArray);
- return [$criteria, $bindings];
+ $bindings = array_merge($bindings, $whereBindings);
+
+ return compact('sql', 'bindings');
}
/**
* Wrap values with adapter's sanitizer like, '`'
*
* @param string|Raw|\Closure $value
+ *
* @return string
*/
public function wrapSanitizer($value)
@@ -515,72 +604,4 @@ public function wrapSanitizer($value)
// Join these back with "." and return
return implode('.', $valueArr);
}
-
- /**
- * Build criteria string and binding with various types added, like WHERE and Having
- *
- * @param array $statements
- * @param string $key
- * @param string $type
- * @param bool $bindValues
- * @return array
- * @throws Exception
- */
- protected function buildCriteriaWithType(array $statements, $key, $type, $bindValues = true)
- {
- $criteria = '';
- $bindings = [];
-
- if (isset($statements[$key]) === true) {
- // Get the generic/adapter agnostic criteria string from parent
- list($criteria, $bindings) = $this->buildCriteria($statements[$key], $bindValues);
-
- if ($criteria !== null) {
- $criteria = $type . ' ' . $criteria;
- }
- }
-
- return [$criteria, $bindings];
- }
-
- /**
- * Build join string
- *
- * @param array $statements
- * @return string
- * @throws Exception
- */
- protected function buildJoin(array $statements)
- {
- $sql = '';
-
- if (array_key_exists('joins', $statements) === false || count($statements['joins']) === 0) {
- return $sql;
- }
-
- foreach ((array)$statements['joins'] as $joinArr) {
- if (is_array($joinArr['table']) === true) {
- list($mainTable, $aliasTable) = $joinArr['table'];
- $table = $this->wrapSanitizer($mainTable) . ' AS ' . $this->wrapSanitizer($aliasTable);
- } else {
- $table = $joinArr['table'] instanceof Raw ? (string)$joinArr['table'] : $this->wrapSanitizer($joinArr['table']);
- }
-
- /* @var $joinBuilder \Pecee\Pixie\QueryBuilder\QueryBuilderHandler */
- $joinBuilder = $joinArr['joinBuilder'];
-
- $sqlArr = [
- $sql,
- strtoupper($joinArr['type']),
- 'JOIN',
- $table,
- 'ON',
- $joinBuilder->getQuery('criteriaOnly', false)->getSql(),
- ];
-
- $sql = $this->concatenateQuery($sqlArr);
- }
-
- return $sql;
- }
}
diff --git a/src/Pecee/Pixie/QueryBuilder/NestedCriteria.php b/src/Pecee/Pixie/QueryBuilder/NestedCriteria.php
index 1941d99..5e9b4bb 100644
--- a/src/Pecee/Pixie/QueryBuilder/NestedCriteria.php
+++ b/src/Pecee/Pixie/QueryBuilder/NestedCriteria.php
@@ -10,15 +10,16 @@
class NestedCriteria extends QueryBuilderHandler
{
/**
- * @param string|Raw|\Closure $key
- * @param string|Raw|\Closure|null $operator
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
* @param string|Raw|\Closure|null $value
- * @param string $joiner
+ * @param string $joiner
+ *
* @return static
*/
- protected function whereHandler($key, $operator = null, $value = null, $joiner = 'AND')
+ protected function whereHandler($key, string $operator = null, $value = null, $joiner = 'AND')
{
- $key = $this->addTablePrefix($key);
+ $key = $this->addTablePrefix($key);
$this->statements['criteria'][] = compact('key', 'operator', 'value', 'joiner');
return $this;
diff --git a/src/Pecee/Pixie/QueryBuilder/QueryBuilderHandler.php b/src/Pecee/Pixie/QueryBuilder/QueryBuilderHandler.php
index 50d289c..159b38f 100644
--- a/src/Pecee/Pixie/QueryBuilder/QueryBuilderHandler.php
+++ b/src/Pecee/Pixie/QueryBuilder/QueryBuilderHandler.php
@@ -13,6 +13,54 @@
*/
class QueryBuilderHandler
{
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_BEFORE_DELETE = 'before-delete';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_BEFORE_INSERT = 'before-insert';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_BEFORE_UPDATE = 'before-update';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_BEFORE_SELECT = 'before-select';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_AFTER_DELETE = 'after-delete';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_AFTER_INSERT = 'after-insert';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_AFTER_UPDATE = 'after-update';
+ /**
+ * Event name
+ *
+ * @var string
+ */
+ const EVENT_AFTER_SELECT = 'after-select';
/**
* @var \Viocon\Container
@@ -32,7 +80,7 @@ class QueryBuilderHandler
];
/**
- * @var \PDO
+ * @var PDO
*/
protected $pdo;
@@ -70,18 +118,20 @@ class QueryBuilderHandler
/**
* @param \Pecee\Pixie\Connection|null $connection
+ *
* @throws \Pecee\Pixie\Exception
*/
public function __construct(Connection $connection = null)
{
- if ($connection === null && ($connection = Connection::getStoredConnection()) === false) {
+ $this->connection = $connection ?? Connection::getStoredConnection();
+
+ if ($this->connection === null) {
throw new Exception('No database connection found.', 1);
}
- $this->connection = $connection;
- $this->container = $this->connection->getContainer();
- $this->pdo = $this->connection->getPdoInstance();
- $this->adapter = $this->connection->getAdapter();
+ $this->container = $this->connection->getContainer();
+ $this->pdo = $this->connection->getPdoInstance();
+ $this->adapter = $this->connection->getAdapter();
$this->adapterConfig = $this->connection->getAdapterConfig();
if (isset($this->adapterConfig['prefix']) === true) {
@@ -98,267 +148,369 @@ public function __construct(Connection $connection = null)
}
/**
- * Add fetch parameters to the PDO-query.
+ * Add new statement to statement-list
*
- * @param mixed $parameters ...
- * @return static
+ * @param string $key
+ * @param mixed $value
*/
- public function setFetchMode($parameters = null)
+ protected function addStatement(string $key, $value)
{
- $this->fetchParameters = func_get_args();
-
- return $this;
+ if (array_key_exists($key, $this->statements) === false) {
+ $this->statements[$key] = (array)$value;
+ } else {
+ $this->statements[$key] = array_merge($this->statements[$key], (array)$value);
+ }
}
/**
- * Fetch query results as object of specified type
+ * Add table prefix (if given) on given string.
*
- * @param string $className
- * @param array $constructorArgs
- * @return QueryBuilderHandler
- */
- public function asObject($className, array $constructorArgs = [])
- {
- return $this->setFetchMode(\PDO::FETCH_CLASS, $className, $constructorArgs);
- }
-
- /**
- * Creates and returns new query.
+ * @param string|array|Raw|\Closure $values
+ * @param bool $tableFieldMix If we have mixes of field and table names with a "."
*
- * @param \Pecee\Pixie\Connection|null $connection
- * @throws \Pecee\Pixie\Exception
- * @return static
+ * @return array|string
*/
- public function newQuery(Connection $connection = null)
+ public function addTablePrefix($values, bool $tableFieldMix = true)
{
- if ($connection === null) {
- $connection = $this->connection;
+ if ($this->tablePrefix === null) {
+ return $values;
}
- return new static($connection);
+ // $value will be an array and we will add prefix to all table names
+ // If supplied value is not an array then make it one
+
+ $single = false;
+ if (\is_array($values) === false) {
+ $values = [$values];
+
+ // We had single value, so should return a single value
+ $single = true;
+ }
+
+ $return = [];
+
+ foreach ($values as $key => $value) {
+ // It's a raw query, just add it to our return array and continue next
+ if ($value instanceof Raw || $value instanceof \Closure) {
+ $return[$key] = $value;
+ continue;
+ }
+
+ // If key is not integer, it is likely a alias mapping, so we need to change prefix target
+ $target = &$value;
+
+ if (\is_int($key) === false) {
+ $target = &$key;
+ }
+
+ if ($tableFieldMix === false || ($tableFieldMix && strpos($target, '.') !== false)) {
+ $target = $this->tablePrefix . $target;
+ }
+
+ $return[$key] = $value;
+ }
+
+ // If we had single value then we should return a single value (end value of the array)
+ return $single ? end($return) : $return;
}
/**
- * Performs query.
+ * Performs special queries like COUNT, SUM etc based on the current query.
*
- * @param string $sql
- * @param array $bindings
- * @return static
+ * @param string $type
+ *
+ * @throws Exception
+ * @return int
*/
- public function query($sql, array $bindings = [])
+ protected function aggregate(string $type): int
{
- list($this->pdoStatement) = $this->statement($sql, $bindings);
+ // Get the current selects
+ $mainSelects = $this->statements['selects'] ?? null;
- return $this;
+ // Replace select with a scalar value like `count`
+ $this->statements['selects'] = [$this->raw($type . '(*) AS `field`')];
+ $row = $this->get();
+
+ // Set the select as it was
+ if ($mainSelects !== null) {
+ $this->statements['selects'] = $mainSelects;
+ } else {
+ unset($this->statements['selects']);
+ }
+
+ if (isset($row[0]) === true) {
+ if (\is_array($row[0]) === true) {
+ return (int)$row[0]['field'];
+ }
+ if (\is_object($row[0]) === true) {
+ return (int)$row[0]->field;
+ }
+ }
+
+ return 0;
}
/**
* Add or change table alias
* Example: table AS alias
*
- * @param string $table
* @param string $alias
- * @return QueryBuilderHandler
+ * @param string $table
+ *
+ * @return static
*/
- public function alias($table, $alias)
+ public function alias(string $alias, string $table = null)
{
- $this->statements['aliases'][$this->tablePrefix . $table] = strtolower($alias);
+ if($table === null && isset($this->statements['tables'][0]) === true) {
+ $table = $this->statements['tables'][0];
+ } else {
+ $table = $this->tablePrefix . $table;
+ }
+
+ $this->statements['aliases'][$table] = strtolower($alias);
return $this;
}
/**
- * Add or change table alias
+ * Fetch query results as object of specified type
*
- * Example: table AS alias
+ * @param string $className
+ * @param array $constructorArgs
*
- * @deprecated This method will be removed in the near future, please use QueryBuilderHandler::alias instead.
- * @see QueryBuilderHandler::alias
- * @param string $table
- * @param string $alias
- * @return QueryBuilderHandler
+ * @return static
*/
- public function prefix($table, $alias)
+ public function asObject(string $className, array $constructorArgs = []): QueryBuilderHandler
{
- return $this->alias($table, $alias);
+ return $this->setFetchMode(PDO::FETCH_CLASS, $className, $constructorArgs);
}
/**
- * Parses correct data-type for PDO parameter.
+ * Get count of rows
*
- * @param mixed $value
- * @return int PDO-parameter type
+ * @throws Exception
+ * @return int
*/
- protected function parseDataType($value)
+ public function count(): int
{
- if (is_int($value) === true) {
- return PDO::PARAM_INT;
- }
+ // Get the current statements
+ $originalStatements = $this->statements;
- if (is_bool($value) === true) {
- return PDO::PARAM_BOOL;
- }
+ unset($this->statements['orderBys'], $this->statements['limit'], $this->statements['offset']);
+
+ $count = $this->aggregate('count');
+ $this->statements = $originalStatements;
- return PDO::PARAM_STR;
+ return $count;
}
/**
- * Execute statement
+ * Forms delete on the current query.
*
- * @param string $sql
- * @param array $bindings
- * @return array PDOStatement and execution time as float
+ * @return \PDOStatement
+ * @throws Exception
*/
- public function statement($sql, array $bindings = [])
+ public function delete(): \PDOStatement
{
- $start = microtime(true);
-
- $pdoStatement = $this->pdo->prepare($sql);
+ /* @var $response \PDOStatement */
+ $queryObject = $this->getQuery('delete');
- foreach ($bindings as $key => $value) {
- $pdoStatement->bindValue(
- is_int($key) ? $key + 1 : $key,
- $value,
- $this->parseDataType($value)
- );
- }
+ $this->fireEvents(static::EVENT_BEFORE_DELETE, $queryObject);
- $pdoStatement->execute();
+ list($response, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
+ $this->fireEvents(static::EVENT_AFTER_DELETE, $queryObject, $executionTime);
- return [$pdoStatement, microtime(true) - $start];
+ return $response;
}
/**
- * Get all rows
+ * Performs insert
+ *
+ * @param array $data
+ * @param string $type
*
* @throws Exception
- * @return \stdClass[]
+ * @return array|string|null
*/
- public function get()
+ private function doInsert(array $data, string $type)
{
- $queryObject = null;
- $executionTime = 0;
+ // If first value is not an array - it's not a batch insert
+ if (\is_array(current($data)) === false) {
+ $queryObject = $this->getQuery($type, $data);
- if ($this->pdoStatement === null) {
- $queryObject = $this->getQuery();
- list($this->pdoStatement, $executionTime) = $this->statement(
- $queryObject->getSql(),
- $queryObject->getBindings()
- );
+ $this->fireEvents(static::EVENT_BEFORE_INSERT, $queryObject);
+ /**
+ * @var $result \PDOStatement
+ * @var $executionTime float
+ */
+ list($result, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
+ $return = $result->rowCount() === 1 ? $this->pdo->lastInsertId() : null;
+ $this->fireEvents(static::EVENT_AFTER_INSERT, $queryObject, $return, $executionTime);
+
+ return $return;
}
- $start = microtime(true);
- $this->fireEvents('before-select', $queryObject);
- $result = call_user_func_array([$this->pdoStatement, 'fetchAll'], $this->fetchParameters);
- $executionTime += microtime(true) - $start;
- $this->pdoStatement = null;
- $this->fireEvents('after-select', $queryObject, $result, $executionTime);
+ // Perform batch insert
- return $result;
+ $return = [];
+ foreach ($data as $subData) {
+ $queryObject = $this->getQuery($type, $subData);
+
+ $this->fireEvents(static::EVENT_BEFORE_INSERT, $queryObject);
+ list($result, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
+ $result = $result->rowCount() === 1 ? $this->pdo->lastInsertId() : null;
+ $this->fireEvents(static::EVENT_AFTER_INSERT, $queryObject, $result, $executionTime);
+
+ $return[] = $result;
+ }
+
+ return $return;
}
/**
- * Returns the first row
+ * Find by value and field name.
+ *
+ * @param string|int|float $value
+ * @param string $fieldName
*
* @throws Exception
- * @return \stdClass|null
+ * @return null|\stdClass
*/
- public function first()
+ public function find($value, $fieldName = 'id')
{
- $result = $this->limit(1)->get();
-
- return ($result !== null && count($result) > 0) ? $result[0] : null;
+ return $this->where($fieldName, '=', $value)->first();
}
/**
* Find all by field name and value
*
- * @param string $fieldName
+ * @param string $fieldName
* @param string|int|float $value
+ *
* @throws Exception
* @return \stdClass[]
*/
- public function findAll($fieldName, $value)
+ public function findAll(string $fieldName, $value)
{
return $this->where($fieldName, '=', $value)->get();
}
/**
- * Find by value and field name.
+ * Fires event by given event name
*
- * @param string|int|float $value
- * @param string $fieldName
- * @throws Exception
- * @return null|\stdClass
+ * @param string $name
+ * @param ... $parameters
+ *
+ * @return mixed|null
*/
- public function find($value, $fieldName = 'id')
+ public function fireEvents($name, $parameters = null)
{
- return $this->where($fieldName, '=', $value)->first();
+ $params = \func_get_args();
+ array_unshift($params, $this);
+
+ return \call_user_func_array([$this->connection->getEventHandler(), 'fireEvents'], $params);
}
/**
- * Get count of rows
+ * Returns the first row
*
* @throws Exception
- * @return int
+ * @return \stdClass|null
*/
- public function count()
+ public function first()
{
- // Get the current statements
- $originalStatements = $this->statements;
-
- unset($this->statements['orderBys'], $this->statements['limit'], $this->statements['offset']);
-
- $count = $this->aggregate('count');
- $this->statements = $originalStatements;
+ $result = $this->limit(1)->get();
- return $count;
+ return ($result !== null && \count($result) > 0) ? $result[0] : null;
}
/**
- * Performs special queries like COUNT, SUM etc based on the current query.
+ * Adds FROM statement to the current query.
*
- * @param string $type
- * @throws Exception
- * @return int
- */
- protected function aggregate($type)
- {
- // Get the current selects
- $mainSelects = isset($this->statements['selects']) ? $this->statements['selects'] : null;
+ * @param string|array $tables
+ *
+ * @return static
+ */
+ public function from($tables)
+ {
+ if (\is_array($tables) === false) {
+ $tables = \func_get_args();
+ }
- // Replace select with a scalar value like `count`
- $this->statements['selects'] = [$this->raw($type . '(*) AS `field`')];
- $row = $this->get();
+ $tables = $this->addTablePrefix($tables, false);
+ $this->addStatement('tables', $tables);
- // Set the select as it was
- if ($mainSelects !== null) {
- $this->statements['selects'] = $mainSelects;
- } else {
- unset($this->statements['selects']);
- }
+ return $this;
+ }
- if (isset($row[0]) === true) {
- if (is_array($row[0]) === true) {
- return (int)$row[0]['field'];
- }
- if (is_object($row[0])) {
- return (int)$row[0]->field;
- }
+ /**
+ * Get all rows
+ *
+ * @throws Exception
+ * @return object[]
+ */
+ public function get(): array
+ {
+ /**
+ * @var $queryObject \Pecee\Pixie\QueryBuilder\QueryObject
+ * @var $executionTime float
+ * @var $start float
+ * @var $result array
+ */
+ $queryObject = null;
+ $executionTime = 0;
+
+ if ($this->pdoStatement === null) {
+ $queryObject = $this->getQuery();
+ list($this->pdoStatement, $executionTime) = $this->statement(
+ $queryObject->getSql(),
+ $queryObject->getBindings()
+ );
}
- return 0;
+ $start = microtime(true);
+ $this->fireEvents(static::EVENT_BEFORE_SELECT, $queryObject);
+ $result = \call_user_func_array([$this->pdoStatement, 'fetchAll'], $this->fetchParameters);
+ $executionTime += microtime(true) - $start;
+ $this->pdoStatement = null;
+ $this->fireEvents(static::EVENT_AFTER_SELECT, $queryObject, $result, $executionTime);
+
+ return $result;
+ }
+
+ /**
+ * Get connection object
+ *
+ * @return Connection
+ */
+ public function getConnection(): Connection
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Get event by event name
+ *
+ * @param string $name
+ * @param string|null $table
+ *
+ * @return \Closure|null
+ */
+ public function getEvent(string $name, string $table = null)
+ {
+ return $this->connection->getEventHandler()->getEvent($name, $table);
}
/**
* Returns Query-object.
*
- * @param string $type
+ * @param string $type
* @param array|mixed|null $dataToBePassed
+ *
* @return QueryObject
* @throws Exception
*/
- public function getQuery($type = 'select', $dataToBePassed = null)
+ public function getQuery(string $type = 'select', $dataToBePassed = null): QueryObject
{
$allowedTypes = [
'select',
@@ -370,7 +522,7 @@ public function getQuery($type = 'select', $dataToBePassed = null)
'criteriaonly',
];
- if (in_array(strtolower($type), $allowedTypes, true) === false) {
+ if (\in_array(strtolower($type), $allowedTypes, true) === false) {
throw new Exception($type . ' is not a known type.', 2);
}
@@ -383,71 +535,79 @@ public function getQuery($type = 'select', $dataToBePassed = null)
}
/**
- * Performs new sub-query.
- * Call this method when you want to add a new sub-query in your where etc.
+ * Returns statements
*
- * @param QueryBuilderHandler $queryBuilder
- * @param string|null $alias
- * @throws Exception
- * @return Raw
+ * @return array
*/
- public function subQuery(QueryBuilderHandler $queryBuilder, $alias = null)
+ public function getStatements(): array
{
- $sql = '(' . $queryBuilder->getQuery()->getRawSql() . ')';
- if ($alias !== null) {
- $sql = $sql . ' AS ' . $this->adapterInstance->wrapSanitizer($alias);
- }
-
- return $queryBuilder->raw($sql);
+ return $this->statements;
}
/**
- * Performs insert
+ * Adds GROUP BY to the current query.
*
- * @param array $data
- * @param string $type
- * @throws Exception
- * @return array|string
+ * @param string|Raw|\Closure|array $field
+ *
+ * @return static
*/
- private function doInsert($data, $type)
+ public function groupBy($field)
{
- // If first value is not an array - it's not a batch insert
- if (is_array(current($data)) === false) {
- $queryObject = $this->getQuery($type, $data);
-
- $this->fireEvents('before-insert', $queryObject);
- list($result, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
- $return = $result->rowCount() === 1 ? $this->pdo->lastInsertId() : null;
- $this->fireEvents('after-insert', $queryObject, $return, $executionTime);
-
- return $return;
+ if (($field instanceof Raw) === false) {
+ $field = $this->addTablePrefix($field);
}
- // Perform batch insert
+ if (\is_array($field) === true) {
+ $this->statements['groupBys'] = array_merge($this->statements['groupBys'], $field);
+ } else {
+ $this->statements['groupBys'][] = $field;
+ }
- $return = [];
- foreach ($data as $subData) {
- $queryObject = $this->getQuery($type, $subData);
+ return $this;
+ }
- $this->fireEvents('before-insert', $queryObject);
- list($result, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
- $result = $result->rowCount() === 1 ? $this->pdo->lastInsertId() : null;
- $this->fireEvents('after-insert', $queryObject, $result, $executionTime);
+ /**
+ * Adds HAVING statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ * @param string|mixed $operator
+ * @param string|mixed $value
+ * @param string $joiner
+ *
+ * @return static
+ */
+ public function having($key, $operator, $value, $joiner = 'AND')
+ {
+ $key = $this->addTablePrefix($key);
+ $this->statements['havings'][] = compact('key', 'operator', 'value', 'joiner');
- $return[] = $result;
- }
+ return $this;
+ }
- return $return;
+ /**
+ * Adds new INNER JOIN statement to the current query.
+ *
+ * @param string|Raw|\Closure $table
+ * @param string|Raw|\Closure $key
+ * @param string|mixed|null $operator
+ * @param string|Raw|\Closure|null $value
+ *
+ * @return static
+ */
+ public function innerJoin($table, $key, $operator = null, $value = null)
+ {
+ return $this->join($table, $key, $operator, $value);
}
/**
* Insert key/value array
*
* @param array $data
+ *
* @throws Exception
* @return array|string
*/
- public function insert($data)
+ public function insert(array $data)
{
return $this->doInsert($data, 'insert');
}
@@ -456,6 +616,7 @@ public function insert($data)
* Insert with ignore key/value array
*
* @param array $data
+ *
* @throws Exception
* @return array|string
*/
@@ -465,203 +626,270 @@ public function insertIgnore($data)
}
/**
- * Replace key/value array
+ * Adds new JOIN statement to the current query.
*
- * @param array $data
- * @throws Exception
- * @return array|string
- */
- public function replace($data)
- {
- return $this->doInsert($data, 'replace');
- }
-
- /**
- * Update key/value array
+ * @param string|Raw|\Closure|array $table
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
+ * @param string|Raw|\Closure $value
+ * @param string $type
*
- * @param array $data
- * @throws Exception
- * @return \PDOStatement
+ * @return static
+ * ```
+ * Examples:
+ * - basic usage
+ * ->join('table2', 'table2.person_id', '=', 'table1.id');
+ *
+ * - as alias 'bar'
+ * ->join(['table2','bar'], 'bar.person_id', '=', 'table1.id');
+ *
+ * - complex usage
+ * ->join('another_table', function($table)
+ * {
+ * $table->on('another_table.person_id', '=', 'my_table.id');
+ * $table->on('another_table.person_id2', '=', 'my_table.id2');
+ * $table->orOn('another_table.age', '>', $queryBuilder->raw(1));
+ * })
+ * ```
*/
- public function update($data)
+ public function join($table, $key, $operator = null, $value = null, $type = 'inner')
{
+ if (($key instanceof \Closure) === false) {
+ $key = function (JoinBuilder $joinBuilder) use ($key, $operator, $value) {
+ $joinBuilder->on($key, $operator, $value);
+ };
+ }
+
/**
- * @var $response \PDOStatement
+ * Build a new JoinBuilder class, keep it by reference so any changes made
+ * in the closure should reflect here
*/
- $queryObject = $this->getQuery('update', $data);
- $this->fireEvents('before-update', $queryObject);
+ $joinBuilder = $this->container->build(JoinBuilder::class, [$this->connection]);
- list($response, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
- $this->fireEvents('after-update', $queryObject, $executionTime);
+ // Call the closure with our new joinBuilder object
+ $key($joinBuilder);
+ $table = $this->addTablePrefix($table, false);
- return $response;
+ // Get the criteria only query from the joinBuilder object
+ $this->statements['joins'][] = compact('type', 'table', 'joinBuilder');
+
+ return $this;
}
/**
- * Update or insert key/value array
+ * Adds new LEFT JOIN statement to the current query.
*
- * @param array $data
- * @return array|\PDOStatement|string
- * @throws Exception
+ * @param string|Raw|\Closure|array $table
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
+ * @param string|Raw|\Closure|null $value
+ *
+ * @return static
*/
- public function updateOrInsert($data)
+ public function leftJoin($table, $key, $operator = null, $value = null)
{
- if ($this->first() !== null) {
- return $this->update($data);
- }
-
- return $this->insert($data);
+ return $this->join($table, $key, $operator, $value, 'left');
}
/**
- * Add on duplicate key statement.
+ * Adds LIMIT statement to the current query.
+ *
+ * @param int $limit
*
- * @param string $data
* @return static
*/
- public function onDuplicateKeyUpdate($data)
+ public function limit($limit)
{
- $this->addStatement('onduplicate', $data);
+ $this->statements['limit'] = $limit;
return $this;
}
/**
- * Forms delete on the current query.
+ * Creates and returns new query.
*
- * @return \PDOStatement
- * @throws Exception
+ * @param \Pecee\Pixie\Connection|null $connection
+ *
+ * @throws \Pecee\Pixie\Exception
+ * @return static
*/
- public function delete()
+ public function newQuery(Connection $connection = null)
{
- /* @var $response \PDOStatement */
- $queryObject = $this->getQuery('delete');
-
- $this->fireEvents('before-delete', $queryObject);
-
- list($response, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
- $this->fireEvents('after-delete', $queryObject, $executionTime);
+ if ($connection === null) {
+ $connection = $this->connection;
+ }
- return $response;
+ return new static($connection);
}
/**
- * Sets the table that the query is using
+ * Adds OFFSET statement to the current query.
*
- * @param string|array $tables Single table or multiple tables as an array or as multiple parameters
- * @throws Exception
- * @return static
+ * @param int $offset
+ *
+ * @return static $this
*/
- public function table($tables)
+ public function offset($offset)
{
- if (is_array($tables) === false) {
- // Because a single table is converted to an array anyways, this makes sense.
- $tables = func_get_args();
- }
-
- $instance = new static($this->connection);
- $tables = $this->addTablePrefix($tables, false);
- $instance->addStatement('tables', $tables);
+ $this->statements['offset'] = $offset;
- return $instance;
+ return $this;
}
/**
- * Adds FROM statement to the current query.
+ * Add on duplicate key statement.
+ *
+ * @param string|array $data
*
- * @param string|array $tables
* @return static
*/
- public function from($tables)
+ public function onDuplicateKeyUpdate($data)
{
- if (is_array($tables) === false) {
- $tables = func_get_args();
- }
-
- $tables = $this->addTablePrefix($tables, false);
- $this->addStatement('tables', $tables);
+ $this->addStatement('onduplicate', $data);
return $this;
}
/**
- * Adds fields to select on the current query (defaults is all).
- * You can use key/value array to create alias.
- * Sub-queries and raw-objects are also supported.
+ * Adds OR HAVING statement to the current query.
*
- * Example: ['field' => 'alias'] will become `field` AS `alias`
+ * @param string|Raw|\Closure $key
+ * @param string|Raw|\Closure $operator
+ * @param mixed|Raw|\Closure|null $value
*
- * @param string|array $fields,...
* @return static
*/
- public function select($fields)
+ public function orHaving($key, $operator, $value)
{
- if (is_array($fields) === false) {
- $fields = func_get_args();
- }
+ return $this->having($key, $operator, $value, 'OR');
+ }
- $fields = $this->addTablePrefix($fields);
- $this->addStatement('selects', $fields);
+ /**
+ * Adds OR WHERE statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
+ * @param mixed|Raw|\Closure|null $value
+ *
+ * @return static
+ */
+ public function orWhere($key, $operator = null, $value = null)
+ {
+ // If two params are given then assume operator is =
+ if (\func_num_args() === 2) {
+ $value = $operator;
+ $operator = '=';
+ }
- return $this;
+ return $this->whereHandler($key, $operator, $value, 'OR');
}
/**
- * Performs select distinct on the current query.
+ * Adds OR WHERE BETWEEN statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ * @param string|integer|float $valueFrom
+ * @param string|integer|float $valueTo
*
- * @param string|Raw|\Closure|array $fields
* @return static
*/
- public function selectDistinct($fields)
+ public function orWhereBetween($key, $valueFrom, $valueTo)
{
- $this->select($fields);
- $this->addStatement('distinct', true);
+ return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'OR');
+ }
- return $this;
+ /**
+ * Adds OR WHERE IN statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ * @param array|Raw|\Closure $values
+ *
+ * @return static
+ */
+ public function orWhereIn($key, $values)
+ {
+ return $this->whereHandler($key, 'IN', $values, 'OR');
}
/**
- * Adds GROUP BY to the current query.
+ * Adds OR WHERE NOT statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
+ * @param mixed|Raw|\Closure|null $value
*
- * @param string|Raw|\Closure|array $field
* @return static
*/
- public function groupBy($field)
+ public function orWhereNot($key, $operator = null, $value = null)
{
- if (($field instanceof Raw) === false) {
- $field = $this->addTablePrefix($field);
+ // If two params are given then assume operator is =
+ if (\func_num_args() === 2) {
+ $value = $operator;
+ $operator = '=';
}
- if (is_array($field) === true) {
- $this->statements['groupBys'] = array_merge($this->statements['groupBys'], $field);
- } else {
- $this->statements['groupBys'][] = $field;
- }
+ return $this->whereHandler($key, $operator, $value, 'OR NOT');
+ }
- return $this;
+ /**
+ * Adds or WHERE NOT IN statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ * @param array|Raw|\Closure $values
+ *
+ * @return static
+ */
+ public function orWhereNotIn($key, $values)
+ {
+ return $this->whereHandler($key, 'NOT IN', $values, 'OR');
+ }
+
+ /**
+ * Adds OR WHERE NOT NULL statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ *
+ * @return static
+ */
+ public function orWhereNotNull($key)
+ {
+ return $this->whereNullHandler($key, 'NOT', 'or');
+ }
+
+ /**
+ * Adds OR WHERE NULL statement to the current query.
+ *
+ * @param string|Raw|\Closure $key
+ *
+ * @return static
+ */
+ public function orWhereNull($key)
+ {
+ return $this->whereNullHandler($key, '', 'or');
}
/**
* Adds ORDER BY statement to the current query.
*
* @param string|Raw|\Closure|array $fields
- * @param string $defaultDirection
+ * @param string $defaultDirection
+ *
* @return static
*/
public function orderBy($fields, $defaultDirection = 'ASC')
{
- if (is_array($fields) === false) {
+ if (\is_array($fields) === false) {
$fields = [$fields];
}
foreach ((array)$fields as $key => $value) {
$field = $key;
- $type = $value;
+ $type = $value;
- if (is_int($key) === true) {
+ if (\is_int($key) === true) {
$field = $value;
- $type = $defaultDirection;
+ $type = $defaultDirection;
}
if (($field instanceof Raw) === false) {
@@ -675,580 +903,478 @@ public function orderBy($fields, $defaultDirection = 'ASC')
}
/**
- * Adds LIMIT statement to the current query.
+ * Return PDO instance
*
- * @param int $limit
- * @return static
+ * @return PDO
*/
- public function limit($limit)
+ public function pdo()
{
- $this->statements['limit'] = $limit;
-
- return $this;
+ return $this->pdo;
}
/**
- * Adds OFFSET statement to the current query.
+ * Performs query.
*
- * @param int $offset
- * @return static $this
+ * @param string $sql
+ * @param array $bindings
+ *
+ * @return static
*/
- public function offset($offset)
+ public function query($sql, array $bindings = [])
{
- $this->statements['offset'] = $offset;
+ list($this->pdoStatement) = $this->statement($sql, $bindings);
return $this;
}
/**
- * Adds HAVING statement to the current query.
+ * Adds a raw string to the current query.
+ * This query will be ignored from any parsing or formatting by the Query builder
+ * and should be used in conjunction with other statements in the query.
*
- * @param string|Raw|\Closure $key
- * @param string|mixed $operator
- * @param string|mixed $value
- * @param string $joiner
- * @return static
+ * For example: $qb->where('result', '>', $qb->raw('COUNT(`score`)));
+ *
+ * @param string $value
+ * @param array|null|mixed $bindings ...
+ *
+ * @return Raw
*/
- public function having($key, $operator, $value, $joiner = 'AND')
+ public function raw($value, $bindings = null): Raw
{
- $key = $this->addTablePrefix($key);
- $this->statements['havings'][] = compact('key', 'operator', 'value', 'joiner');
+ if (\is_array($bindings) === false) {
+ $bindings = \func_get_args();
+ array_shift($bindings);
+ }
- return $this;
+ return $this->container->build(Raw::class, [$value, $bindings]);
}
/**
- * Adds OR HAVING statement to the current query.
+ * Register new event
*
- * @param string|Raw|\Closure $key
- * @param string|Raw|\Closure $operator
- * @param mixed|Raw|\Closure|null $value
- * @return static
+ * @param string $name
+ * @param string|null $table
+ * @param \Closure $action
+ *
+ * @return void
*/
- public function orHaving($key, $operator, $value)
+ public function registerEvent($name, $table = null, \Closure $action)
{
- return $this->having($key, $operator, $value, 'OR');
+ $this->connection->getEventHandler()->registerEvent($name, $table, $action);
}
/**
- * Adds WHERE statement to the current query.
+ * Remove event by event-name and/or table
*
- * @param string|Raw|\Closure $key
- * @param string|Raw|\Closure|null $operator
- * @param mixed|Raw|\Closure|null $value
- * @return static
+ * @param string $name
+ * @param string|null $table
+ *
+ * @return void
*/
- public function where($key, $operator = null, $value = null)
+ public function removeEvent($name, $table = null)
{
- // If two params are given then assume operator is =
- if (func_num_args() === 2) {
- $value = $operator;
- $operator = '=';
- }
-
- if (is_bool($value) === true) {
- $value = (int)$value;
- }
-
- return $this->whereHandler($key, $operator, $value);
+ $this->connection->getEventHandler()->removeEvent($name, $table);
}
/**
- * Adds OR WHERE statement to the current query.
+ * Replace key/value array
*
- * @param string|Raw|\Closure $key
- * @param string|null $operator
- * @param mixed|Raw|\Closure|null $value
- * @return static
+ * @param array $data
+ *
+ * @throws Exception
+ * @return array|string
*/
- public function orWhere($key, $operator = null, $value = null)
+ public function replace($data)
{
- // If two params are given then assume operator is =
- if (func_num_args() === 2) {
- $value = $operator;
- $operator = '=';
- }
-
- return $this->whereHandler($key, $operator, $value, 'OR');
+ return $this->doInsert($data, 'replace');
}
/**
- * Adds WHERE NOT statement to the current query.
+ * Adds new right join statement to the current query.
+ *
+ * @param string|Raw|\Closure|array $table
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
+ * @param string|Raw|\Closure|null $value
*
- * @param string|Raw|\Closure $key
- * @param string|array|Raw|\Closure|null $operator
- * @param mixed|Raw|\Closure|null $value
* @return static
*/
- public function whereNot($key, $operator = null, $value = null)
+ public function rightJoin($table, $key, $operator = null, $value = null)
{
- // If two params are given then assume operator is =
- if (func_num_args() === 2) {
- $value = $operator;
- $operator = '=';
- }
-
- return $this->whereHandler($key, $operator, $value, 'AND NOT');
+ return $this->join($table, $key, $operator, $value, 'right');
}
/**
- * Adds OR WHERE NOT statement to the current query.
+ * Adds fields to select on the current query (defaults is all).
+ * You can use key/value array to create alias.
+ * Sub-queries and raw-objects are also supported.
+ *
+ * Example: ['field' => 'alias'] will become `field` AS `alias`
+ *
+ * @param string|array $fields,...
*
- * @param string|Raw|\Closure $key
- * @param string|array|Raw|\Closure|null $operator
- * @param mixed|Raw|\Closure|null $value
* @return static
*/
- public function orWhereNot($key, $operator = null, $value = null)
+ public function select($fields)
{
- // If two params are given then assume operator is =
- if (func_num_args() === 2) {
- $value = $operator;
- $operator = '=';
+ if (\is_array($fields) === false) {
+ $fields = \func_get_args();
}
- return $this->whereHandler($key, $operator, $value, 'OR NOT');
+ $fields = $this->addTablePrefix($fields);
+ $this->addStatement('selects', $fields);
+
+ return $this;
}
/**
- * Adds WHERE IN statement to the current query.
+ * Performs select distinct on the current query.
+ *
+ * @param string|Raw|\Closure|array $fields
*
- * @param string|Raw|\Closure $key
- * @param array|Raw|\Closure $values
* @return static
*/
- public function whereIn($key, $values)
+ public function selectDistinct($fields)
{
- return $this->whereHandler($key, 'IN', $values);
+ $this->select($fields);
+ $this->addStatement('distinct', true);
+
+ return $this;
}
/**
- * Adds OR WHERE NOT IN statement to the current query.
+ * Set connection object
+ *
+ * @param Connection $connection
*
- * @param string|Raw|\Closure $key
- * @param array|Raw|\Closure $values
* @return static
*/
- public function whereNotIn($key, $values)
+ public function setConnection(Connection $connection)
{
- return $this->whereHandler($key, 'NOT IN', $values);
+ $this->connection = $connection;
+
+ return $this;
}
/**
- * Adds OR WHERE IN statement to the current query.
+ * Add fetch parameters to the PDO-query.
+ *
+ * @param mixed $parameters ...
*
- * @param string|Raw|\Closure $key
- * @param array|Raw|\Closure $values
* @return static
*/
- public function orWhereIn($key, $values)
+ public function setFetchMode($parameters = null)
{
- return $this->whereHandler($key, 'IN', $values, 'OR');
+ $this->fetchParameters = \func_get_args();
+
+ return $this;
}
/**
- * Adds or WHERE NOT IN statement to the current query.
+ * Execute statement
*
- * @param string|Raw|\Closure $key
- * @param array|Raw|\Closure $values
- * @return static
- */
- public function orWhereNotIn($key, $values)
- {
- return $this->whereHandler($key, 'NOT IN', $values, 'OR');
- }
-
- /**
- * Adds WHERE BETWEEN statement to the current query.
+ * @param string $sql
+ * @param array $bindings
*
- * @param string|Raw|\Closure $key
- * @param string|integer|float $valueFrom
- * @param string|integer|float $valueTo
- * @return static
+ * @return array PDOStatement and execution time as float
*/
- public function whereBetween($key, $valueFrom, $valueTo)
+ public function statement(string $sql, array $bindings = []): array
{
- return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo]);
- }
+ $start = microtime(true);
- /**
- * Adds OR WHERE BETWEEN statement to the current query.
- *
- * @param string|Raw|\Closure $key
- * @param string|integer|float $valueFrom
- * @param string|integer|float $valueTo
- * @return static
- */
- public function orWhereBetween($key, $valueFrom, $valueTo)
- {
- return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'OR');
- }
+ $pdoStatement = $this->pdo->prepare($sql);
- /**
- * Adds WHERE NULL statement to the current query.
- *
- * @param string|Raw|\Closure $key
- * @return QueryBuilderHandler
- */
- public function whereNull($key)
- {
- return $this->whereNullHandler($key);
- }
+ foreach ($bindings as $key => $value) {
+ $pdoStatement->bindValue(
+ \is_int($key) ? $key + 1 : $key,
+ $value,
+ (\is_int($value) || \is_bool($value)) ? PDO::PARAM_INT : PDO::PARAM_STR
+ );
+ }
- /**
- * Adds WHERE NOT NULL statement to the current query.
- *
- * @param string|Raw|\Closure $key
- * @return QueryBuilderHandler
- */
- public function whereNotNull($key)
- {
- return $this->whereNullHandler($key, 'NOT');
- }
+ $pdoStatement->execute();
- /**
- * Adds OR WHERE NULL statement to the current query.
- *
- * @param string|Raw|\Closure $key
- * @return QueryBuilderHandler
- */
- public function orWhereNull($key)
- {
- return $this->whereNullHandler($key, '', 'or');
+ return [$pdoStatement, microtime(true) - $start];
}
/**
- * Adds OR WHERE NOT NULL statement to the current query.
+ * Performs new sub-query.
+ * Call this method when you want to add a new sub-query in your where etc.
*
- * @param string|Raw|\Closure $key
- * @return QueryBuilderHandler
- */
- public function orWhereNotNull($key)
- {
- return $this->whereNullHandler($key, 'NOT', 'or');
- }
-
- /**
- * Handles WHERE NULL statements.
+ * @param QueryBuilderHandler $queryBuilder
+ * @param string|null $alias
*
- * @param string|Raw|\Closure $key
- * @param string $prefix
- * @param string $operator
- * @return mixed
+ * @throws Exception
+ * @return Raw
*/
- protected function whereNullHandler($key, $prefix = '', $operator = '')
+ public function subQuery(QueryBuilderHandler $queryBuilder, $alias = null): Raw
{
- $key = $this->adapterInstance->wrapSanitizer($this->addTablePrefix($key));
+ $sql = '(' . $queryBuilder->getQuery()->getRawSql() . ')';
+ if ($alias !== null) {
+ $sql = $sql . ' AS ' . $this->adapterInstance->wrapSanitizer($alias);
+ }
- return $this->{$operator . 'Where'}($this->raw("{$key} IS {$prefix} NULL"));
+ return $queryBuilder->raw($sql);
}
/**
- * Adds new JOIN statement to the current query.
+ * Sets the table that the query is using
*
- * @param string|Raw|\Closure $table
- * @param string|Raw|\Closure $key
- * @param string|null $operator
- * @param string|Raw|\Closure $value
- * @param string $type
+ * @param string|array $tables Single table or multiple tables as an array or as multiple parameters
+ * @throws Exception
* @return static
+ *
+ * ```
+ * Examples:
+ * - basic usage
+ * ->table('table_one')
+ * ->table(['table_one'])
+ *
+ * - with aliasing
+ * ->table(['table_one' => 'one'])
+ * ->table($qb->raw('table_one as one'))
+ * ```
*/
- public function join($table, $key, $operator = null, $value = null, $type = 'inner')
+ public function table($tables)
{
- if (($key instanceof \Closure) === false) {
- $key = function (JoinBuilder $joinBuilder) use ($key, $operator, $value) {
- $joinBuilder->on($key, $operator, $value);
- };
+ $tTables = [];
+ if (\is_array($tables) === false) {
+ // Because a single table is converted to an array anyways, this makes sense.
+ $tables = \func_get_args();
}
- /**
- * Build a new JoinBuilder class, keep it by reference so any changes made
- * in the closure should reflect here
- */
-
- $joinBuilder = $this->container->build(JoinBuilder::class, [$this->connection]);
-
- // Call the closure with our new joinBuilder object
- $key($joinBuilder);
- $table = $this->addTablePrefix($table, false);
+ $instance = new static($this->connection);
- // Get the criteria only query from the joinBuilder object
- $this->statements['joins'][] = compact('type', 'table', 'joinBuilder');
+ foreach ($tables as $key => $value) {
+ if (\is_string($key)) {
+ $instance->alias($value, $key);
+ $tTables[] = $key;
+ } else {
+ $tTables[] = $value;
+ }
+ }
+ $tTables = $this->addTablePrefix($tTables, false);
+ $instance->addStatement('tables', $tTables);
- return $this;
+ return $instance;
}
/**
* Performs the transaction
*
* @param \Closure $callback
+ * @throws \Exception
* @return static
*/
public function transaction(\Closure $callback)
{
+ /**
+ * Get the Transaction class
+ *
+ * @var \Pecee\Pixie\QueryBuilder\Transaction $queryTransaction
+ * @throws \Exception
+ */
+ $queryTransaction = $this->container->build(Transaction::class, [$this->connection]);
+ $inTransaction = $queryTransaction->inTransaction();
try {
// Begin the PDO transaction
- $this->pdo->beginTransaction();
-
- // Get the Transaction class
- $transaction = $this->container->build(Transaction::class, [$this->connection]);
+ $queryTransaction->begin($inTransaction);
// Call closure
- $callback($transaction);
+ $callback($queryTransaction);
// If no errors have been thrown or the transaction wasn't completed within the closure, commit the changes
- $this->pdo->commit();
+ $queryTransaction->commit($inTransaction);
return $this;
- } catch (TransactionHaltException $e) {
- // Commit or rollback behavior has been handled in the closure, so exit
- return $this;
+
} catch (\Exception $e) {
- // something happened, rollback changes
- $this->pdo->rollBack();
+ // something happened, rollback changes and throw Exception
+ $queryTransaction->rollBack($inTransaction);
- return $this;
+ throw $e;
}
}
/**
- * Adds new right join statement to the current query.
+ * Update key/value array
*
- * @param string|Raw|\Closure $table
- * @param string|Raw|\Closure $key
- * @param string|null $operator
- * @param string|Raw|\Closure|null $value
- * @return static
- */
- public function rightJoin($table, $key, $operator = null, $value = null)
- {
- return $this->join($table, $key, $operator, $value, 'right');
- }
-
- /**
- * Adds new LEFT JOIN statement to the current query.
+ * @param array $data
*
- * @param string|Raw|\Closure $table
- * @param string|Raw|\Closure $key
- * @param string|null $operator
- * @param string|Raw|\Closure|null $value
- * @return static
+ * @throws Exception
+ * @return \PDOStatement
*/
- public function leftJoin($table, $key, $operator = null, $value = null)
+ public function update($data)
{
- return $this->join($table, $key, $operator, $value, 'left');
- }
+ /**
+ * @var $response \PDOStatement
+ */
+ $queryObject = $this->getQuery('update', $data);
- /**
- * Adds new INNER JOIN statement to the current query.
- *
- * @param string|Raw|\Closure $table
- * @param string|Raw|\Closure $key
- * @param string|mixed|null $operator
- * @param string|Raw|\Closure|null $value
- * @return static
- */
- public function innerJoin($table, $key, $operator = null, $value = null)
- {
- return $this->join($table, $key, $operator, $value);
+ $this->fireEvents(static::EVENT_BEFORE_UPDATE, $queryObject);
+
+ list($response, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
+ $this->fireEvents(static::EVENT_AFTER_UPDATE, $queryObject, $executionTime);
+
+ return $response;
}
/**
- * Adds a raw string to the current query.
- * This query will be ignored from any parsing or formatting by the Query builder
- * and should be used in conjunction with other statements in the query.
+ * Update or insert key/value array
*
- * For example: $qb->where('result', '>', $qb->raw('COUNT(`score`)));
+ * @param array $data
*
- * @param string $value
- * @param array|null|mixed $bindings ...
- * @return Raw
+ * @return array|\PDOStatement|string
+ * @throws Exception
*/
- public function raw($value, $bindings = null)
+ public function updateOrInsert($data)
{
- if(is_array($bindings) === false) {
- $bindings = func_get_args();
- array_shift($bindings);
+ if ($this->first() !== null) {
+ return $this->update($data);
}
- return $this->container->build(Raw::class, [$value, $bindings]);
+ return $this->insert($data);
}
/**
- * Return PDO instance
+ * Adds WHERE statement to the current query.
*
- * @return PDO
- */
- public function pdo()
- {
- return $this->pdo;
- }
-
- /**
- * Set connection object
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
+ * @param mixed|Raw|\Closure|null $value
*
- * @param Connection $connection
* @return static
*/
- public function setConnection(Connection $connection)
+ public function where($key, $operator = null, $value = null)
{
- $this->connection = $connection;
+ // If two params are given then assume operator is =
+ if (\func_num_args() === 2) {
+ $value = $operator;
+ $operator = '=';
+ }
- return $this;
+ if (\is_bool($value) === true) {
+ $value = (int)$value;
+ }
+
+ return $this->whereHandler($key, $operator, $value);
}
/**
- * Get connection object
+ * Adds WHERE BETWEEN statement to the current query.
*
- * @return Connection
+ * @param string|Raw|\Closure $key
+ * @param string|integer|float $valueFrom
+ * @param string|integer|float $valueTo
+ *
+ * @return static
*/
- public function getConnection()
+ public function whereBetween($key, $valueFrom, $valueTo)
{
- return $this->connection;
+ return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo]);
}
/**
* Handles where statements
*
- * @param string|Raw|\Closure $key
- * @param string|Raw|\Closure|null $operator
+ * @param string|Raw|\Closure $key
+ * @param string|null $operator
* @param string|Raw|\Closure|null $value
- * @param string $joiner
+ * @param string $joiner
+ *
* @return static
*/
- protected function whereHandler($key, $operator = null, $value = null, $joiner = 'AND')
+ protected function whereHandler($key, string $operator = null, $value = null, $joiner = 'AND')
{
- $key = $this->addTablePrefix($key);
+ $key = $this->addTablePrefix($key);
$this->statements['wheres'][] = compact('key', 'operator', 'value', 'joiner');
return $this;
}
/**
- * Add table prefix (if given) on given string.
+ * Adds WHERE IN statement to the current query.
*
- * @param string|array|Raw|\Closure $values
- * @param bool $tableFieldMix If we have mixes of field and table names with a "."
- * @return array|string
+ * @param string|Raw|\Closure $key
+ * @param array|Raw|\Closure $values
+ *
+ * @return static
*/
- public function addTablePrefix($values, $tableFieldMix = true)
+ public function whereIn($key, $values)
{
- if ($this->tablePrefix === null) {
- return $values;
- }
-
- // $value will be an array and we will add prefix to all table names
- // If supplied value is not an array then make it one
-
- $single = false;
- if (is_array($values) === false) {
- $values = [$values];
-
- // We had single value, so should return a single value
- $single = true;
- }
-
- $return = [];
-
- foreach ($values as $key => $value) {
- // It's a raw query, just add it to our return array and continue next
- if ($value instanceof Raw || $value instanceof \Closure) {
- $return[$key] = $value;
- continue;
- }
-
- // If key is not integer, it is likely a alias mapping, so we need to change prefix target
- $target = &$value;
-
- if (is_int($key) === false) {
- $target = &$key;
- }
-
- if ($tableFieldMix === false || ($tableFieldMix && strpos($target, '.') !== false)) {
- $target = $this->tablePrefix . $target;
- }
-
- $return[$key] = $value;
- }
-
- // If we had single value then we should return a single value (end value of the array)
- return $single ? end($return) : $return;
+ return $this->whereHandler($key, 'IN', $values);
}
/**
- * Add new statement to statement-list
+ * Adds WHERE NOT statement to the current query.
*
- * @param string $key
- * @param mixed $value
+ * @param string|Raw|\Closure $key
+ * @param string|array|Raw|\Closure|null $operator
+ * @param mixed|Raw|\Closure|null $value
+ *
+ * @return static
*/
- protected function addStatement($key, $value)
+ public function whereNot($key, $operator = null, $value = null)
{
- if (array_key_exists($key, $this->statements) === false) {
- $this->statements[$key] = (array)$value;
- } else {
- $this->statements[$key] = array_merge($this->statements[$key], (array)$value);
+ // If two params are given then assume operator is =
+ if (\func_num_args() === 2) {
+ $value = $operator;
+ $operator = '=';
}
- }
- /**
- * Get event by event name
- *
- * @param string $name
- * @param string|null $table
- * @return \Closure|null
- */
- public function getEvent($name, $table = null)
- {
- return $this->connection->getEventHandler()->getEvent($name, $table);
+ return $this->whereHandler($key, $operator, $value, 'AND NOT');
}
/**
- * Register new event
+ * Adds OR WHERE NOT IN statement to the current query.
*
- * @param string $name
- * @param string|null $table
- * @param \Closure $action
- * @return void
+ * @param string|Raw|\Closure $key
+ * @param array|Raw|\Closure $values
+ *
+ * @return static
*/
- public function registerEvent($name, $table = null, \Closure $action)
+ public function whereNotIn($key, $values)
{
- $this->connection->getEventHandler()->registerEvent($name, $table, $action);
+ return $this->whereHandler($key, 'NOT IN', $values);
}
/**
- * Remove event by event-name and/or table
+ * Adds WHERE NOT NULL statement to the current query.
*
- * @param string $name
- * @param string|null $table
- * @return void
+ * @param string|Raw|\Closure $key
+ *
+ * @return static
*/
- public function removeEvent($name, $table = null)
+ public function whereNotNull($key)
{
- $this->connection->getEventHandler()->removeEvent($name, $table);
+ return $this->whereNullHandler($key, 'NOT');
}
/**
- * Fires event by given event name
+ * Adds WHERE NULL statement to the current query.
*
- * @param string $name
- * @param ... $parameters
- * @return mixed|null
+ * @param string|Raw|\Closure $key
+ *
+ * @return static
*/
- public function fireEvents($name, $parameters = null)
+ public function whereNull($key)
{
- $params = func_get_args();
- array_unshift($params, $this);
-
- return call_user_func_array([$this->connection->getEventHandler(), 'fireEvents'], $params);
+ return $this->whereNullHandler($key);
}
/**
- * Returns statements
+ * Handles WHERE NULL statements.
*
- * @return array
+ * @param string|Raw|\Closure $key
+ * @param string $prefix
+ * @param string $operator
+ *
+ * @return mixed
*/
- public function getStatements()
+ protected function whereNullHandler($key, $prefix = '', $operator = '')
{
- return $this->statements;
+ $key = $this->adapterInstance->wrapSanitizer($this->addTablePrefix($key));
+ $prefix = ($prefix !== '') ? $prefix . ' ' : $prefix;
+ return $this->{$operator . 'Where'}($this->raw("$key IS {$prefix}NULL"));
}
}
diff --git a/src/Pecee/Pixie/QueryBuilder/QueryObject.php b/src/Pecee/Pixie/QueryBuilder/QueryObject.php
index 68a5171..4bd1463 100644
--- a/src/Pecee/Pixie/QueryBuilder/QueryObject.php
+++ b/src/Pecee/Pixie/QueryBuilder/QueryObject.php
@@ -29,29 +29,30 @@ class QueryObject
* QueryObject constructor.
*
* @param string $sql
- * @param array $bindings
- * @param \PDO $pdo
+ * @param array $bindings
+ * @param \PDO $pdo
*/
- public function __construct($sql, array $bindings, \PDO $pdo)
+ public function __construct(string $sql, array $bindings, \PDO $pdo)
{
- $this->sql = (string)$sql;
+ $this->sql = $sql;
$this->bindings = $bindings;
- $this->pdo = $pdo;
+ $this->pdo = $pdo;
}
/**
* @return array
*/
- public function getBindings()
+ public function getBindings(): array
{
return $this->bindings;
}
/**
* Get the raw/bound sql
+ *
* @return string
*/
- public function getRawSql()
+ public function getRawSql(): string
{
return $this->interpolateQuery($this->sql, $this->bindings);
}
@@ -59,7 +60,7 @@ public function getRawSql()
/**
* @return string
*/
- public function getSql()
+ public function getSql(): string
{
return $this->sql;
}
@@ -71,25 +72,26 @@ public function getSql()
*
* Reference: http://stackoverflow.com/a/1376838/656489
*
- * @param string $query The sql query with parameter placeholders
- * @param array $params The array of substitution parameters
+ * @param string $query The sql query with parameter placeholders
+ * @param array $params The array of substitution parameters
+ *
* @return string The interpolated query
*/
protected function interpolateQuery($query, $params)
{
- $keys = [];
+ $keys = [];
$values = $params;
// build a regular expression for each parameter
foreach ($params as $key => $value) {
- $keys[] = '/' . (is_string($key) ? ':' . $key : '[?]') . '/';
+ $keys[] = '/' . (\is_string($key) ? ':' . $key : '[?]') . '/';
- if (is_string($value) === true) {
+ if (\is_string($value) === true) {
$values[$key] = $this->pdo->quote($value);
continue;
}
- if (is_array($value) === true) {
+ if (\is_array($value) === true) {
$values[$key] = $this->pdo->quote(implode(',', $value));
continue;
}
diff --git a/src/Pecee/Pixie/QueryBuilder/Raw.php b/src/Pecee/Pixie/QueryBuilder/Raw.php
index ad5dded..20a3803 100644
--- a/src/Pecee/Pixie/QueryBuilder/Raw.php
+++ b/src/Pecee/Pixie/QueryBuilder/Raw.php
@@ -1,4 +1,5 @@
value = (string)$value;
- $this->bindings = (array)$bindings;
+ $this->value = $value;
+ $this->bindings = $bindings;
}
/**
- * @return array
+ * @return string
*/
- public function getBindings()
+ public function __toString()
{
- return $this->bindings;
+ return (string)$this->value;
}
/**
- * @return string
+ * @return array
*/
- public function __toString()
+ public function getBindings(): array
{
- return (string)$this->value;
+ return $this->bindings;
}
}
diff --git a/src/Pecee/Pixie/QueryBuilder/Transaction.php b/src/Pecee/Pixie/QueryBuilder/Transaction.php
index ce86f82..d87332c 100644
--- a/src/Pecee/Pixie/QueryBuilder/Transaction.php
+++ b/src/Pecee/Pixie/QueryBuilder/Transaction.php
@@ -9,24 +9,63 @@
*/
class Transaction extends QueryBuilderHandler
{
+ /**
+ * Check if we are in transaction
+ *
+ * @return bool
+ */
+ public function inTransaction(): bool
+ {
+ return $this->pdo()->inTransaction();
+ }
+
+ /**
+ * Begin transaction
+ *
+ * @param bool $inTransaction
+ *
+ * @return $this
+ */
+ public function begin(bool $inTransaction = false)
+ {
+ if (false === $inTransaction) {
+ $this->pdo()->beginTransaction();
+ }
+
+ return $this;
+ }
/**
- * Commit the database changes
- * @throws TransactionHaltException
+ * Commit transaction
+ *
+ * @param bool $inTransaction
+ *
+ * @return $this
*/
- public function commit()
+ public function commit(bool $inTransaction = false)
{
- $this->pdo->commit();
- throw new TransactionHaltException('Commit');
+ if (false === $inTransaction) {
+ $this->pdo()->commit();
+ }
+
+ return $this;
}
/**
- * Rollback the database changes
- * @throws TransactionHaltException
+ * RollBack transaction
+ *
+ * @param bool $inTransaction
+ *
+ * @return $this
*/
- public function rollback()
+ public function rollBack(bool $inTransaction = false)
{
- $this->pdo->rollBack();
- throw new TransactionHaltException('Rollback');
+ if (false === $inTransaction) {
+ $this->pdo()->rollBack();
+ }
+
+ return $this;
}
+
+
}
diff --git a/src/Pecee/Pixie/QueryBuilder/TransactionHaltException.php b/src/Pecee/Pixie/QueryBuilder/TransactionHaltException.php
deleted file mode 100644
index 8c42b6e..0000000
--- a/src/Pecee/Pixie/QueryBuilder/TransactionHaltException.php
+++ /dev/null
@@ -1,12 +0,0 @@
-assertEquals('mysqlmock', $this->connection->getAdapter());
$this->assertEquals(array('prefix' => 'cb_'), $this->connection->getAdapterConfig());
}
-}
\ No newline at end of file
+}
diff --git a/tests/Pecee/Pixie/NoTableSubQueryTest.php b/tests/Pecee/Pixie/NoTableSubQueryTest.php
index de146f4..f99cd64 100644
--- a/tests/Pecee/Pixie/NoTableSubQueryTest.php
+++ b/tests/Pecee/Pixie/NoTableSubQueryTest.php
@@ -1,9 +1,14 @@
builder->table('mail')->select($this->builder->raw('COUNT(*)'));
$subQuery2 = $this->builder->table('event_message')->select($this->builder->raw('COUNT(*)'));
- $count = $this->builder->select($this->builder->subQuery($subQuery1, 'row1'), $this->builder->subQuery($subQuery2, 'row2'))->first();
+ $count = $this->builder->select($this->builder->subQuery($subQuery1, 'row1'),
+ $this->builder->subQuery($subQuery2, 'row2'))->first()
+ ;
- $this->assertEquals('SELECT (SELECT COUNT(*) FROM `cb_mail`) AS `row1`, (SELECT COUNT(*) FROM `cb_event_message`) AS `row2` LIMIT 1', $count);
+ $this->assertEquals('SELECT (SELECT COUNT(*) FROM `cb_mail`) AS `row1`, (SELECT COUNT(*) FROM `cb_event_message`) AS `row2` LIMIT 1',
+ $count);
}
-}
\ No newline at end of file
+}
diff --git a/tests/Pecee/Pixie/QueryBuilderBehaviorTest.php b/tests/Pecee/Pixie/QueryBuilderBehaviorTest.php
index 182aea9..70ebdda 100644
--- a/tests/Pecee/Pixie/QueryBuilderBehaviorTest.php
+++ b/tests/Pecee/Pixie/QueryBuilderBehaviorTest.php
@@ -1,9 +1,14 @@
builder = new QueryBuilder\QueryBuilderHandler($this->mockConnection);
}
- public function testSelectFlexibility()
- {
- $query = $this->builder
- ->select('foo')
- ->select(array('bar', 'baz'))
- ->select('qux', 'lol', 'wut')
- ->from('t');
- $this->assertEquals(
- 'SELECT `foo`, `bar`, `baz`, `qux`, `lol`, `wut` FROM `cb_t`',
- $query->getQuery()->getRawSql(),
- 'SELECT is pretty flexible!'
- );
- }
-
+ /**
+ * Test alias
+ */
public function testAlias()
{
$query = $this->builder
->table(['table1'])
- ->alias('table1', 't1')
- ->join('table2', 'table2.person_id', '=', 'foo2.id');
+ ->alias('t1')
+ ->join('table2', 'table2.person_id', '=', 'foo2.id')
+ ;
$this->assertEquals('SELECT * FROM `cb_table1` AS `t1` INNER JOIN `cb_table2` ON `cb_table2`.`person_id` = `cb_foo2`.`id`',
$query->getQuery()->getRawSql());
}
- public function testSelectQuery()
- {
- $subQuery = $this->builder->table('person_details')->select('details')->where('person_id', '=', 3);
-
- $query = $this->builder->table('my_table')
- ->select('my_table.*')
- ->select(array($this->builder->raw('count(cb_my_table.id) AS `tot`'), $this->builder->subQuery($subQuery, 'pop')))
- ->where('value', '=', 'Ifrah')
- ->whereNot('my_table.id', -1)
- ->orWhereNot('my_table.id', -2)
- ->orWhereIn('my_table.id', array(1, 2))
- ->groupBy(array('value', 'my_table.id', 'person_details.id'))
- ->orderBy('my_table.id', 'DESC')
- ->orderBy('value')
- ->having('tot', '<', 2)
- ->limit(1)
- ->offset(0)
- ->join(
- 'person_details',
- 'person_details.person_id',
- '=',
- 'my_table.id'
- )
- ;
-
- $nestedQuery = $this->builder->table($this->builder->subQuery($query, 'bb'))->select('*');
- $this->assertEquals("SELECT * FROM (SELECT `cb_my_table`.*, count(cb_my_table.id) AS `tot`, (SELECT `details` FROM `cb_person_details` WHERE `person_id` = 3) AS `pop` FROM `cb_my_table` INNER JOIN `cb_person_details` ON `cb_person_details`.`person_id` = `cb_my_table`.`id` WHERE `value` = 'Ifrah' AND NOT `cb_my_table`.`id` = -1 OR NOT `cb_my_table`.`id` = -2 OR `cb_my_table`.`id` IN (1, 2) GROUP BY `value`, `cb_my_table`.`id`, `cb_person_details`.`id` HAVING `tot` < 2 ORDER BY `cb_my_table`.`id` DESC, `value` ASC LIMIT 1 OFFSET 0) AS `bb`"
- , $nestedQuery->getQuery()->getRawSql());
- }
-
- public function testSelectAliases()
- {
- $query = $this->builder->from('my_table')->select('foo')->select(array('bar' => 'baz', 'qux'));
-
- $this->assertEquals(
- "SELECT `foo`, `bar` AS `baz`, `qux` FROM `cb_my_table`",
- $query->getQuery()->getRawSql()
- );
- }
-
- public function testRawStatementsWithinCriteria()
- {
- $query = $this->builder->from('my_table')
- ->where('simple', 'criteria')
- ->where($this->builder->raw('RAW'))
- ->where($this->builder->raw('PARAMETERIZED_ONE(?)', 'foo'))
- ->where($this->builder->raw('PARAMETERIZED_SEVERAL(?, ?, ?)', array(1, '2', 'foo')));
-
- $this->assertEquals(
- "SELECT * FROM `cb_my_table` WHERE `simple` = 'criteria' AND RAW AND PARAMETERIZED_ONE('foo') AND PARAMETERIZED_SEVERAL(1, '2', 'foo')",
- $query->getQuery()->getRawSql()
- );
- }
-
- public function testStandaloneWhereNot()
- {
- $query = $this->builder->table('my_table')->whereNot('foo', 1);
- $this->assertEquals("SELECT * FROM `cb_my_table` WHERE NOT `foo` = 1", $query->getQuery()->getRawSql());
- }
-
- public function testSelectDistinct()
- {
- $query = $this->builder->selectDistinct(array('name', 'surname'))->from('my_table');
- $this->assertEquals("SELECT DISTINCT `name`, `surname` FROM `cb_my_table`", $query->getQuery()->getRawSql());
- }
-
- public function testSelectDistinctWithSingleColumn()
- {
- $query = $this->builder->selectDistinct('name')->from('my_table');
- $this->assertEquals("SELECT DISTINCT `name` FROM `cb_my_table`", $query->getQuery()->getRawSql());
- }
-
- public function testSelectDistinctAndSelectCalls()
- {
- $query = $this->builder->select('name')->selectDistinct('surname')->select(array('birthday', 'address'))->from('my_table');
- $this->assertEquals("SELECT DISTINCT `name`, `surname`, `birthday`, `address` FROM `cb_my_table`", $query->getQuery()->getRawSql());
- }
-
- public function testSelectQueryWithNestedCriteriaAndJoins()
- {
- $builder = $this->builder;
-
- $query = $builder->table('my_table')
- ->where('my_table.id', '>', 1)
- ->orWhere('my_table.id', 1)
- ->where(function($q)
- {
- $q->where('value', 'LIKE', '%sana%');
- $q->orWhere(function($q2)
- {
- $q2->where('key', 'LIKE', '%sana%');
- $q2->orWhere('value', 'LIKE', '%sana%');
- });
- })
- ->join(array('person_details', 'a'), 'a.person_id', '=', 'my_table.id')
-
- ->leftJoin(array('person_details', 'b'), function($table) use ($builder)
- {
- $table->on('b.person_id', '=', 'my_table.id');
- $table->on('b.deleted', '=', $builder->raw(0));
- $table->orOn('b.age', '>', $builder->raw(1));
- })
- ;
-
- $this->assertEquals("SELECT * FROM `cb_my_table` INNER JOIN `cb_person_details` AS `cb_a` ON `cb_a`.`person_id` = `cb_my_table`.`id` LEFT JOIN `cb_person_details` AS `cb_b` ON `cb_b`.`person_id` = `cb_my_table`.`id` AND `cb_b`.`deleted` = 0 OR `cb_b`.`age` > 1 WHERE `cb_my_table`.`id` > 1 OR `cb_my_table`.`id` = 1 AND (`value` LIKE '%sana%' OR (`key` LIKE '%sana%' OR `value` LIKE '%sana%'))"
- , $query->getQuery()->getRawSql());
- }
-
- public function testSelectWithQueryEvents()
+ /**
+ * Test delete
+ */
+ public function testDeleteQuery()
{
- $builder = $this->builder;
-
- $builder->registerEvent('before-select', ':any', function($qb)
- {
- $qb->whereIn('status', array(1, 2));
- });
+ $this->builder = new QueryBuilder\QueryBuilderHandler($this->mockConnection);
- $query = $builder->table('some_table')->where('name', 'Some');
- $query->get();
- $actual = $query->getQuery()->getRawSql();
+ $builder = $this->builder->table('my_table')->where('value', '=', 'Amrin');
- $this->assertEquals("SELECT * FROM `cb_some_table` WHERE `name` = 'Some' AND `status` IN (1, 2)", $actual);
+ $this->assertEquals("DELETE FROM `cb_my_table` WHERE `value` = 'Amrin'"
+ , $builder->getQuery('delete')->getRawSql());
}
public function testEventPropagation()
@@ -190,81 +74,78 @@ public function testEventPropagation()
});
}
- $builder->table('foo')->insert(array('bar' => 'baz'));
+ $builder->table('foo')->insert(['bar' => 'baz']);
$builder->from('foo')->select('bar')->get();
- $builder->table('foo')->update(array('bar' => 'baz'));
+ $builder->table('foo')->update(['bar' => 'baz']);
$builder->from('foo')->delete();
$this->assertEquals($triggeredEvents, $events);
}
- public function testInsertQuery()
- {
- $builder = $this->builder->from('my_table');
- $data = array('key' => 'Name',
- 'value' => 'Sana',);
-
- $this->assertEquals("INSERT INTO `cb_my_table` (`key`,`value`) VALUES ('Name','Sana')"
- , $builder->getQuery('insert', $data)->getRawSql());
- }
-
public function testInsertIgnoreQuery()
{
$builder = $this->builder->from('my_table');
- $data = array('key' => 'Name',
- 'value' => 'Sana',);
+ $data = [
+ 'key' => 'Name',
+ 'value' => 'Sana',
+ ];
$this->assertEquals("INSERT IGNORE INTO `cb_my_table` (`key`,`value`) VALUES ('Name','Sana')"
, $builder->getQuery('insertignore', $data)->getRawSql());
}
- public function testReplaceQuery()
- {
- $builder = $this->builder->from('my_table');
- $data = array('key' => 'Name',
- 'value' => 'Sana',);
-
- $this->assertEquals("REPLACE INTO `cb_my_table` (`key`,`value`) VALUES ('Name','Sana')"
- , $builder->getQuery('replace', $data)->getRawSql());
- }
-
public function testInsertOnDuplicateKeyUpdateQuery()
{
- $builder = $this->builder;
- $data = array(
- 'name' => 'Sana',
- 'counter' => 1
- );
- $dataUpdate = array(
- 'name' => 'Sana',
- 'counter' => 2
- );
+ $builder = $this->builder;
+ $data = [
+ 'name' => 'Sana',
+ 'counter' => 1,
+ ];
+ $dataUpdate = [
+ 'name' => 'Sana',
+ 'counter' => 2,
+ ];
$builder->from('my_table')->onDuplicateKeyUpdate($dataUpdate);
$this->assertEquals("INSERT INTO `cb_my_table` (`name`,`counter`) VALUES ('Sana',1) ON DUPLICATE KEY UPDATE `name`='Sana',`counter`=2"
, $builder->getQuery('insert', $data)->getRawSql());
}
- public function testUpdateQuery()
+ public function testInsertQuery()
{
- $builder = $this->builder->table('my_table')->where('value', 'Sana');
-
- $data = array(
- 'key' => 'Sana',
- 'value' => 'Amrin',
- );
+ $builder = $this->builder->from('my_table');
+ $data = [
+ 'key' => 'Name',
+ 'value' => 'Sana',
+ ];
- $this->assertEquals("UPDATE `cb_my_table` SET `key`='Sana',`value`='Amrin' WHERE `value` = 'Sana'"
- , $builder->getQuery('update', $data)->getRawSql());
+ $this->assertEquals("INSERT INTO `cb_my_table` (`key`,`value`) VALUES ('Name','Sana')"
+ , $builder->getQuery('insert', $data)->getRawSql());
}
- public function testDeleteQuery()
+ public function testIsPossibleToUseSubqueryInWhereClause()
{
- $this->builder = new QueryBuilder\QueryBuilderHandler($this->mockConnection);
-
- $builder = $this->builder->table('my_table')->where('value', '=', 'Amrin');
+ $sub = clone $this->builder;
+ $query = $this->builder->from('my_table')->whereIn('foo', $this->builder->subQuery(
+ $sub->from('some_table')->select('foo')->where('id', 1)
+ ))
+ ;
+ $this->assertEquals(
+ "SELECT * FROM `cb_my_table` WHERE `foo` IN (SELECT `foo` FROM `cb_some_table` WHERE `id` = 1)",
+ $query->getQuery()->getRawSql()
+ );
+ }
- $this->assertEquals("DELETE FROM `cb_my_table` WHERE `value` = 'Amrin'"
- , $builder->getQuery('delete')->getRawSql());
+ public function testIsPossibleToUseSubqueryInWhereNotClause()
+ {
+ $sub = clone $this->builder;
+ $query = $this->builder->from('my_table')->whereNotIn('foo', $this->builder->subQuery(
+ $sub->from('some_table')->select('foo')->where('id', 1)
+ ))
+ ;
+ $this->assertEquals(
+ "SELECT * FROM `cb_my_table` WHERE `foo` NOT IN (SELECT `foo` FROM `cb_some_table` WHERE `id` = 1)",
+ $query->getQuery()->getRawSql()
+ );
}
public function testOrderByFlexibility()
@@ -272,8 +153,9 @@ public function testOrderByFlexibility()
$query = $this->builder
->from('t')
->orderBy('foo', 'DESC')
- ->orderBy(array('bar', 'baz' => 'ASC', $this->builder->raw('raw1')), 'DESC')
- ->orderBy($this->builder->raw('raw2'), 'DESC');
+ ->orderBy(['bar', 'baz' => 'ASC', $this->builder->raw('raw1')], 'DESC')
+ ->orderBy($this->builder->raw('raw2'), 'DESC')
+ ;
$this->assertEquals(
'SELECT * FROM `cb_t` ORDER BY `foo` DESC, `bar` DESC, `baz` ASC, raw1 DESC, raw2 DESC',
@@ -282,41 +164,206 @@ public function testOrderByFlexibility()
);
}
- public function testSelectQueryWithNull()
+ public function testRawStatementsWithinCriteria()
{
$query = $this->builder->from('my_table')
- ->whereNull('key1')
- ->orWhereNull('key2')
- ->whereNotNull('key3')
- ->orWhereNotNull('key4');
+ ->where('simple', 'criteria')
+ ->where($this->builder->raw('RAW'))
+ ->where($this->builder->raw('PARAMETERIZED_ONE(?)', 'foo'))
+ ->where($this->builder->raw('PARAMETERIZED_SEVERAL(?, ?, ?)', [1, '2', 'foo']))
+ ;
$this->assertEquals(
- "SELECT * FROM `cb_my_table` WHERE `key1` IS NULL OR `key2` IS NULL AND `key3` IS NOT NULL OR `key4` IS NOT NULL",
+ "SELECT * FROM `cb_my_table` WHERE `simple` = 'criteria' AND RAW AND PARAMETERIZED_ONE('foo') AND PARAMETERIZED_SEVERAL(1, '2', 'foo')",
$query->getQuery()->getRawSql()
);
}
- public function testIsPossibleToUseSubqueryInWhereClause()
+ public function testReplaceQuery()
{
- $sub = clone $this->builder;
- $query = $this->builder->from('my_table')->whereIn('foo', $this->builder->subQuery(
- $sub->from('some_table')->select('foo')->where('id', 1)
- ));
+ $builder = $this->builder->from('my_table');
+ $data = [
+ 'key' => 'Name',
+ 'value' => 'Sana',
+ ];
+
+ $this->assertEquals("REPLACE INTO `cb_my_table` (`key`,`value`) VALUES ('Name','Sana')"
+ , $builder->getQuery('replace', $data)->getRawSql());
+ }
+
+ public function testSelectAliases()
+ {
+ $query = $this->builder->from('my_table')->select('foo')->select(['bar' => 'baz', 'qux']);
+
$this->assertEquals(
- "SELECT * FROM `cb_my_table` WHERE `foo` IN (SELECT `foo` FROM `cb_some_table` WHERE `id` = 1)",
+ "SELECT `foo`, `bar` AS `baz`, `qux` FROM `cb_my_table`",
$query->getQuery()->getRawSql()
);
}
- public function testIsPossibleToUseSubqueryInWhereNotClause()
+ public function testSelectDistinct()
{
- $sub = clone $this->builder;
- $query = $this->builder->from('my_table')->whereNotIn('foo', $this->builder->subQuery(
- $sub->from('some_table')->select('foo')->where('id', 1)
- ));
+ $query = $this->builder->selectDistinct(['name', 'surname'])->from('my_table');
+ $this->assertEquals("SELECT DISTINCT `name`, `surname` FROM `cb_my_table`", $query->getQuery()->getRawSql());
+ }
+
+ public function testSelectDistinctAndSelectCalls()
+ {
+ $query = $this->builder->select('name')->selectDistinct('surname')->select(['birthday', 'address'])->from('my_table');
+ $this->assertEquals("SELECT DISTINCT `name`, `surname`, `birthday`, `address` FROM `cb_my_table`", $query->getQuery()->getRawSql());
+ }
+
+ public function testSelectDistinctWithSingleColumn()
+ {
+ $query = $this->builder->selectDistinct('name')->from('my_table');
+ $this->assertEquals("SELECT DISTINCT `name` FROM `cb_my_table`", $query->getQuery()->getRawSql());
+ }
+
+ public function testSelectFlexibility()
+ {
+ $query = $this->builder
+ ->select('foo')
+ ->select(['bar', 'baz'])
+ ->select('qux', 'lol', 'wut')
+ ->from('t')
+ ;
$this->assertEquals(
- "SELECT * FROM `cb_my_table` WHERE `foo` NOT IN (SELECT `foo` FROM `cb_some_table` WHERE `id` = 1)",
+ 'SELECT `foo`, `bar`, `baz`, `qux`, `lol`, `wut` FROM `cb_t`',
+ $query->getQuery()->getRawSql(),
+ 'SELECT is pretty flexible!'
+ );
+ }
+
+ public function testSelectQuery()
+ {
+ $subQuery = $this->builder->table('person_details')->select('details')->where('person_id', '=', 3);
+
+ $query = $this->builder->table('my_table')
+ ->select('my_table.*')
+ ->select([$this->builder->raw('count(cb_my_table.id) AS `tot`'), $this->builder->subQuery($subQuery, 'pop')])
+ ->where('value', '=', 'Ifrah')
+ ->whereNot('my_table.id', -1)
+ ->orWhereNot('my_table.id', -2)
+ ->orWhereIn('my_table.id', [1, 2])
+ ->groupBy(['value', 'my_table.id', 'person_details.id'])
+ ->orderBy('my_table.id', 'DESC')
+ ->orderBy('value')
+ ->having('tot', '<', 2)
+ ->limit(1)
+ ->offset(0)
+ ->join(
+ 'person_details',
+ 'person_details.person_id',
+ '=',
+ 'my_table.id'
+ )
+ ;
+
+ $nestedQuery = $this->builder->table($this->builder->subQuery($query, 'bb'))->select('*');
+ $this->assertEquals("SELECT * FROM (SELECT `cb_my_table`.*, count(cb_my_table.id) AS `tot`, (SELECT `details` FROM `cb_person_details` WHERE `person_id` = 3) AS `pop` FROM `cb_my_table` INNER JOIN `cb_person_details` ON `cb_person_details`.`person_id` = `cb_my_table`.`id` WHERE `value` = 'Ifrah' AND NOT `cb_my_table`.`id` = -1 OR NOT `cb_my_table`.`id` = -2 OR `cb_my_table`.`id` IN (1, 2) GROUP BY `value`, `cb_my_table`.`id`, `cb_person_details`.`id` HAVING `tot` < 2 ORDER BY `cb_my_table`.`id` DESC, `value` ASC LIMIT 1 OFFSET 0) AS `bb`"
+ , $nestedQuery->getQuery()->getRawSql());
+ }
+
+ public function testSelectQueryWithNestedCriteriaAndJoins()
+ {
+ $builder = $this->builder;
+
+ $query = $builder->table('my_table')
+ ->where('my_table.id', '>', 1)
+ ->orWhere('my_table.id', 1)
+ ->where(function ($q) {
+ $q->where('value', 'LIKE', '%sana%');
+ $q->orWhere(function ($q2) {
+ $q2->where('key', 'LIKE', '%sana%');
+ $q2->orWhere('value', 'LIKE', '%sana%');
+ });
+ })
+ ->join(['person_details', 'a'], 'a.person_id', '=', 'my_table.id')
+ ->leftJoin(['person_details', 'b'], function ($table) use ($builder) {
+ $table->on('b.person_id', '=', 'my_table.id');
+ $table->on('b.deleted', '=', $builder->raw(0));
+ $table->orOn('b.age', '>', $builder->raw(1));
+ })
+ ;
+
+ $this->assertEquals("SELECT * FROM `cb_my_table` INNER JOIN `cb_person_details` AS `cb_a` ON `cb_a`.`person_id` = `cb_my_table`.`id` LEFT JOIN `cb_person_details` AS `cb_b` ON `cb_b`.`person_id` = `cb_my_table`.`id` AND `cb_b`.`deleted` = 0 OR `cb_b`.`age` > 1 WHERE `cb_my_table`.`id` > 1 OR `cb_my_table`.`id` = 1 AND (`value` LIKE '%sana%' OR (`key` LIKE '%sana%' OR `value` LIKE '%sana%'))"
+ , $query->getQuery()->getRawSql());
+ }
+
+ public function testSelectQueryWithNull()
+ {
+ $query = $this->builder->from('my_table')
+ ->whereNull('key1')
+ ->orWhereNull('key2')
+ ->whereNotNull('key3')
+ ->orWhereNotNull('key4')
+ ->orWhere('key5', '=', null)
+ ;
+
+ $this->assertEquals(
+ "SELECT * FROM `cb_my_table` WHERE `key1` IS NULL OR `key2` IS NULL AND `key3` IS NOT NULL OR `key4` IS NOT NULL OR `key5` = NULL",
$query->getQuery()->getRawSql()
);
}
+
+ public function testSelectWithQueryEvents()
+ {
+ $builder = $this->builder;
+
+ $builder->registerEvent('before-select', ':any', function ($qb) {
+ $qb->whereIn('status', [1, 2]);
+ });
+
+ $query = $builder->table('some_table')->where('name', 'Some');
+ $query->get();
+ $actual = $query->getQuery()->getRawSql();
+
+ $this->assertEquals("SELECT * FROM `cb_some_table` WHERE `name` = 'Some' AND `status` IN (1, 2)", $actual);
+ }
+
+ public function testStandaloneWhereNot()
+ {
+ $query = $this->builder->table('my_table')->whereNot('foo', 1);
+ $this->assertEquals("SELECT * FROM `cb_my_table` WHERE NOT `foo` = 1", $query->getQuery()->getRawSql());
+ }
+
+ public function testUpdateQuery()
+ {
+ $builder = $this->builder->table('my_table')->where('value', 'Sana');
+
+ $data = [
+ 'key' => 'Sana',
+ 'value' => 'Amrin',
+ ];
+
+ $this->assertEquals("UPDATE `cb_my_table` SET `key`='Sana',`value`='Amrin' WHERE `value` = 'Sana'"
+ , $builder->getQuery('update', $data)->getRawSql());
+ }
+
+ public function testFromSubQuery() {
+
+ $subQuery = $this->builder->table('person');
+ $builder = $this->builder->table($this->builder->subQuery($subQuery))->where('id', '=', 2);
+
+ $this->assertEquals('SELECT * FROM (SELECT * FROM `cb_person`) WHERE `id` = 2', $builder->getQuery()->getRawSql());
+
+ }
+
+ public function testTableAlias() {
+
+ $builder = $this->builder->table('persons')->alias('staff');
+
+ $this->assertEquals('SELECT * FROM `cb_persons` AS `staff`', $builder->getQuery()->getRawSql());
+
+ }
+
+ public function testWhereNotNullSubQuery() {
+ $subQuery = $this->builder->table('persons')->alias('staff');
+
+ $query = $this->builder->whereNull($this->builder->subQuery($subQuery));
+
+ $this->assertEquals('SELECT * WHERE (SELECT * FROM `cb_persons` AS `staff`) IS NULL', $query->getQuery()->getRawSql());
+
+ }
+
}
diff --git a/tests/Pecee/Pixie/QueryBuilderTest.php b/tests/Pecee/Pixie/QueryBuilderTest.php
index 080cb2b..0acc645 100644
--- a/tests/Pecee/Pixie/QueryBuilderTest.php
+++ b/tests/Pecee/Pixie/QueryBuilderTest.php
@@ -1,10 +1,15 @@
builder = new QueryBuilderHandler($this->mockConnection);
}
- public function testRawQuery()
+ public function testFalseBoolWhere()
{
- $query = 'select * from cb_my_table where id = ? and name = ?';
- $bindings = array(5, 'usman');
- $queryArr = $this->builder->query($query, $bindings)->get();
- $this->assertEquals(
- array(
- $query,
- array(array(5, PDO::PARAM_INT), array('usman', PDO::PARAM_STR)),
- ),
- $queryArr
- );
+ $result = $this->builder->table('test')->where('id', '=', false);
+ $this->assertEquals('SELECT * FROM `cb_test` WHERE `id` = 0', $result->getQuery()->getRawSql());
}
public function testInsertQueryReturnsIdForInsert()
@@ -38,17 +38,20 @@ public function testInsertQueryReturnsIdForInsert()
$this->mockPdoStatement
->expects($this->once())
->method('rowCount')
- ->will($this->returnValue(1));
+ ->will($this->returnValue(1))
+ ;
$this->mockPdo
->expects($this->once())
->method('lastInsertId')
- ->will($this->returnValue(11));
+ ->will($this->returnValue(11))
+ ;
- $id = $this->builder->table('test')->insert(array(
- 'id' => 5,
- 'name' => 'usman'
- ));
+ $id = $this->builder->table('test')->insert([
+ 'id' => 5,
+ 'name' => 'usman',
+ ])
+ ;
$this->assertEquals(11, $id);
}
@@ -58,17 +61,20 @@ public function testInsertQueryReturnsIdForInsertIgnore()
$this->mockPdoStatement
->expects($this->once())
->method('rowCount')
- ->will($this->returnValue(1));
+ ->will($this->returnValue(1))
+ ;
$this->mockPdo
->expects($this->once())
->method('lastInsertId')
- ->will($this->returnValue(11));
+ ->will($this->returnValue(11))
+ ;
- $id = $this->builder->table('test')->insertIgnore(array(
- 'id' => 5,
- 'name' => 'usman'
- ));
+ $id = $this->builder->table('test')->insertIgnore([
+ 'id' => 5,
+ 'name' => 'usman',
+ ])
+ ;
$this->assertEquals(11, $id);
}
@@ -78,19 +84,30 @@ public function testInsertQueryReturnsNullForIgnoredInsert()
$this->mockPdoStatement
->expects($this->once())
->method('rowCount')
- ->will($this->returnValue(0));
+ ->will($this->returnValue(0))
+ ;
- $id = $this->builder->table('test')->insertIgnore(array(
- 'id' => 5,
- 'name' => 'usman'
- ));
+ $id = $this->builder->table('test')->insertIgnore([
+ 'id' => 5,
+ 'name' => 'usman',
+ ])
+ ;
$this->assertEquals(null, $id);
}
- public function testFalseBoolWhere() {
- $result = $this->builder->table('test')->where('id', '=', false);
- $this->assertEquals('SELECT * FROM `cb_test` WHERE `id` = 0', $result->getQuery()->getRawSql());
+ public function testRawQuery()
+ {
+ $query = 'select * from cb_my_table where id = ? and name = ?';
+ $bindings = [5, 'usman'];
+ $queryArr = $this->builder->query($query, $bindings)->get();
+ $this->assertEquals(
+ [
+ $query,
+ [[5, PDO::PARAM_INT], ['usman', PDO::PARAM_STR]],
+ ],
+ $queryArr
+ );
}
-}
\ No newline at end of file
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index a07098c..afcba2b 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -1,75 +1,111 @@
container = new Container();
- $this->mockPdoStatement = $this->getMock(\PDOStatement::class);
+ $this->mockPdoStatement = $this->getMockBuilder(\PDOStatement::class)->getMock();
- $mockPdoStatement = & $this->mockPdoStatement;
+ $mockPdoStatement = &$this->mockPdoStatement;
- $mockPdoStatement->bindings = array();
+ $mockPdoStatement->bindings = [];
$this->mockPdoStatement
->expects($this->any())
->method('bindValue')
->will($this->returnCallback(function ($parameter, $value, $dataType) use ($mockPdoStatement) {
- $mockPdoStatement->bindings[] = array($value, $dataType);
- }));
+ $mockPdoStatement->bindings[] = [$value, $dataType];
+ }))
+ ;
$this->mockPdoStatement
->expects($this->any())
->method('execute')
- ->will($this->returnCallback(function($bindings = null) use ($mockPdoStatement) {
+ ->will($this->returnCallback(function ($bindings = null) use ($mockPdoStatement) {
if ($bindings) {
$mockPdoStatement->bindings = $bindings;
}
- }));
+ }))
+ ;
$this->mockPdoStatement
->expects($this->any())
->method('fetchAll')
- ->will($this->returnCallback(function() use ($mockPdoStatement){
- return array($mockPdoStatement->sql, $mockPdoStatement->bindings);
- }));
+ ->will($this->returnCallback(function () use ($mockPdoStatement) {
+ return [$mockPdoStatement->sql, $mockPdoStatement->bindings];
+ }))
+ ;
- $this->mockPdo = $this->getMock(MockPdo::class, array('prepare', 'setAttribute', 'quote', 'lastInsertId'));
+ $this->mockPdo = $this
+ ->getMockBuilder(MockPdo::class)
+ ->setMethods(['prepare', 'setAttribute', 'quote', 'lastInsertId'])
+ ->getMock()
+ ;
$this->mockPdo
->expects($this->any())
->method('prepare')
- ->will($this->returnCallback(function($sql) use ($mockPdoStatement){
+ ->will($this->returnCallback(function ($sql) use ($mockPdoStatement) {
$mockPdoStatement->sql = $sql;
+
return $mockPdoStatement;
- }));
+ }))
+ ;
$this->mockPdo
->expects($this->any())
->method('quote')
- ->will($this->returnCallback(function($value){
+ ->will($this->returnCallback(function ($value) {
return "'$value'";
- }));
+ }))
+ ;
$eventHandler = new EventHandler();
$this->mockConnection = m::mock(Connection::class);
$this->mockConnection->shouldReceive('getPdoInstance')->andReturn($this->mockPdo);
$this->mockConnection->shouldReceive('getAdapter')->andReturn('mysql');
- $this->mockConnection->shouldReceive('getAdapterConfig')->andReturn(array('prefix' => 'cb_'));
+ $this->mockConnection->shouldReceive('getAdapterConfig')->andReturn(['prefix' => 'cb_']);
$this->mockConnection->shouldReceive('getContainer')->andReturn($this->container);
$this->mockConnection->shouldReceive('getEventHandler')->andReturn($eventHandler);
}
@@ -78,18 +114,20 @@ public function tearDown()
{
m::close();
}
-
- public function callbackMock()
- {
- $args = func_get_args();
- return count($args) == 1 ? $args[0] : $args;
- }
}
+/**
+ * Class MockPdo
+ *
+ * @package Pecee\Pixie
+ */
class MockPdo extends \PDO
{
+ /**
+ * MockPdo constructor.
+ */
public function __construct()
{
}
-}
\ No newline at end of file
+}