├── .gitignore ├── .php_cs ├── .scrutinizer.yml ├── .styleci.yml ├── .travis.yml ├── build ├── split-faster.sh ├── split-full.sh └── split.sh ├── composer.json ├── contributing.md ├── license.txt ├── phpunit.xml ├── readme.md ├── src └── Fist │ ├── Container │ ├── .gitignore │ ├── .travis.yml │ ├── composer.json │ ├── license.txt │ ├── phpunit.xml │ ├── src │ │ ├── BindingException.php │ │ └── Container.php │ └── tests │ │ ├── ContainerTest.php │ │ └── bootstrap.php │ ├── Database │ ├── .gitignore │ ├── .travis.yml │ ├── composer.json │ ├── license.txt │ ├── phpunit.xml │ ├── readme.md │ ├── src │ │ ├── Connectors │ │ │ ├── Connection.php │ │ │ ├── ConnectionInterface.php │ │ │ ├── MysqlConnection.php │ │ │ └── SqliteConnection.php │ │ ├── Database.php │ │ ├── DatabaseException.php │ │ └── Query │ │ │ ├── Builder.php │ │ │ ├── Grammars │ │ │ ├── Grammar.php │ │ │ ├── GrammarInterface.php │ │ │ ├── MysqlGrammar.php │ │ │ └── SqliteGrammar.php │ │ │ └── Statement.php │ └── tests │ │ ├── DatabaseConnectionTest.php │ │ └── bootstrap.php │ ├── Facade │ ├── .gitignore │ ├── .travis.yml │ ├── composer.json │ ├── license.txt │ ├── phpunit.xml │ ├── src │ │ ├── ContainerFacade.php │ │ ├── ContainerFacadeInterface.php │ │ ├── Facade.php │ │ ├── FacadeInterface.php │ │ └── InvalidArgumentException.php │ └── tests │ │ ├── FacadeTest.php │ │ └── bootstrap.php │ ├── Repository │ ├── .gitignore │ ├── .travis.yml │ ├── composer.json │ ├── license.txt │ ├── phpunit.xml │ ├── src │ │ ├── ArrayRepository.php │ │ ├── ContainerRepository.php │ │ └── RepositoryInterface.php │ └── tests │ │ ├── RepositoryTest.php │ │ └── bootstrap.php │ ├── Routing │ ├── .gitignore │ ├── .travis.yml │ ├── composer.json │ ├── license.txt │ ├── phpunit.xml │ ├── src │ │ ├── MethodNotAllowedException.php │ │ ├── NotFoundException.php │ │ ├── Route.php │ │ └── Router.php │ └── tests │ │ ├── RoutingTest.php │ │ └── bootstrap.php │ └── Testing │ ├── .gitignore │ ├── .travis.yml │ ├── composer.json │ ├── license.txt │ ├── phpunit.xml │ ├── src │ ├── TestCase.php │ └── WithDatabase.php │ └── tests │ └── bootstrap.php └── tests └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .subsplit/ 4 | build/coverage.txt 5 | build/coverage/ 6 | build/logs/ 7 | build/report.junit.xml 8 | build/report.tap 9 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | =7.0" 19 | }, 20 | "replace": { 21 | "fist/container": "self.version", 22 | "fist/database": "self.version", 23 | "fist/repository": "self.version", 24 | "fist/routing": "self.version", 25 | "fist/facade": "self.version", 26 | "fist/testing": "self.version" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "~6.3", 30 | "codacy/coverage": "dev-master", 31 | "sllh/php-cs-fixer-styleci-bridge": "^2.1" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Fist\\Container\\": "src/Fist/Container/src/", 36 | "Fist\\Database\\": "src/Fist/Database/src/", 37 | "Fist\\Repository\\": "src/Fist/Repository/src/", 38 | "Fist\\Routing\\": "src/Fist/Routing/src/", 39 | "Fist\\Facade\\": "src/Fist/Facade/src/", 40 | "Fist\\Testing\\": "src/Fist/Testing/src/" 41 | } 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-master": "1.0-dev" 46 | } 47 | }, 48 | "minimum-stability": "dev" 49 | } 50 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | ## Bug Reports 4 | 5 | When you file a bug report, your issue should contain a title and a clear description of the issue. You should also include as much relevant information as possible and a code sample that demonstrates the issue. The goal of a bug report is to make it easy for yourself - and others - to replicate the bug and develop a fix. 6 | 7 | A bug report may also be sent in the form of a pull request containing a failing test. 8 | 9 | ## Branching 10 | 11 | All bug fixes should be sent to the latest stable branch. Bug fixes should never be sent to the master branch unless they fix features that exist only in the upcoming release. 12 | 13 | Minor features that are fully backwards compatible with the current Fistlab release may be sent to the latest stable branch. 14 | 15 | Major new features should always be sent to the master branch, which contains the upcoming Fistlab release. 16 | 17 | If you are unsure if your feature qualifies as a major or minor, please ask Mark Topper at mark@ulties.com. 18 | 19 | ## Coding Style 20 | 21 | Fistlab follows the PSR-2 coding standard and the PSR-4 autoloading standard. 22 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./src/Fist/*/tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ./src/Fist/*/src 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # The Fistlab PHP Components 2 | 3 | [![StyleCI](https://styleci.io/repos/67337527/shield?style=flat)](https://styleci.io/repos/67337527) 4 | [![Build Status](https://travis-ci.org/fistlab/php.svg)](https://travis-ci.org/fistlab/php) 5 | [![Total Downloads](https://poser.pugx.org/fistlab/php/d/total.svg)](https://packagist.org/packages/fistlab/php) 6 | [![Latest Stable Version](https://poser.pugx.org/fistlab/php/v/stable.svg)](https://packagist.org/packages/fistlab/php) 7 | [![Latest Unstable Version](https://poser.pugx.org/fistlab/php/v/unstable.svg)](https://packagist.org/packages/fistlab/php) 8 | [![License](https://poser.pugx.org/fistlab/php/license.svg)](https://packagist.org/packages/fistlab/php) 9 | [![Code Climate](https://codeclimate.com/github/fistlab/php/badges/gpa.svg)](https://codeclimate.com/github/fistlab/php) 10 | [![Test Coverage](https://codeclimate.com/github/fistlab/php/badges/coverage.svg)](https://codeclimate.com/github/fistlab/php/coverage) 11 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fistlab/php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fistlab/php/?branch=master) 12 | [![Code Coverage](https://scrutinizer-ci.com/g/fistlab/php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/fistlab/php/?branch=master) 13 | [![Build Status](https://scrutinizer-ci.com/g/fistlab/php/badges/build.png?b=master)](https://scrutinizer-ci.com/g/fistlab/php/build-status/master) 14 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/17d30f51b41c432293de5ff2d4a75bf8)](https://www.codacy.com/app/marktopper/php?utm_source=github.com&utm_medium=referral&utm_content=fistlab/php&utm_campaign=Badge_Grade) 15 | [![codecov](https://codecov.io/gh/fistlab/php/branch/master/graph/badge.svg)](https://codecov.io/gh/fistlab/php) 16 | [![Open Source Helpers](https://www.codetriage.com/fistlab/php/badges/users.svg)](https://www.codetriage.com/fistlab/php) 17 | 18 | ## Idea 19 | 20 | The idea is to create a set of components in various programming languages, which however works almost the same way. This way it will be easier for developers to go around and play with another programming language without having to find a whole new set of components and dig into how it works. 21 | Current programming languages: [php](https://github.com/fistlab/php) 22 | 23 | ## Components 24 | 25 | * [Container](https://github.com/fistphp/container) 26 | * [Database](https://github.com/fistphp/database) 27 | * [Repository](https://github.com/fistphp/repository) 28 | 29 | ## Contributing 30 | 31 | Thank you for considering contributing to the Fistlab components! The contribution guide can be found [here](https://github.com/fistlab/php/blob/master/contributing.md). 32 | 33 | ## Security Vulnerabilities 34 | 35 | If you discover a security vulnerability within the components, please send an e-mail to Mark Topper at mark@ulties.com. All security vulnerabilities will be promptly addressed. 36 | 37 | ## License 38 | 39 | The Fistlab Components is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). 40 | -------------------------------------------------------------------------------- /src/Fist/Container/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .subsplit/ 4 | -------------------------------------------------------------------------------- /src/Fist/Container/.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | 8 | env: 9 | global: 10 | - setup=basic 11 | 12 | matrix: 13 | include: 14 | - php: 5.6 15 | env: setup=lowest 16 | - php: 5.6 17 | env: setup=stable 18 | 19 | sudo: false 20 | 21 | before_install: 22 | - if [[ $TRAVIS_PHP_VERSION != 7.1 ]] ; then phpenv config-rm xdebug.ini; fi 23 | - travis_retry composer self-update 24 | 25 | install: 26 | - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-dist --no-suggest; fi 27 | - if [[ $setup = 'stable' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest; fi 28 | - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable --no-suggest; fi 29 | 30 | script: vendor/bin/phpunit 31 | 32 | matrix: 33 | fast_finish: true 34 | -------------------------------------------------------------------------------- /src/Fist/Container/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fist/container", 3 | "description": "The Fistlab PHP Container Components", 4 | "keywords": ["fistlab", "fistphp", "component", "container"], 5 | "license": "MIT", 6 | "homepage": "https://github.com/fistphp/container", 7 | "support": { 8 | "issues": "https://github.com/fistlab/php/issues", 9 | "source": "https://github.com/fistlab/php" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "Mark Topper", 14 | "email": "mark@ulties.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.6.4" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~5.4", 22 | "fist/testing": "self.version" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Fist\\Container\\": "src/" 27 | } 28 | }, 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "1.0-dev" 32 | } 33 | }, 34 | "minimum-stability": "dev" 35 | } 36 | -------------------------------------------------------------------------------- /src/Fist/Container/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fist/Container/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fist/Container/src/BindingException.php: -------------------------------------------------------------------------------- 1 | resolvingListeners[$name])) { 48 | $this->resolvingListeners[$name] = []; 49 | } 50 | 51 | $this->resolvingListeners[$name][] = $closure; 52 | } 53 | 54 | public function bind($name, $closure, $shared = false) 55 | { 56 | $name = $this->normalize($name); 57 | 58 | $bound = $this->bound($name); 59 | 60 | $this->bindings[$name] = [ 61 | 'concrete' => $closure, 62 | 'shared' => $shared, 63 | ]; 64 | 65 | if ($bound) { 66 | $this->rebound($name); 67 | } 68 | } 69 | 70 | protected function getCallReflector($callback) 71 | { 72 | if (is_string($callback) && strpos($callback, '::') !== false) { 73 | $callback = explode('::', $callback); 74 | } 75 | 76 | if (is_array($callback)) { 77 | return new ReflectionMethod($callback[0], $callback[1]); 78 | } 79 | 80 | return new ReflectionFunction($callback); 81 | } 82 | 83 | protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters, &$dependencies) 84 | { 85 | if (array_key_exists($parameter->name, $parameters)) { 86 | $dependencies[] = $parameters[$parameter->name]; 87 | 88 | unset($parameters[$parameter->name]); 89 | } elseif ($parameter->getClass()) { 90 | $dependencies[] = $this->make($parameter->getClass()->name); 91 | } elseif ($parameter->isDefaultValueAvailable()) { 92 | $dependencies[] = $parameter->getDefaultValue(); 93 | } 94 | } 95 | 96 | protected function getMethodDependencies($callback, array $parameters = []) 97 | { 98 | $dependencies = []; 99 | 100 | foreach ($this->getCallReflector($callback)->getParameters() as $parameter) { 101 | $this->addDependencyForCallParameter($parameter, $parameters, $dependencies); 102 | } 103 | 104 | return array_merge($dependencies, $parameters); 105 | } 106 | 107 | protected function isCallableWithAtSign($callback) 108 | { 109 | return is_string($callback) && strpos($callback, '@') !== false; 110 | } 111 | 112 | protected function callClass($target, array $parameters = [], $defaultMethod = null) 113 | { 114 | $segments = explode('@', $target); 115 | 116 | $method = count($segments) == 2 ? $segments[1] : $defaultMethod; 117 | 118 | if (is_null($method)) { 119 | throw new InvalidArgumentException('Method not provided.'); 120 | } 121 | 122 | return $this->call([$this->make($segments[0]), $method], $parameters); 123 | } 124 | 125 | public function call($callback, array $parameters = [], $defaultMethod = null) 126 | { 127 | if ($this->isCallableWithAtSign($callback) || $defaultMethod) { 128 | return $this->callClass($callback, $parameters, $defaultMethod); 129 | } 130 | 131 | $dependencies = $this->getMethodDependencies($callback, $parameters); 132 | 133 | return call_user_func_array($callback, $dependencies); 134 | } 135 | 136 | public function instance($name, $instance) 137 | { 138 | $name = $this->normalize($name); 139 | 140 | $bound = $this->bound($name); 141 | 142 | $this->instances[$name] = $instance; 143 | 144 | if ($bound) { 145 | $this->rebound($name); 146 | } 147 | } 148 | 149 | protected function rebound($name) 150 | { 151 | if (isset($this->rebindingListeners[$name])) { 152 | foreach ($this->rebindingListeners[$name] as $listener) { 153 | $listener(); 154 | } 155 | } 156 | } 157 | 158 | public function rebinding($name, Closure $closure) 159 | { 160 | $name = $this->normalize($name); 161 | 162 | if (! isset($this->rebindingListeners[$name])) { 163 | $this->rebindingListeners[$name] = []; 164 | } 165 | 166 | $this->rebindingListeners[$name][] = $closure; 167 | } 168 | 169 | public function contextual($name, $needs, $give) 170 | { 171 | $name = $this->normalize($name); 172 | $needs = $this->normalize($needs); 173 | $give = $this->normalize($give); 174 | 175 | if (! isset($this->contextual[$name])) { 176 | $this->contextual[$name] = []; 177 | } 178 | 179 | $this->contextual[$name][$needs] = $give; 180 | } 181 | 182 | protected function getContextualConcrete($abstract) 183 | { 184 | $build = end($this->buildStack); 185 | 186 | if (isset($this->contextual[$build][$abstract])) { 187 | return $this->contextual[$build][$abstract]; 188 | } 189 | } 190 | 191 | protected function keyParametersByArgument(array $dependencies, array $parameters) 192 | { 193 | foreach ($parameters as $key => $value) { 194 | if (is_numeric($key)) { 195 | unset($parameters[$key]); 196 | 197 | $parameters[$dependencies[$key]->name] = $value; 198 | } 199 | } 200 | 201 | return $parameters; 202 | } 203 | 204 | protected function getConcrete($name) 205 | { 206 | if (! is_null($concrete = $this->getContextualConcrete($name))) { 207 | return $concrete; 208 | } 209 | 210 | if (isset($this->bindings[$name])) { 211 | return $this->bindings[$name]['concrete']; 212 | } 213 | 214 | return $name; 215 | } 216 | 217 | public function make($name, array $parameters = []) 218 | { 219 | $name = $this->normalize($name); 220 | 221 | if (isset($this->instances[$name])) { 222 | return $this->instances[$name]; 223 | } 224 | 225 | $concrete = $this->getConcrete($name); 226 | 227 | if ($this->isBuildable($concrete, $name)) { 228 | $object = $this->build($concrete, $parameters); 229 | } else { 230 | $object = $this->make($concrete, $parameters); 231 | } 232 | 233 | foreach ($this->getExtenders($name) as $extender) { 234 | $object = $extender($object, $this); 235 | } 236 | 237 | if ($this->isShared($name)) { 238 | $this->instances[$name] = $object; 239 | } 240 | 241 | $this->fireResolvingCallbacks($name, $object); 242 | 243 | return $object; 244 | } 245 | 246 | protected function fireResolvingCallbacks($name, $object) 247 | { 248 | if (isset($this->resolvingListeners[$name])) { 249 | foreach ($this->resolvingListeners[$name] as $listener) { 250 | $listener($object); 251 | } 252 | } 253 | } 254 | 255 | public function extend($name, Closure $closure) 256 | { 257 | $name = $this->normalize($name); 258 | 259 | if (isset($this->instances[$name])) { 260 | $this->instances[$name] = $closure($this->instances[$name], $this); 261 | 262 | $this->rebound($name); 263 | } else { 264 | $this->extenders[$name][] = $closure; 265 | } 266 | } 267 | 268 | protected function isBuildable($concrete, $abstract) 269 | { 270 | return $concrete === $abstract || $concrete instanceof Closure; 271 | } 272 | 273 | public function build($concrete, array $parameters = []) 274 | { 275 | if ($concrete instanceof Closure) { 276 | return $concrete($this, $parameters); 277 | } 278 | 279 | $reflector = new ReflectionClass($concrete); 280 | 281 | if (! $reflector->isInstantiable()) { 282 | if (! empty($this->buildStack)) { 283 | $previous = implode(', ', $this->buildStack); 284 | 285 | $message = "Target [$concrete] is not instantiable while building [$previous]."; 286 | } else { 287 | $message = "Target [$concrete] is not instantiable."; 288 | } 289 | 290 | throw new BindingException($message); 291 | } 292 | 293 | $this->buildStack[] = $concrete; 294 | 295 | $constructor = $reflector->getConstructor(); 296 | 297 | if (is_null($constructor)) { 298 | array_pop($this->buildStack); 299 | 300 | return new $concrete(); 301 | } 302 | 303 | $dependencies = $constructor->getParameters(); 304 | 305 | $parameters = $this->keyParametersByArgument( 306 | $dependencies, $parameters 307 | ); 308 | 309 | $instances = $this->getDependencies( 310 | $dependencies, $parameters 311 | ); 312 | 313 | array_pop($this->buildStack); 314 | 315 | return $reflector->newInstanceArgs($instances); 316 | } 317 | 318 | protected function getDependencies(array $parameters, array $primitives = []) 319 | { 320 | $dependencies = []; 321 | 322 | foreach ($parameters as $parameter) { 323 | $dependency = $parameter->getClass(); 324 | 325 | if (array_key_exists($parameter->name, $primitives)) { 326 | $dependencies[] = $primitives[$parameter->name]; 327 | } elseif (is_null($dependency)) { 328 | $dependencies[] = $this->resolveNonClass($parameter); 329 | } else { 330 | $dependencies[] = $this->resolveClass($parameter); 331 | } 332 | } 333 | 334 | return $dependencies; 335 | } 336 | 337 | protected function resolveNonClass(ReflectionParameter $parameter) 338 | { 339 | if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) { 340 | if ($concrete instanceof Closure) { 341 | return call_user_func($concrete, $this); 342 | } else { 343 | return $concrete; 344 | } 345 | } 346 | 347 | if ($parameter->isDefaultValueAvailable()) { 348 | return $parameter->getDefaultValue(); 349 | } 350 | 351 | $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->name}"; 352 | 353 | throw new BindingException($message); 354 | } 355 | 356 | public function getExtenders($name) 357 | { 358 | return isset($this->extenders[$name]) ? $this->extenders[$name] : []; 359 | } 360 | 361 | protected function resolveClass(ReflectionParameter $parameter) 362 | { 363 | try { 364 | return $this->make($parameter->getClass()->name); 365 | } catch (BindingException $e) { 366 | if ($parameter->isOptional()) { 367 | return $parameter->getDefaultValue(); 368 | } 369 | 370 | throw $e; 371 | } 372 | } 373 | 374 | public function isShared($name) 375 | { 376 | return isset($this->bindings[$name]) && $this->bindings[$name]['shared']; 377 | } 378 | 379 | public function bound($name) 380 | { 381 | $name = $this->normalize($name); 382 | 383 | return isset($this->bindings[$name]) || isset($this->instances[$name]); 384 | } 385 | 386 | public function singleton($name, Closure $closure) 387 | { 388 | $this->bind($name, $closure, true); 389 | } 390 | 391 | public function normalize($name) 392 | { 393 | return is_string($name) ? ltrim($name, '\\') : $name; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/Fist/Container/tests/ContainerTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Container::class, $container); 13 | } 14 | 15 | public function testInstancing() 16 | { 17 | $application = new Container(); 18 | 19 | Container::setInstance($application); 20 | 21 | $this->assertInstanceOf(Container::class, Container::getInstance()); 22 | $this->assertSame($application, Container::getInstance()); 23 | } 24 | 25 | public function testAutoInstancing() 26 | { 27 | Container::removeInstance(); 28 | 29 | $this->assertSame(null, Container::getInstance()); 30 | 31 | $application = new Container(); 32 | 33 | $this->assertInstanceOf(Container::class, Container::getInstance()); 34 | $this->assertSame($application, Container::getInstance()); 35 | } 36 | 37 | public function testBinding() 38 | { 39 | $container = new Container(); 40 | 41 | $container->bind('foo', function () { 42 | return 'bar'; 43 | }); 44 | 45 | $this->assertEquals('bar', $container->make('foo')); 46 | 47 | $container->bind(stdClass::class, function () { 48 | return new stdClass(); 49 | }); 50 | 51 | $this->assertNotSame( 52 | $container->make(stdClass::class), 53 | $container->make(stdClass::class) 54 | ); 55 | } 56 | 57 | public function testBound() 58 | { 59 | $container = new Container(); 60 | 61 | $this->assertFalse($container->bound('foo')); 62 | 63 | $container->bind('foo', function () { 64 | return 'bar'; 65 | }); 66 | 67 | $this->assertTrue($container->bound('foo')); 68 | } 69 | 70 | public function testBindingShared() 71 | { 72 | $container = new Container(); 73 | 74 | $class = new stdClass(); 75 | 76 | $container->singleton('class', function () use ($class) { 77 | return $class; 78 | }); 79 | 80 | $this->assertSame($class, $container->make('class')); 81 | } 82 | 83 | public function testSlashesAreHandled() 84 | { 85 | $container = new Container(); 86 | 87 | $container->bind('\Foo', function () { 88 | return 'bar'; 89 | }); 90 | 91 | $this->assertEquals('bar', $container->make('Foo')); 92 | } 93 | 94 | public function testParametersCanOverrideDependencies() 95 | { 96 | $container = new Container(); 97 | 98 | $stub = new ContainerDependentTestStub( 99 | $mock = $this->createMock(ContainerStubInterface::class) 100 | ); 101 | 102 | $resolved = $container->make(ContainerNestedDependentTestStub::class, [$stub]); 103 | 104 | $this->assertInstanceOf(ContainerNestedDependentTestStub::class, $resolved); 105 | 106 | $this->assertEquals($mock, $resolved->stub->implementation); 107 | } 108 | 109 | public function testContainerIsPassed() 110 | { 111 | $container = new Container(); 112 | 113 | $container->bind('container', function (Container $container) { 114 | return $container; 115 | }); 116 | 117 | $this->assertSame( 118 | $container, 119 | $container->make('container') 120 | ); 121 | } 122 | 123 | public function testOverrideBindings() 124 | { 125 | $container = new Container(); 126 | 127 | $container->bind('foo', function () { 128 | return 'bar'; 129 | }); 130 | 131 | $this->assertEquals('bar', $container->make('foo')); 132 | 133 | $container->bind('foo', function () { 134 | return 'baz'; 135 | }); 136 | 137 | $this->assertEquals('baz', $container->make('foo')); 138 | } 139 | 140 | public function testExtendedBindings() 141 | { 142 | $container = new Container(); 143 | 144 | $container->bind('foo', function () { 145 | return 'foo'; 146 | }); 147 | 148 | $container->extend('foo', function ($old) { 149 | return $old.'bar'; 150 | }); 151 | 152 | $container->extend('foo', function ($old) { 153 | return $old.'baz'; 154 | }); 155 | 156 | $this->assertEquals('foobarbaz', $container->make('foo')); 157 | } 158 | 159 | public function testExtendIsLazyInitialized() 160 | { 161 | $container = new Container(); 162 | 163 | $container->extend(ContainerLazyExtendTestStub::class, function (ContainerLazyExtendTestStub $object) { 164 | $object->init(); 165 | 166 | return $object; 167 | }); 168 | 169 | $this->assertFalse(ContainerLazyExtendTestStub::$initialized); 170 | 171 | $container->make(ContainerLazyExtendTestStub::class); 172 | 173 | $this->assertTrue(ContainerLazyExtendTestStub::$initialized); 174 | } 175 | 176 | public function testExtendCanBeCalledBeforeBind() 177 | { 178 | $container = new Container(); 179 | 180 | $container->extend('foo', function ($old) { 181 | return $old.'bar'; 182 | }); 183 | 184 | $container->bind('foo', function () { 185 | return 'foo'; 186 | }); 187 | 188 | $this->assertEquals('foobar', $container->make('foo')); 189 | } 190 | 191 | public function testParametersCanBePassedThroughToClosure() 192 | { 193 | $container = new Container(); 194 | 195 | $container->bind('foo', function (Container $container, $parameters) { 196 | return $parameters; 197 | }); 198 | 199 | $this->assertEquals([1, 2, 3], $container->make('foo', [1, 2, 3])); 200 | } 201 | 202 | public function testResolutionOfDefaultParameters() 203 | { 204 | $container = new Container(); 205 | 206 | $instance = $container->make(ContainerDefaultValueTestStub::class); 207 | 208 | $this->assertInstanceOf(ContainerTestStub::class, $instance->stub); 209 | 210 | $this->assertEquals('mark', $instance->name); 211 | } 212 | 213 | public function testResolvingCallbacksAreCalled() 214 | { 215 | $container = new Container(); 216 | 217 | $container->resolving('foo', function ($object) { 218 | return $object->name = 'mark'; 219 | }); 220 | 221 | $container->bind('foo', function () { 222 | return new stdClass(); 223 | }); 224 | 225 | $instance = $container->make('foo'); 226 | 227 | $this->assertEquals('mark', $instance->name); 228 | } 229 | 230 | public function testReboundListeners() 231 | { 232 | $test = new stdClass(); 233 | $test->rebound = false; 234 | 235 | $container = new Container(); 236 | 237 | $container->bind('foo', function () { 238 | }); 239 | 240 | $container->rebinding('foo', function () use ($test) { 241 | $test->rebound = true; 242 | }); 243 | 244 | $this->assertFalse($test->rebound); 245 | 246 | $container->bind('foo', function () { 247 | }); 248 | 249 | $this->assertTrue($test->rebound); 250 | } 251 | 252 | public function testReboundListenersOnInstances() 253 | { 254 | $test = new stdClass(); 255 | $test->rebound = false; 256 | 257 | $container = new Container(); 258 | 259 | $container->instance('foo', function () { 260 | }); 261 | 262 | $container->rebinding('foo', function () use ($test) { 263 | $test->rebound = true; 264 | }); 265 | 266 | $container->instance('foo', function () { 267 | }); 268 | 269 | $this->assertTrue($test->rebound); 270 | } 271 | 272 | public function testPassingSomePrimitiveParameters() 273 | { 274 | $container = new Container(); 275 | 276 | $value = $container->make(ContainerMixedPrimitiveTestStub::class, [ 277 | 'first' => 'mark', 278 | 'last' => 'topper', 279 | ]); 280 | 281 | $this->assertInstanceOf(ContainerMixedPrimitiveTestStub::class, $value); 282 | 283 | $this->assertEquals('mark', $value->first); 284 | 285 | $this->assertEquals('topper', $value->last); 286 | 287 | $this->assertInstanceOf(ContainerTestStub::class, $value->stub); 288 | 289 | $container = new Container(); 290 | 291 | $value = $container->make(ContainerMixedPrimitiveTestStub::class, [ 292 | 0 => 'mark', 293 | 2 => 'topper', 294 | ]); 295 | 296 | $this->assertInstanceOf(ContainerMixedPrimitiveTestStub::class, $value); 297 | 298 | $this->assertEquals('mark', $value->first); 299 | 300 | $this->assertEquals('topper', $value->last); 301 | 302 | $this->assertInstanceOf(ContainerTestStub::class, $value->stub); 303 | } 304 | 305 | public function testCreatingBoundConcreteClassPassesParameters() 306 | { 307 | $container = new Container(); 308 | 309 | $container->bind('TestAbstractClass', ContainerConstructorParameterLoggingTestStub::class); 310 | 311 | $parameters = ['foo', 'bar']; 312 | 313 | $instance = $container->make('TestAbstractClass', $parameters); 314 | 315 | $this->assertInstanceOf(ContainerConstructorParameterLoggingTestStub::class, $instance); 316 | 317 | $this->assertEquals($parameters, $instance->receivedParameters); 318 | } 319 | 320 | /** 321 | * @expectedException Fist\Container\BindingException 322 | * @expectedExceptionMessage Unresolvable dependency resolving [Parameter #0 [ $first ]] in class ContainerMixedPrimitiveTestStub 323 | */ 324 | public function testInternalClassWithDefaultParameters() 325 | { 326 | $container = new Container(); 327 | 328 | $container->make(ContainerMixedPrimitiveTestStub::class, []); 329 | } 330 | 331 | /** 332 | * @expectedException Fist\Container\BindingException 333 | * @expectedExceptionMessage Target [ContainerStubInterface] is not instantiable. 334 | */ 335 | public function testBindingResolutionExceptionMessage() 336 | { 337 | $container = new Container(); 338 | 339 | $container->make(ContainerStubInterface::class, []); 340 | } 341 | 342 | /** 343 | * @expectedException Fist\Container\BindingException 344 | * @expectedExceptionMessage Target [ContainerStubInterface] is not instantiable while building [ContainerTestContextInjectOne]. 345 | */ 346 | public function testBindingResolutionExceptionMessageIncludesBuildStack() 347 | { 348 | $container = new Container(); 349 | 350 | $container->make(ContainerTestContextInjectOne::class, []); 351 | } 352 | 353 | public function testCallWithDependencies() 354 | { 355 | $container = new Container(); 356 | 357 | $result = $container->call(function (stdClass $foo, $bar = []) { 358 | return func_get_args(); 359 | }); 360 | 361 | $this->assertInstanceOf(stdClass::class, $result[0]); 362 | 363 | $this->assertEquals([], $result[1]); 364 | 365 | $result = $container->call(function (stdClass $foo, $bar = []) { 366 | return func_get_args(); 367 | }, ['bar' => 'mark']); 368 | 369 | $this->assertInstanceOf(stdClass::class, $result[0]); 370 | 371 | $this->assertEquals('mark', $result[1]); 372 | } 373 | 374 | /** 375 | * @expectedException ReflectionException 376 | */ 377 | public function testCallWithAtSignBasedClassReferencesWithoutMethodThrowsException() 378 | { 379 | $container = new Container(); 380 | 381 | $container->call(ContainerTestCallStub::class); 382 | } 383 | 384 | public function testCallWithAtSignBasedClassReferences() 385 | { 386 | $container = new Container(); 387 | 388 | $result = $container->call('ContainerTestCallStub@work', ['foo', 'bar']); 389 | 390 | $this->assertEquals(['foo', 'bar'], $result); 391 | 392 | $container = new Container(); 393 | 394 | $result = $container->call('ContainerTestCallStub@inject'); 395 | 396 | $this->assertInstanceOf(ContainerTestStub::class, $result[0]); 397 | 398 | $this->assertEquals('mark', $result[1]); 399 | 400 | $container = new Container(); 401 | 402 | $result = $container->call('ContainerTestCallStub@inject', ['default' => 'foo']); 403 | 404 | $this->assertInstanceOf(ContainerTestStub::class, $result[0]); 405 | 406 | $this->assertEquals('foo', $result[1]); 407 | 408 | $container = new Container(); 409 | 410 | $result = $container->call(ContainerTestCallStub::class, ['foo', 'bar'], 'work'); 411 | 412 | $this->assertEquals(['foo', 'bar'], $result); 413 | } 414 | 415 | public function testCallWithCallableArray() 416 | { 417 | $container = new Container(); 418 | 419 | $stub = new ContainerTestCallStub(); 420 | 421 | $result = $container->call([$stub, 'work'], ['foo', 'bar']); 422 | 423 | $this->assertEquals(['foo', 'bar'], $result); 424 | } 425 | 426 | public function testCallWithStaticMethodNameString() 427 | { 428 | $container = new Container(); 429 | 430 | $result = $container->call('ContainerStaticMethodStub::inject'); 431 | 432 | $this->assertInstanceOf(ContainerTestStub::class, $result[0]); 433 | 434 | $this->assertEquals('mark', $result[1]); 435 | } 436 | 437 | public function testCallWithGlobalMethodName() 438 | { 439 | $container = new Container(); 440 | 441 | $result = $container->call('containerInjectTest'); 442 | 443 | $this->assertInstanceOf(ContainerTestStub::class, $result[0]); 444 | 445 | $this->assertEquals('mark', $result[1]); 446 | } 447 | 448 | public function testContainerCanInjectDifferentImplementationsDependingOnContext() 449 | { 450 | $container = new Container(); 451 | 452 | $container->bind(ContainerStubInterface::class, ContainerImplementationTestStub::class); 453 | 454 | $container->contextual(ContainerTestContextInjectOne::class, ContainerStubInterface::class, ContainerImplementationTestStub::class); 455 | $container->contextual(ContainerTestContextInjectTwo::class, ContainerStubInterface::class, ContainerImplementationTestStubTwo::class); 456 | 457 | $one = $container->make(ContainerTestContextInjectOne::class); 458 | $two = $container->make(ContainerTestContextInjectTwo::class); 459 | 460 | $this->assertInstanceOf(ContainerImplementationTestStub::class, $one->impl); 461 | $this->assertInstanceOf(ContainerImplementationTestStubTwo::class, $two->impl); 462 | } 463 | 464 | public function testContainerCanInjectDifferentImplementationsDependingOnContextWithClosure() 465 | { 466 | $container = new Container(); 467 | 468 | $container->bind(ContainerStubInterface::class, ContainerImplementationTestStub::class); 469 | 470 | $container->contextual(ContainerTestContextInjectOne::class, ContainerStubInterface::class, ContainerImplementationTestStub::class); 471 | $container->contextual(ContainerTestContextInjectTwo::class, ContainerStubInterface::class, function (Container $container) { 472 | return $container->make(ContainerImplementationTestStubTwo::class); 473 | }); 474 | 475 | $one = $container->make(ContainerTestContextInjectOne::class); 476 | $two = $container->make(ContainerTestContextInjectTwo::class); 477 | 478 | $this->assertInstanceOf(ContainerImplementationTestStub::class, $one->impl); 479 | $this->assertInstanceOf(ContainerImplementationTestStubTwo::class, $two->impl); 480 | } 481 | 482 | public function testContainerCanInjectSimpleVariable() 483 | { 484 | $container = new Container(); 485 | 486 | $container->contextual(ContainerInjectVariableStub::class, '$something', 9000); 487 | 488 | $instance = $container->make('ContainerInjectVariableStub'); 489 | 490 | $this->assertEquals(9000, $instance->something); 491 | 492 | $container = new Container(); 493 | 494 | $container->contextual('ContainerInjectVariableStub', '$something', function (Container $container) { 495 | return $container->make('ContainerTestStub'); 496 | }); 497 | 498 | $instance = $container->make('ContainerInjectVariableStub'); 499 | 500 | $this->assertInstanceOf('ContainerTestStub', $instance->something); 501 | } 502 | } 503 | 504 | interface ContainerStubInterface 505 | { 506 | // 507 | } 508 | 509 | class ContainerTestStub 510 | { 511 | // 512 | } 513 | 514 | class ContainerImplementationTestStub implements ContainerStubInterface 515 | { 516 | // 517 | } 518 | 519 | class ContainerImplementationTestStubTwo implements ContainerStubInterface 520 | { 521 | // 522 | } 523 | 524 | class ContainerDependentTestStub 525 | { 526 | public $implementation; 527 | 528 | public function __construct(ContainerStubInterface $implementation) 529 | { 530 | $this->implementation = $implementation; 531 | } 532 | } 533 | 534 | class ContainerNestedDependentTestStub 535 | { 536 | public $stub; 537 | 538 | public function __construct(ContainerDependentTestStub $stub) 539 | { 540 | $this->stub = $stub; 541 | } 542 | } 543 | 544 | class ContainerDefaultValueTestStub 545 | { 546 | public $stub; 547 | 548 | public $name; 549 | 550 | public function __construct(ContainerTestStub $stub, $name = 'mark') 551 | { 552 | $this->stub = $stub; 553 | 554 | $this->name = $name; 555 | } 556 | } 557 | 558 | class ContainerMixedPrimitiveTestStub 559 | { 560 | public $first; 561 | 562 | public $last; 563 | 564 | public $stub; 565 | 566 | public function __construct($first, ContainerTestStub $stub, $last = null) 567 | { 568 | $this->stub = $stub; 569 | 570 | $this->last = $last; 571 | 572 | $this->first = $first; 573 | } 574 | } 575 | 576 | class ContainerConstructorParameterLoggingTestStub 577 | { 578 | public $receivedParameters; 579 | 580 | public function __construct($first, $second) 581 | { 582 | $this->receivedParameters = func_get_args(); 583 | } 584 | } 585 | 586 | class ContainerLazyExtendTestStub 587 | { 588 | public static $initialized = false; 589 | 590 | public function init() 591 | { 592 | static::$initialized = true; 593 | } 594 | } 595 | 596 | class ContainerTestCallStub 597 | { 598 | public function work() 599 | { 600 | return func_get_args(); 601 | } 602 | 603 | public function inject(ContainerTestStub $stub, $default = 'mark') 604 | { 605 | return func_get_args(); 606 | } 607 | } 608 | 609 | class ContainerTestContextInjectOne 610 | { 611 | public $impl; 612 | 613 | public function __construct(ContainerStubInterface $impl) 614 | { 615 | $this->impl = $impl; 616 | } 617 | } 618 | 619 | class ContainerTestContextInjectTwo 620 | { 621 | public $impl; 622 | 623 | public function __construct(ContainerStubInterface $impl) 624 | { 625 | $this->impl = $impl; 626 | } 627 | } 628 | 629 | class ContainerStaticMethodStub 630 | { 631 | public static function inject(ContainerTestStub $stub, $default = 'mark') 632 | { 633 | return func_get_args(); 634 | } 635 | } 636 | 637 | class ContainerInjectVariableStub 638 | { 639 | public $concrete; 640 | public $something; 641 | 642 | public function __construct(ContainerTestStub $concrete, $something) 643 | { 644 | $this->concrete = $concrete; 645 | $this->something = $something; 646 | } 647 | } 648 | 649 | function containerInjectTest(ContainerTestStub $stub, $default = 'mark') 650 | { 651 | return func_get_args(); 652 | } 653 | -------------------------------------------------------------------------------- /src/Fist/Container/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.6.4", 19 | "fist/repository": "self.version" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "~5.4", 23 | "fist/testing": "self.version", 24 | "fist/container": "self.version" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Fist\\Database\\": "src/" 29 | } 30 | }, 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "1.0-dev" 34 | } 35 | }, 36 | "minimum-stability": "dev" 37 | } 38 | -------------------------------------------------------------------------------- /src/Fist/Database/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fist/Database/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fist/Database/readme.md: -------------------------------------------------------------------------------- 1 | # Fistlab Database 2 | 3 | [![StyleCI](https://styleci.io/repos/67337527/shield?style=flat)](https://styleci.io/repos/67337527) 4 | [![Build Status](https://travis-ci.org/fistphp/database.svg)](https://travis-ci.org/fistphp/database) 5 | [![Total Downloads](https://poser.pugx.org/fist/database/d/total.svg)](https://packagist.org/packages/fist/database) 6 | [![Latest Stable Version](https://poser.pugx.org/fist/database/v/stable.svg)](https://packagist.org/packages/fist/database) 7 | [![Latest Unstable Version](https://poser.pugx.org/fist/database/v/unstable.svg)](https://packagist.org/packages/fist/database) 8 | [![License](https://poser.pugx.org/fist/database/license.svg)](https://packagist.org/packages/fist/database) 9 | 10 | The Fistlab Database component is a database toolkit, providing an expressive query builder. It currently supports MySQL and SQLite. 11 | 12 | Languages: __php__. 13 | 14 | ## Installation 15 | 16 | Install using Composer. 17 | ``` 18 | composer require fist/database 19 | ``` 20 | 21 | ## Preparing 22 | 23 | The constructor accepts an instance of [`RepositoryInterface`](https://github.com/fistphp/repository/blob/master/RepositoryInterface.php) from [`fist/repository`](https://github.com/fistphp/repository). 24 | 25 | Example 26 | 27 | ``` 28 | $db = new \Fist\Database\Database( 29 | $repository = new Fist\Repository\ArrayRepository([ 30 | 'default' => [ 31 | 'connection' => 'default', 32 | 'driver' => 'mysql', 33 | ], 34 | 'connections' => [ 35 | 'default' => [ 36 | 'driver' => 'mysql', 37 | 'hostname' => '127.0.0.1', 38 | 'database' => 'database', 39 | 'username' => 'root', 40 | 'password' => '', 41 | ], 42 | ], 43 | 'drivers' => [ 44 | 'mysql' => \Fist\Database\Connectors\MysqlConnection::class, 45 | ], 46 | ]) 47 | ); 48 | ``` 49 | 50 | > I have made more setup at this [gist](https://gist.github.com/marktopper/2f783901f19da6597b935e3b432fa41d). 51 | 52 | ## Usage 53 | 54 | #### Running raw statements 55 | 56 | Raw statements can be ran by using the `statement`-method. 57 | 58 | ``` 59 | $db->statement("SELECT * FROM `users` WHERE `username` = 'mark'"); 60 | ``` 61 | 62 | It also takes an optional second argument with parameters to bind. Let's do the same query but by using bindings instead. 63 | 64 | ``` 65 | $db->statement("SELECT * FROM `users` WHERE `username` = ?", ['mark']) 66 | ``` 67 | 68 | #### Selecting all rows 69 | 70 | Select all rows from a table using the query builder is quite easy. 71 | 72 | ``` 73 | $users = $db->table('users')->get(); 74 | 75 | foreach ($users as $user) { 76 | echo "Hello ".$user->username; 77 | } 78 | ``` 79 | 80 | #### Select single row 81 | 82 | Often you might want to get just a single database row object, like the current logged in user. 83 | 84 | This can be done quite easy as well. 85 | 86 | ``` 87 | $user = $db->table('users')->first(); 88 | 89 | echo "Hello ".$user->username; 90 | ``` 91 | 92 | > Note that in case of no results. `null` will be returned. To get an exception instead use the `firstOrFail`-method. 93 | 94 | #### Select specific columns 95 | 96 | Want to select only specific columns, like `username`, `name` and `age`. 97 | 98 | ``` 99 | $db->table('users')->select(['username', 'name', 'age'])->get(); 100 | ``` 101 | 102 | You can also use aliases for the selected columns, like you want to get `name` as `fullname`. 103 | 104 | ``` 105 | $db->table('users')->select(['username', ['name' => 'fullname'], 'age'])->get(); 106 | ``` 107 | 108 | #### Where clauses 109 | 110 | You can use where clauses to the query builder to filter your results. 111 | 112 | ##### Basic where clauses 113 | 114 | By default the operator is `=` for where clauses. 115 | 116 | ``` 117 | $db->table('users')->where('username', 'mark')->first(); 118 | $db->table('users')->where('username', '=', 'mark')->first(); 119 | ``` 120 | 121 | The two methods above will do exactly the same, however you can use a set of other operators. 122 | 123 | ``` 124 | $db->table('users')->where('username', '!=', 'mark')->first(); 125 | $db->table('users')->where('age', '>', 18)->first(); 126 | $db->table('users')->where('age', '<', 18)->first(); 127 | $db->table('users')->where('age', '>=', 18)->first(); 128 | $db->table('users')->where('age', '<=', 18)->first(); 129 | $db->table('users')->where('age', 'LIKE', 'ma%')->first(); 130 | ``` 131 | 132 | The default behaviour of the where clauses are all using `and` for combining. 133 | 134 | However you might want to use `or` for some situations. 135 | 136 | ``` 137 | $db->table('users') 138 | ->where('username', 'mark') 139 | ->orWhere('username', 'topper') 140 | ->first(); 141 | ``` 142 | 143 | You mind want to group the where clauses in sub clauses. 144 | 145 | ``` 146 | $db->table('users') 147 | ->where('username', 'mark') 148 | ->orWhere(function ($query) { 149 | $query->where('username', 'topper') 150 | ->orWhere('name', 'Mark Topper') 151 | }) 152 | ->first(); 153 | ``` 154 | 155 | ##### Where null 156 | 157 | Want to use the where clause to filter value from that are not null. 158 | 159 | ``` 160 | $db->table('users')->whereNull('age')->get(); 161 | ``` 162 | 163 | ##### Where not null 164 | 165 | Want to use the where clause to filter value from that are null. 166 | 167 | ``` 168 | $db->table('users')->whereNotNull('age')->get(); 169 | ``` 170 | 171 | #### Joining 172 | 173 | You can join additional tables using our joining methods. 174 | 175 | ##### Inner join table 176 | 177 | ``` 178 | $db->table('users') 179 | ->join('devices', 'users.id', '=', 'devices.user_id') 180 | ->get(); 181 | ``` 182 | 183 | > By default the operator is `=` for join clauses. 184 | > So you can actually use `join('devices', 'users.id', 'devices.user_id')` 185 | 186 | ##### Outer join table 187 | 188 | ``` 189 | $db->table('users') 190 | ->outerJoin('devices', 'users.id', '=', 'devices.user_id') 191 | ->get(); 192 | ``` 193 | 194 | ##### Left join table 195 | 196 | ``` 197 | $db->table('users') 198 | ->leftJoin('devices', 'users.id', '=', 'devices.user_id') 199 | ->get(); 200 | ``` 201 | ##### Right join table 202 | 203 | ``` 204 | $db->table('users') 205 | ->rightJoin('devices', 'users.id', '=', 'devices.user_id') 206 | ->get(); 207 | ``` 208 | 209 | ##### Cross join table 210 | 211 | ``` 212 | $db->table('users') 213 | ->crossJoin('devices', 'users.id', '=', 'devices.user_id') 214 | ->get(); 215 | ``` 216 | 217 | ##### Advanced join clause 218 | 219 | ``` 220 | $db->table('users') 221 | ->join('devices', function ($join) { 222 | $join->on('users.id', '=', 'devices.user_id') 223 | ->where('devices.platform', 'ios'); 224 | }) 225 | ->get(); 226 | ``` 227 | 228 | #### Order results 229 | 230 | You can other by a column, while the second argument controls the direction of the sort and may be either `asc` or `desc`. 231 | 232 | ``` 233 | $db->table('users') 234 | ->orderBy('name', 'desc') 235 | ->get(); 236 | ``` 237 | 238 | You can other by multiple columns. 239 | 240 | ``` 241 | $db->table('users') 242 | ->orderBy('fistname', 'desc') 243 | ->orderBy('lastname', 'desc') 244 | ->get(); 245 | ``` 246 | 247 | ##### Order by random 248 | 249 | Randomize the order 250 | 251 | ``` 252 | $db->table('users') 253 | ->orderByRandom() 254 | ->first(); 255 | ``` 256 | 257 | #### Grouping results 258 | 259 | You can group the results. 260 | 261 | ``` 262 | $db->table('users') 263 | ->groupBy('country') 264 | ->get(); 265 | ``` 266 | 267 | #### Limit results (& offset) 268 | 269 | Limiting results with an offset are often used, specially when paginating. 270 | 271 | ``` 272 | $db->table('users') 273 | ->limit(100) 274 | ->offset(100) 275 | ->get(); 276 | ``` 277 | 278 | #### Count results 279 | 280 | Count rows easily 281 | 282 | ``` 283 | $users = $db->table('users')->count(); 284 | ``` 285 | 286 | #### Raw expressions 287 | 288 | Sometimes you may need to use a raw expression in a query. 289 | 290 | ``` 291 | $db->table('users') 292 | ->select([ 293 | $db->raw('count(*) as user_count'), 294 | 'status', 295 | ]) 296 | ->groupBy('status') 297 | ->get(); 298 | ``` 299 | 300 | #### Conditional clauses 301 | 302 | Sometimes you might want to only run a certain part of your query when something is true. 303 | You may for instance implement and `where` statement that only applies if a user is logged in. 304 | 305 | ``` 306 | $currentUserId = 1; 307 | $loggedIn = true; 308 | 309 | $db->table('users') 310 | ->when($loggedIn, function ($query) { 311 | $query->where('id', '=', $currentUserId); 312 | }) 313 | ->get(); 314 | ``` 315 | 316 | #### Insert rows 317 | 318 | The `insert` method accepts an array of column names and values. 319 | 320 | ``` 321 | $db->table('users')->insert([ 322 | ['email' => 'mark@example.com', 'username' => 'mark'], 323 | ['email' => 'john@example.com', 'username' => 'john'], 324 | ]); 325 | ``` 326 | 327 | Or you can insert a single row. 328 | 329 | ``` 330 | $db->table('users')->insert( 331 | ['email' => 'mark@example.com', 'username' => 'mark'] 332 | ); 333 | ``` 334 | 335 | ##### Auto incrementing IDs 336 | 337 | Want to insert a row and get the auto incremented ID? You can do this using the `insertGetId` method. 338 | 339 | ``` 340 | $id = $db->table('users')->insertGetId( 341 | ['email' => 'mark@example.com', 'username' => 'mark'] 342 | ); 343 | ``` 344 | 345 | #### Update rows 346 | 347 | Update `name` for the for the user with the `username` set to `mark`? 348 | 349 | ``` 350 | $db->table('user')->where('username', 'mark')->update(['name' => 'Foobar']); 351 | ``` 352 | 353 | #### Deleting rows 354 | 355 | Deleting rows have never been easier. 356 | 357 | ``` 358 | $db->table('users')->where('last_login', '<', '2016-01-01 00:00:00')->delete(); 359 | ``` 360 | 361 | If you wish to truncate the entire table, which will remove all rows and reset the auto-incrementing ID to zero, you may use the `truncate` method. 362 | 363 | ``` 364 | $db->table('users')->truncate(); 365 | ``` 366 | 367 | #### Connection swapping 368 | 369 | Have multiple connections configured you may swap between connections. The default connection is used unless anything else specified. 370 | 371 | ``` 372 | $db->connection('connection-name') 373 | ->table('users') 374 | ->get(); 375 | ``` 376 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Connectors/Connection.php: -------------------------------------------------------------------------------- 1 | configure($repository); 25 | } 26 | } 27 | 28 | public function getPdo() 29 | { 30 | return $this->pdo; 31 | } 32 | 33 | public function configure(RepositoryInterface $repository) 34 | { 35 | if ($this->repository != $repository) { 36 | $this->repository = $repository; 37 | 38 | $this->pdo = $this->newPdo($repository); 39 | } 40 | } 41 | 42 | public function table($name) 43 | { 44 | return $this->callQueryBuilder('table', [$name]); 45 | } 46 | 47 | protected function callQueryBuilder($method, array $parameters = []) 48 | { 49 | return call_user_func_array([$this->newQueryBuilder(), $method], $parameters); 50 | } 51 | 52 | protected function newQueryBuilder() 53 | { 54 | return new QueryBuilder($this, $this->newQueryGrammar()); 55 | } 56 | 57 | public function statement($sql, array $parameters = []) 58 | { 59 | return $this->createStatement( 60 | $this->pdo->prepare($sql), 61 | $parameters 62 | )->execute(); 63 | } 64 | 65 | public function raw($sql, array $parameters = []) 66 | { 67 | return $this->createStatement( 68 | $this->pdo->prepare($sql), 69 | $parameters 70 | ); 71 | } 72 | 73 | public function getLastInsertedId() 74 | { 75 | return $this->pdo->lastInsertId(); 76 | } 77 | 78 | protected function createStatement(PDOStatement $statement, array $parameters = []) 79 | { 80 | return new Statement($this->pdo, $statement, $parameters); 81 | } 82 | 83 | public function getTablePrefix() 84 | { 85 | return $this->repository->get('prefix'); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Connectors/ConnectionInterface.php: -------------------------------------------------------------------------------- 1 | get('hostname', 'localhost'); 14 | $database = $repository->get('database', ''); 15 | 16 | $pdo = new PDO( 17 | "mysql:host={$hostname};dbname={$database}", 18 | $repository->get('username'), 19 | $repository->get('password') 20 | ); 21 | 22 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 23 | 24 | return $pdo; 25 | } 26 | 27 | public function newQueryGrammar() 28 | { 29 | return new MysqlGrammar(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Connectors/SqliteConnection.php: -------------------------------------------------------------------------------- 1 | get('database'); 14 | 15 | $pdo = new PDO("sqlite:{$database}"); 16 | 17 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 18 | 19 | return $pdo; 20 | } 21 | 22 | public function newQueryGrammar() 23 | { 24 | return new SqliteGrammar(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Database.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 19 | } 20 | 21 | public function getRepository() 22 | { 23 | return $this->repository; 24 | } 25 | 26 | public function setRepository(RepositoryInterface $repository) 27 | { 28 | $this->repository = $repository; 29 | } 30 | 31 | public function getDefaultConnection($default = 'default') 32 | { 33 | return $this->repository->get('default.connection', $default); 34 | } 35 | 36 | public function getDefaultDriver($default = 'mysql') 37 | { 38 | return $this->repository->get('default.driver', $default); 39 | } 40 | 41 | /** 42 | * Get connection configuration repository by name. 43 | * 44 | * @param string $key 45 | * @param null|string $default 46 | * 47 | * @return null|\Fist\Repository\RepositoryInterface 48 | */ 49 | public function getConnection($key, $default = null) 50 | { 51 | $connection = $this->repository->get('connections.'.$key, $default); 52 | 53 | if ($connection instanceof Closure) { 54 | $connection = $connection(); 55 | } 56 | 57 | if (is_array($connection)) { 58 | $connection = new ArrayRepository($connection); 59 | } 60 | 61 | return $connection; 62 | } 63 | 64 | public function hasConnection($key) 65 | { 66 | return $this->repository->has('connections.'.$key); 67 | } 68 | 69 | public function setConnection($key, $value) 70 | { 71 | if (is_string($value)) { 72 | $value = new $value(); 73 | } 74 | 75 | $this->repository->set('connections.'.$key, $value); 76 | } 77 | 78 | /** 79 | * Get driver by name. 80 | * 81 | * @param string $key 82 | * @param null|string $default 83 | * 84 | * @return null|\Fist\Database\Connectors\Connection 85 | */ 86 | public function getDriver($key, $default = null) 87 | { 88 | $driver = $this->repository->get('drivers.'.$key, $default); 89 | 90 | if ($driver instanceof Closure) { 91 | $driver = $driver(); 92 | } 93 | 94 | if (is_string($driver)) { 95 | $driver = new $driver(); 96 | } 97 | 98 | return clone $driver; 99 | } 100 | 101 | public function hasDriver($key) 102 | { 103 | return $this->repository->has('drivers.'.$key); 104 | } 105 | 106 | public function setDriver($key, $value) 107 | { 108 | if (is_string($value)) { 109 | $value = new $value(); 110 | } 111 | 112 | $this->repository->set('drivers.'.$key, $value); 113 | } 114 | 115 | public function setDrivers(array $drivers) 116 | { 117 | foreach ($drivers as $name => $driver) { 118 | $this->setDriver($name, $driver); 119 | } 120 | } 121 | 122 | public function setDefaultConnection($connection) 123 | { 124 | $this->setDefaultValue('connection', $connection); 125 | } 126 | 127 | public function setDefaultDriver($driver) 128 | { 129 | $this->setDefaultValue('driver', $driver); 130 | } 131 | 132 | public function setDefaultValue($key, $value) 133 | { 134 | $this->repository->set('default.'.$key, $value); 135 | } 136 | 137 | public function setDefaultValues(array $values) 138 | { 139 | foreach ($values as $name => $value) { 140 | $this->setDefaultValue($name, $value); 141 | } 142 | } 143 | 144 | public function setConnections(array $connections) 145 | { 146 | foreach ($connections as $name => $connection) { 147 | if (is_array($connection)) { 148 | $connection = new ArrayRepository($connection); 149 | } 150 | 151 | $this->setConnection($name, $connection); 152 | } 153 | } 154 | 155 | public function connection($name = null) 156 | { 157 | if (is_null($name)) { 158 | $name = $this->getDefaultConnection(); 159 | } 160 | 161 | $connection = $this->getConnection($name); 162 | 163 | if (is_null($connection)) { 164 | throw new DatabaseException("Connection [{$name}] does not exists."); 165 | } 166 | 167 | $driverName = $connection->get('driver', $this->getDefaultDriver()); 168 | 169 | if ($this->hasConnectedDriver($driverName, $name)) { 170 | $driver = $this->getConnectedDriver($driverName, $name); 171 | } else { 172 | $driver = $this->getDriver($driverName); 173 | } 174 | 175 | if (is_null($driver)) { 176 | throw new DatabaseException("Driver [{$driverName}] does not exists."); 177 | } 178 | 179 | $this->setConnectedDriver($driverName, $name, $driver); 180 | 181 | $driver->configure($connection); 182 | 183 | return $driver; 184 | } 185 | 186 | protected function getConnectedDriver($driverName, $connectionName, $default = null) 187 | { 188 | if ($this->hasConnectedDriver($driverName, $connectionName)) { 189 | return $this->connectedDrivers[$driverName][$connectionName]; 190 | } 191 | 192 | return $default; 193 | } 194 | 195 | protected function hasConnectedDriver($driverName, $connectionName) 196 | { 197 | if (isset($this->connectedDrivers[$driverName])) { 198 | return isset($this->connectedDrivers[$driverName][$connectionName]); 199 | } 200 | 201 | return false; 202 | } 203 | 204 | protected function setConnectedDriver($driverName, $connectionName, ConnectionInterface $connection) 205 | { 206 | if (! isset($this->connectedDrivers[$driverName])) { 207 | $this->connectedDrivers[$driverName] = []; 208 | } 209 | 210 | $this->connectedDrivers[$driverName][$connectionName] = $connection; 211 | } 212 | 213 | public function table($name) 214 | { 215 | return $this->callConnectionDriver('table', [$name]); 216 | } 217 | 218 | public function createTable($name, $closure) 219 | { 220 | return $this->callConnectionDriver('createTable', [$name, $closure]); 221 | } 222 | 223 | protected function callConnectionDriver($method, array $arguments = []) 224 | { 225 | return call_user_func_array([$this->connection(), $method], $arguments); 226 | } 227 | 228 | public function statement($sql, array $parameters = []) 229 | { 230 | return $this->callConnectionDriver('statement', [$sql, $parameters]); 231 | } 232 | 233 | public function raw($sql, array $parameters = []) 234 | { 235 | return $this->callConnectionDriver('raw', [$sql, $parameters]); 236 | } 237 | 238 | public function getLastInsertedId() 239 | { 240 | return $this->callConnectionDriver('getLastInsertedId'); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/Fist/Database/src/DatabaseException.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 39 | 40 | $this->grammar = $grammar; 41 | } 42 | 43 | public function getPrimaryKey() 44 | { 45 | return $this->primaryKey; 46 | } 47 | 48 | public function table($name) 49 | { 50 | $this->table = $name; 51 | 52 | return $this; 53 | } 54 | 55 | public function select(array $columns) 56 | { 57 | $this->select = $columns; 58 | 59 | return $this; 60 | } 61 | 62 | protected function makeWhere($aggregator, $column, $operator, $value = null) 63 | { 64 | if (is_null($value)) { 65 | $value = $operator; 66 | 67 | $operator = '='; 68 | } 69 | 70 | $this->where[] = [ 71 | 'column' => $column, 72 | 'operator' => $operator, 73 | 'value' => $value, 74 | 'aggregator' => $aggregator, 75 | ]; 76 | 77 | return $this; 78 | } 79 | 80 | public function where($column, $operator, $value = null) 81 | { 82 | return $this->makeWhere('AND', $column, $operator, $value); 83 | } 84 | 85 | public function orWhere($column, $operator, $value = null) 86 | { 87 | return $this->makeWhere('OR', $column, $operator, $value); 88 | } 89 | 90 | public function orderBy($column, $direction = 'ASC') 91 | { 92 | $this->orders[] = [ 93 | 'column' => $column, 94 | 'direction' => $direction, 95 | 'random' => false, 96 | ]; 97 | 98 | return $this; 99 | } 100 | 101 | public function orderByRandom() 102 | { 103 | $this->orders[] = [ 104 | 'random' => true, 105 | ]; 106 | 107 | return $this; 108 | } 109 | 110 | public function groupBy($column) 111 | { 112 | $this->groups[] = $column; 113 | 114 | return $this; 115 | } 116 | 117 | protected function makeJoin($type, $table, $key, $operator, $foreign) 118 | { 119 | if (is_null($foreign)) { 120 | $foreign = $operator; 121 | 122 | $operator = '='; 123 | } 124 | 125 | $this->joins[] = [ 126 | 'type' => $type, 127 | 'table' => $table, 128 | 'key' => $key, 129 | 'foreign' => $foreign, 130 | 'operator' => $operator, 131 | ]; 132 | 133 | return $this; 134 | } 135 | 136 | public function innerJoin($table, $key, $operator, $foreign = null) 137 | { 138 | return $this->makeJoin('INNER', $table, $key, $operator, $foreign); 139 | } 140 | 141 | public function rightJoin($table, $key, $operator, $foreign = null) 142 | { 143 | return $this->makeJoin('RIGHT', $table, $key, $operator, $foreign); 144 | } 145 | 146 | public function leftJoin($table, $key, $operator, $foreign = null) 147 | { 148 | return $this->makeJoin('LEFT', $table, $key, $operator, $foreign); 149 | } 150 | 151 | public function outerJoin($table, $key, $operator, $foreign = null) 152 | { 153 | return $this->makeJoin('OUTER', $table, $key, $operator, $foreign); 154 | } 155 | 156 | public function truncate() 157 | { 158 | $this->aggregator = 'truncate'; 159 | 160 | return $this->connection->statement( 161 | $this->toSql() 162 | ); 163 | } 164 | 165 | public function get() 166 | { 167 | $this->aggregator = 'select'; 168 | 169 | return $this->connection->statement( 170 | $this->toSql() 171 | ); 172 | } 173 | 174 | public function update(array $values) 175 | { 176 | $this->aggregator = 'update'; 177 | 178 | $this->values = $values; 179 | 180 | return $this->connection->statement( 181 | $this->toSql() 182 | ); 183 | } 184 | 185 | public function insert(array $values) 186 | { 187 | $this->aggregator = 'insert'; 188 | 189 | $this->values = $values; 190 | 191 | return $this->connection->statement( 192 | $this->toSql() 193 | ); 194 | } 195 | 196 | public function delete() 197 | { 198 | $this->aggregator = 'delete'; 199 | 200 | return $this->connection->statement( 201 | $this->toSql() 202 | ); 203 | } 204 | 205 | public function first() 206 | { 207 | $this->aggregator = 'select'; 208 | 209 | $this->limit = 1; 210 | 211 | $results = $this->connection->statement( 212 | $this->toSql() 213 | ); 214 | 215 | return isset($results[0]) ? $results[0] : null; 216 | } 217 | 218 | public function last() 219 | { 220 | $this->aggregator = 'select'; 221 | 222 | $this->orderBy( 223 | $this->getPrimaryKey(), 224 | 'DESC' 225 | ); 226 | $this->limit = 1; 227 | 228 | $results = $this->connection->statement( 229 | $this->toSql() 230 | ); 231 | 232 | return isset($results[0]) ? $results[0] : null; 233 | } 234 | 235 | public function toSql() 236 | { 237 | return $this->grammar->toSql($this); 238 | } 239 | 240 | public function getAggregator() 241 | { 242 | return $this->aggregator; 243 | } 244 | 245 | public function getTable() 246 | { 247 | return $this->table; 248 | } 249 | 250 | public function getTablePrefix() 251 | { 252 | return $this->connection->getTablePrefix(); 253 | } 254 | 255 | public function getSelect() 256 | { 257 | return $this->select; 258 | } 259 | 260 | public function getWhereStatements() 261 | { 262 | return $this->where; 263 | } 264 | 265 | public function getOrders() 266 | { 267 | return $this->orders; 268 | } 269 | 270 | public function getLimit() 271 | { 272 | return $this->limit; 273 | } 274 | 275 | public function getOffset() 276 | { 277 | return $this->offset; 278 | } 279 | 280 | public function getValues() 281 | { 282 | return $this->values; 283 | } 284 | 285 | public function getGroups() 286 | { 287 | return $this->groups; 288 | } 289 | 290 | public function getJoins() 291 | { 292 | return $this->joins; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Query/Grammars/Grammar.php: -------------------------------------------------------------------------------- 1 | tablePrefix = $builder->getTablePrefix(); 17 | 18 | return $this->compileAggregator( 19 | $builder, 20 | $builder->getAggregator() 21 | ); 22 | } 23 | 24 | public function compileAggregator(Builder $builder, $aggregator) 25 | { 26 | if (is_null($aggregator)) { 27 | throw new DatabaseException('Missing aggregator.'); 28 | } 29 | 30 | if (! in_array($aggregator, $this->aggregators)) { 31 | throw new DatabaseException("Invalid aggregator [{$aggregator}]."); 32 | } 33 | 34 | $method = 'compile'.ucfirst($aggregator).'Aggregator'; 35 | 36 | if (method_exists($this, $method)) { 37 | return $this->$method($builder, $aggregator); 38 | } 39 | 40 | $method = 'get'.ucfirst($aggregator).'AggregatorPrefix'; 41 | 42 | $prefix = method_exists($this, $method) ? $this->$method($builder, $aggregator) : null; 43 | 44 | $method = 'get'.ucfirst($aggregator).'AggregatorSuffix'; 45 | 46 | $suffix = method_exists($this, $method) ? $this->$method($builder, $aggregator) : null; 47 | 48 | $string = ''; 49 | 50 | foreach ($this->getAggregatorComponents($aggregator) as $component) { 51 | $method = 'compile'.ucfirst($component).'ComponentFor'.ucfirst($aggregator).'Aggregator'; 52 | 53 | if (! method_exists($this, $method)) { 54 | $method = 'compile'.ucfirst($component).'Component'; 55 | } 56 | 57 | if ($content = $this->$method($builder, $aggregator)) { 58 | if (! is_array($content)) { 59 | $content = [$content, true]; 60 | } 61 | 62 | $space = isset($content[1]) ? $content[1] : true; 63 | $string .= $space ? ' ' : ''; 64 | $string .= $content[0]; 65 | } 66 | } 67 | 68 | return "{$prefix}{$string}{$suffix}"; 69 | } 70 | 71 | public function getAggregatorComponents($aggregator) 72 | { 73 | $method = 'get'.ucfirst($aggregator).'Components'; 74 | 75 | if (method_exists($this, $method)) { 76 | return $this->$method(); 77 | } 78 | 79 | return $this->getComponents(); 80 | } 81 | 82 | /** 83 | * Convert an array of column names into a delimited string. 84 | * 85 | * @param array $columns 86 | * 87 | * @return string 88 | */ 89 | public function columnize(array $columns) 90 | { 91 | if (empty($columns)) { 92 | return '*'; 93 | } 94 | 95 | return implode(', ', array_map([$this, 'wrap'], $columns)); 96 | } 97 | 98 | /** 99 | * Wrap a value in keyword identifiers. 100 | * 101 | * @param string $value 102 | * @param bool $prefixAlias 103 | * 104 | * @return string 105 | */ 106 | public function wrap($value, $prefixAlias = false) 107 | { 108 | // Work for "as" aliases. 109 | if (is_array($value) && count($value) === 2) { 110 | if ($prefixAlias) { 111 | $value[1] = $this->tablePrefix.$value[1]; 112 | } 113 | 114 | return $this->wrap($value[0]).' as '.$this->wrap($value[1]); 115 | } 116 | 117 | $wrapped = []; 118 | 119 | $segments = explode('.', $value); 120 | 121 | // Wrap first segment as table, and the rest (if any) as regular values 122 | foreach ($segments as $key => $segment) { 123 | if ($key == 0 && count($segments) > 1) { 124 | $wrapped[] = $this->wrapTable($segment); 125 | } else { 126 | $wrapped[] = $this->wrapColumn($segment); 127 | } 128 | } 129 | 130 | return implode('.', $wrapped); 131 | } 132 | 133 | public function wrapTable($table) 134 | { 135 | $prefix = $this->tablePrefix; 136 | 137 | return "`{$prefix}{$table}`"; 138 | } 139 | 140 | public function wrapColumn($column) 141 | { 142 | return "`{$column}`"; 143 | } 144 | 145 | /** 146 | * Wrap a single string in keyword identifiers. 147 | * 148 | * @param string $value 149 | * 150 | * @return string 151 | */ 152 | public function wrapValue($value) 153 | { 154 | if ($value === '*') { 155 | return $value; 156 | } 157 | 158 | return '"'.str_replace('"', '""', $value).'"'; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Query/Grammars/GrammarInterface.php: -------------------------------------------------------------------------------- 1 | columnize( 38 | $builder->getSelect() 39 | ); 40 | } 41 | 42 | protected function compileValuesComponent(Builder $builder, $aggregator) 43 | { 44 | if (in_array($aggregator, ['select', 'insert', 'delete', 'truncate'])) { 45 | return; 46 | } 47 | 48 | $values = $builder->getValues(); 49 | 50 | return 'SET '.implode(', ', array_map(function ($column, $value) { 51 | $column = $this->wrapColumn($column); 52 | $value = $this->wrapValue($value); 53 | 54 | return "{$column} = {$value}"; 55 | }, array_keys($values), $values)); 56 | } 57 | 58 | protected function compileValuesComponentForInsertAggregator(Builder $builder) 59 | { 60 | $values = $builder->getValues(); 61 | $keys = isset($values[0]) ? array_keys($values[0]) : []; 62 | 63 | return '('.implode(', ', array_map(function ($column) { 64 | return $this->wrapColumn($column); 65 | }, $keys)).') VALUES '.implode(', ', array_map(function ($items) { 66 | return '('.implode(', ', array_map(function ($item) { 67 | return $this->wrapValue($item); 68 | }, $items)).')'; 69 | }, $builder->getValues())); 70 | } 71 | 72 | protected function compileTableComponent(Builder $builder, $aggregator) 73 | { 74 | $table = $this->wrapTable( 75 | $builder->getTable() 76 | ); 77 | 78 | switch ($aggregator) { 79 | case 'select': return "FROM {$table}"; 80 | default: return $table; 81 | } 82 | } 83 | 84 | protected function compileWheresComponent(Builder $builder) 85 | { 86 | $wheres = []; 87 | 88 | foreach ($builder->getWhereStatements() as $index => $where) { 89 | $column = $this->wrapColumn($where['column']); 90 | $operator = $where['operator']; 91 | $value = $this->wrapValue($where['value']); 92 | $aggregator = $index > 0 ? $where['aggregator'] : 'WHERE'; 93 | 94 | $wheres[] = "{$aggregator} {$column} {$operator} {$value}"; 95 | } 96 | 97 | return implode(' ', $wheres); 98 | } 99 | 100 | protected function compileOrdersComponent(Builder $builder) 101 | { 102 | $orders = $builder->getOrders(); 103 | 104 | if (empty($orders)) { 105 | return; 106 | } 107 | 108 | return 'ORDER BY '.implode(', ', array_map(function ($order) { 109 | if ($order['random']) { 110 | return 'RAND()'; 111 | } 112 | 113 | $column = $this->wrapColumn($order['column']); 114 | 115 | $direction = isset($order['direction']) ? $order['direction'] : null; 116 | 117 | if (is_null($direction)) { 118 | return $column; 119 | } 120 | 121 | return "{$column} {$direction}"; 122 | }, $orders)); 123 | } 124 | 125 | protected function compileLimitComponent(Builder $builder) 126 | { 127 | $limit = $builder->getLimit(); 128 | 129 | if (is_null($limit)) { 130 | return; 131 | } 132 | 133 | return "LIMIT {$limit}"; 134 | } 135 | 136 | protected function compileOffsetComponent(Builder $builder) 137 | { 138 | $offset = $builder->getOffset(); 139 | 140 | if (is_null($offset)) { 141 | return; 142 | } 143 | 144 | return [", {$offset}", false]; 145 | } 146 | 147 | protected function compileGroupsComponent(Builder $builder) 148 | { 149 | $groups = $builder->getGroups(); 150 | 151 | if (empty($groups)) { 152 | return; 153 | } 154 | 155 | return 'GROUP BY '.implode(', ', array_map(function ($group) { 156 | return $this->wrapColumn($group); 157 | }, $groups)); 158 | } 159 | 160 | protected function compileJoinsComponent(Builder $builder) 161 | { 162 | $joins = $builder->getJoins(); 163 | 164 | if (empty($joins)) { 165 | return; 166 | } 167 | 168 | return implode(' ', array_map(function ($join) { 169 | $type = $join['type']; 170 | $table = $this->wrapTable($join['table']); 171 | $key = $this->wrapColumn($join['key']); 172 | $operator = $join['operator']; 173 | $foreign = $this->wrapColumn($join['foreign']); 174 | 175 | return "{$type} JOIN {$table} ON {$key} {$operator} {$foreign}"; 176 | }, $joins)); 177 | } 178 | 179 | public function getComponents() 180 | { 181 | return $this->components; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Query/Grammars/SqliteGrammar.php: -------------------------------------------------------------------------------- 1 | wrapTable( 12 | $builder->getTable() 13 | ); 14 | 15 | return "DELETE FROM {$table}"; 16 | } 17 | 18 | protected function compileOrdersComponent(Builder $builder) 19 | { 20 | $orders = $builder->getOrders(); 21 | 22 | if (empty($orders)) { 23 | return; 24 | } 25 | 26 | return 'ORDER BY '.implode(', ', array_map(function ($order) { 27 | if ($order['random']) { 28 | return 'RANDOM()'; 29 | } 30 | 31 | $column = $this->wrapColumn($order['column']); 32 | 33 | $direction = isset($order['direction']) ? $order['direction'] : null; 34 | 35 | if (is_null($direction)) { 36 | return $column; 37 | } 38 | 39 | return "{$column} {$direction}"; 40 | }, $orders)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Fist/Database/src/Query/Statement.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 41 | 42 | $this->statement = $statement; 43 | 44 | $this->bindings = $bindings; 45 | } 46 | 47 | /** 48 | * Execute query statement. 49 | * 50 | * @return array 51 | */ 52 | public function execute() 53 | { 54 | if ($this->statement->execute($this->bindings)) { 55 | return $this->statement->fetchAll(PDO::FETCH_OBJ); 56 | } 57 | } 58 | 59 | /** 60 | * Get query string. 61 | * 62 | * @return string 63 | */ 64 | public function toSqlWithoutBindings() 65 | { 66 | return $this->statement->queryString; 67 | } 68 | 69 | /** 70 | * Get query string with bindings. 71 | * 72 | * @return string 73 | */ 74 | public function toSql() 75 | { 76 | return array_reduce( 77 | $this->bindings, 78 | function ($sql, $binding) { 79 | return preg_replace('/\?/', $this->quote($binding), $sql, 1); 80 | }, 81 | $this->statement->queryString 82 | ); 83 | } 84 | 85 | /** 86 | * Get PDOStatement instance. 87 | * 88 | * @return \PDOStatement 89 | */ 90 | public function getPdoStatement() 91 | { 92 | return $this->statement; 93 | } 94 | 95 | /** 96 | * Get binding parameters. 97 | * 98 | * @return array 99 | */ 100 | public function getBindings() 101 | { 102 | return $this->bindings; 103 | } 104 | 105 | /** 106 | * Quote parameter according to its type. 107 | * 108 | * @param $parameter 109 | * 110 | * @return string 111 | */ 112 | protected function quote($parameter) 113 | { 114 | if (is_string($parameter)) { 115 | return $this->pdo->quote($parameter); 116 | } 117 | 118 | return $parameter; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Fist/Database/tests/DatabaseConnectionTest.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'driver' => 'mysql', 23 | 'database' => 'database', 24 | 'hostname' => 'localhost', 25 | 'username' => '', 26 | 'password' => '', 27 | ], 28 | 'sqlite' => [ 29 | 'driver' => 'sqlite', 30 | 'database' => ':memory:', // in-memory 31 | 'hostname' => 'localhost', 32 | 'username' => '', 33 | 'password' => '', 34 | ], 35 | 'tmp' => [ 36 | 'driver' => 'sqlite', 37 | 'database' => '', // temporary 38 | 'hostname' => 'localhost', 39 | 'username' => '', 40 | 'password' => '', 41 | ], 42 | ]; 43 | 44 | public function testConnectionBuildingUsingArrayRepository() 45 | { 46 | $db = new Database( 47 | new ArrayRepository() 48 | ); 49 | 50 | $this->assertInstanceOf(RepositoryInterface::class, $db->getRepository()); 51 | $this->assertInstanceOf(ArrayRepository::class, $db->getRepository()); 52 | } 53 | 54 | public function testConnectionBuildingUsingContainerRepository() 55 | { 56 | $db = new Database( 57 | new ContainerRepository( 58 | new Container() 59 | ) 60 | ); 61 | 62 | $this->assertInstanceOf(RepositoryInterface::class, $db->getRepository()); 63 | $this->assertInstanceOf(ContainerRepository::class, $db->getRepository()); 64 | } 65 | 66 | public function testDefaultConnection() 67 | { 68 | $db = new Database( 69 | new ArrayRepository() 70 | ); 71 | 72 | $this->setDatabaseConnectionsAndDrivers($db); 73 | 74 | $this->assertEquals('default', $db->getDefaultConnection()); 75 | $this->throwsException(function () use ($db) { 76 | $this->assertInstanceOf(MysqlConnection::class, $db->connection()); 77 | }, DatabaseException::class, 'Connection [default] does not exists.'); 78 | 79 | $db->setDefaultConnection('mysql'); 80 | 81 | $this->assertEquals('mysql', $db->getDefaultConnection()); 82 | $this->throwsException(function () use ($db) { 83 | $this->assertInstanceOf(MysqlConnection::class, $db->connection()); 84 | }, PDOException::class, [ 85 | "SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO)", 86 | "SQLSTATE[HY000] [1044] Access denied for user ''@'localhost' to database 'database'", 87 | ]); 88 | 89 | $db->setDefaultConnection('sqlite'); 90 | 91 | $this->assertEquals('sqlite', $db->getDefaultConnection()); 92 | $this->assertInstanceOf(SqliteConnection::class, $db->connection()); 93 | } 94 | 95 | public function testDefaultDriver() 96 | { 97 | $db = new Database( 98 | new ArrayRepository() 99 | ); 100 | 101 | $this->setDatabaseConnectionsAndDrivers($db); 102 | $db->setConnection('default', []); 103 | 104 | $this->assertEquals('mysql', $db->getDefaultDriver()); 105 | $this->throwsException(function () use ($db) { 106 | $db->connection()->statement('SELECT * FROM test'); 107 | }, PDOException::class, [ 108 | 'SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected', 109 | "SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO)", 110 | ]); 111 | 112 | $db->setDefaultDriver('sqlite'); 113 | $this->assertEquals('sqlite', $db->getDefaultDriver()); 114 | $this->assertInstanceOf(SqliteConnection::class, $db->connection()); 115 | } 116 | 117 | public function testConnectionSwapping() 118 | { 119 | $db = new Database( 120 | new ArrayRepository() 121 | ); 122 | 123 | $this->setDatabaseConnectionsAndDrivers($db); 124 | $db->setDefaultConnection('mysql'); 125 | 126 | $this->throwsException(function () use ($db) { 127 | $this->assertInstanceOf(MysqlConnection::class, $db->connection()); 128 | }, PDOException::class, [ 129 | "SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO)", 130 | "SQLSTATE[HY000] [1044] Access denied for user ''@'localhost' to database 'database'", 131 | ]); 132 | 133 | $this->assertInstanceOf(SqliteConnection::class, $db->connection('sqlite')); 134 | } 135 | 136 | public function testDriverConnectionAreShared() 137 | { 138 | $db = new Database( 139 | new ContainerRepository( 140 | new Container(), 141 | 'db.' 142 | ) 143 | ); 144 | 145 | $this->setDatabaseConnectionsAndDrivers($db); 146 | $db->setDefaultConnection('sqlite'); 147 | 148 | $this->runOnDatabaseConnections($db, ['sqlite', 'tmp'], function (ConnectionInterface $connection) { 149 | $connection->statement('CREATE TABLE items (name VARCHAR(50))'); 150 | }); 151 | 152 | $this->runOnDatabaseConnections($db, [ 153 | 'sqlite', 154 | 'tmp', 155 | 'sqlite', 156 | 'tmp', 157 | null, 158 | ], function (ConnectionInterface $connection) { 159 | $this->throwsException(function () use ($connection) { 160 | $connection->statement('CREATE TABLE items (name VARCHAR(50))'); 161 | }, PDOException::class, 'SQLSTATE[HY000]: General error: 1 table items already exists'); 162 | }); 163 | } 164 | 165 | public function testDriverSwapping() 166 | { 167 | $db = new Database( 168 | $repository = new ContainerRepository( 169 | new Container(), 170 | 'db.' 171 | ) 172 | ); 173 | 174 | $this->setDatabaseConnectionsAndDrivers($db); 175 | $db->setDefaultConnection('sqlite'); 176 | 177 | // Test getting default connection 178 | $connection = $db->connection(); 179 | $this->assertInstanceOf(SqliteConnection::class, $connection); 180 | 181 | // Change from in-memory to temporary database 182 | $db->setConnection('sqlite', [ 183 | 'driver' => 'mysql', 184 | ]); 185 | 186 | // Connection fails using the mysql setup since it's not setup. 187 | // So we expect it to fail 188 | $this->throwsException(function () use ($db) { 189 | $db->connection()->statement('SELECT * FROM test'); 190 | }, PDOException::class, [ 191 | 'SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected', 192 | "SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO)", 193 | ]); 194 | } 195 | 196 | public function testSharedDriverConnectionsUpdateSettings() 197 | { 198 | $db = new Database( 199 | $repository = new ContainerRepository( 200 | new Container(), 'db.' 201 | ) 202 | ); 203 | 204 | $this->setDatabaseConnectionsAndDrivers($db); 205 | $db->setDefaultConnection('sqlite'); 206 | 207 | $db->statement('CREATE TABLE items (name VARCHAR(50))'); 208 | 209 | // Ensure that table exists 210 | $this->assertEquals([], $db->table('items')->get()); 211 | 212 | // Change from in-memory to temporary database 213 | $db->setConnection('sqlite', [ 214 | 'driver' => 'sqlite', 215 | 'database' => '', 216 | ]); 217 | 218 | // Ensure that table does not exists on the new connection 219 | $this->throwsException(function () use ($db) { 220 | $db->table('items')->get(); 221 | }, PDOException::class, [ 222 | 'SQLSTATE[HY000]: General error: 1 no such table: items', 223 | ]); 224 | 225 | // The change should make sure that this does not fail, 226 | // even that it is the same driver and same connection, 227 | // but since the connection variable have been changed. 228 | $db->statement('CREATE TABLE items (name VARCHAR(50))'); 229 | 230 | // Ensure that table exists 231 | $this->assertEquals([], $db->table('items')->get()); 232 | } 233 | 234 | public function testRawQueries() 235 | { 236 | $db = $this->prepareDatabase(); 237 | 238 | $statement = $db->raw("SELECT * FROM items WHERE name = 'foo'"); 239 | 240 | $this->assertInstanceOf(Statement::class, $statement); 241 | $this->assertEquals("SELECT * FROM items WHERE name = 'foo'", $statement->toSql()); 242 | 243 | $results = $statement->execute(); 244 | 245 | $this->assertTrue(is_array($results)); 246 | $this->assertCount(1, $results); 247 | $this->assertEquals('foo', $results[0]->name); 248 | } 249 | 250 | public function testRawQueriesWithParameters() 251 | { 252 | $db = $this->prepareDatabase(); 253 | 254 | $statement = $db->raw('SELECT * FROM items WHERE name = ? OR name = ?', ['foo', 1]); 255 | 256 | $this->assertInstanceOf(Statement::class, $statement); 257 | $this->assertEquals('SELECT * FROM items WHERE name = ? OR name = ?', $statement->toSqlWithoutBindings()); 258 | $this->assertEquals("SELECT * FROM items WHERE name = 'foo' OR name = 1", $statement->toSql()); 259 | 260 | $results = $statement->execute(); 261 | 262 | $this->assertTrue(is_array($results)); 263 | $this->assertCount(1, $results); 264 | $this->assertEquals('foo', $results[0]->name); 265 | } 266 | 267 | public function testStatements() 268 | { 269 | $db = $this->prepareDatabase(); 270 | 271 | $results = $db->statement("SELECT * FROM items WHERE name = 'foo'"); 272 | 273 | $this->assertTrue(is_array($results)); 274 | $this->assertCount(1, $results); 275 | $this->assertEquals('foo', $results[0]->name); 276 | } 277 | 278 | public function testStatementsWithParameters() 279 | { 280 | $db = $this->prepareDatabase(); 281 | 282 | $results = $db->statement('SELECT * FROM items WHERE name = ?', ['foo']); 283 | 284 | $this->assertTrue(is_array($results)); 285 | $this->assertCount(1, $results); 286 | $this->assertEquals('foo', $results[0]->name); 287 | } 288 | 289 | public function testGettingLastInsertedId() 290 | { 291 | $db = $this->prepareDatabase(); 292 | 293 | $this->assertEquals(1, $db->getLastInsertedId()); 294 | } 295 | 296 | public function testTruncatingTables() 297 | { 298 | $db = $this->prepareDatabase(); 299 | 300 | $results = $db->statement('SELECT * FROM items'); 301 | 302 | $this->assertCount(1, $results); 303 | 304 | $db->table('items')->truncate(); 305 | 306 | $results = $db->statement('SELECT * FROM items'); 307 | 308 | $this->assertCount(0, $results); 309 | } 310 | 311 | public function testSelectRows() 312 | { 313 | $db = $this->prepareDatabase(); 314 | 315 | $results = $db->table('items')->get(); 316 | 317 | $this->assertTrue(is_array($results)); 318 | $this->assertCount(1, $results); 319 | $this->assertEquals('foo', $results[0]->name); 320 | 321 | $results = $db->table('items')->where('name', 'foo')->get(); 322 | 323 | $this->assertTrue(is_array($results)); 324 | $this->assertCount(1, $results); 325 | $this->assertEquals('foo', $results[0]->name); 326 | 327 | $results = $db->table('items')->select(['name'])->get(); 328 | 329 | $this->assertTrue(is_array($results)); 330 | $this->assertCount(1, $results); 331 | $this->assertEquals('foo', $results[0]->name); 332 | 333 | $result = $db->table('items')->first(); 334 | 335 | $this->assertInstanceOf(stdClass::class, $result); 336 | $this->assertEquals('foo', $result->name); 337 | 338 | $result = $db->table('items')->last(); 339 | 340 | $this->assertInstanceOf(stdClass::class, $result); 341 | $this->assertEquals('foo', $result->name); 342 | } 343 | 344 | public function testInsertRows() 345 | { 346 | $db = $this->prepareDatabase(); 347 | 348 | $results = $db->table('items')->get(); 349 | 350 | $this->assertTrue(is_array($results)); 351 | $this->assertCount(1, $results); 352 | 353 | $db->table('items')->insert([ 354 | ['name' => 'bar'], 355 | ['name' => 'baz'], 356 | ]); 357 | 358 | $results = $db->table('items')->get(); 359 | 360 | $this->assertTrue(is_array($results)); 361 | $this->assertCount(3, $results); 362 | } 363 | 364 | public function testUpdateRows() 365 | { 366 | $db = $this->prepareDatabase(); 367 | 368 | $results = $db->table('items')->get(); 369 | 370 | $this->assertTrue(is_array($results)); 371 | $this->assertCount(1, $results); 372 | $this->assertEquals('foo', $results[0]->name); 373 | 374 | $db->table('items')->where('name', 'foo')->update(['name' => 'bar']); 375 | 376 | $results = $db->table('items')->get(); 377 | 378 | $this->assertTrue(is_array($results)); 379 | $this->assertCount(1, $results); 380 | $this->assertEquals('bar', $results[0]->name); 381 | } 382 | 383 | public function testDeleteRows() 384 | { 385 | $db = $this->prepareDatabase(); 386 | 387 | $results = $db->table('items')->get(); 388 | 389 | $this->assertTrue(is_array($results)); 390 | $this->assertCount(1, $results); 391 | 392 | $db->table('items')->where('name', 'foo')->delete(); 393 | 394 | $results = $db->table('items')->get(); 395 | 396 | $this->assertTrue(is_array($results)); 397 | $this->assertCount(0, $results); 398 | } 399 | 400 | public function testBuilderGrammars() 401 | { 402 | $db = $this->prepareDatabase(); 403 | 404 | $this->assertEquals('SELECT * FROM `items`', $db->table('items')->toSql()); 405 | $this->assertEquals('SELECT * FROM `items` WHERE `name` = "foo"', $db->table('items')->where('name', 'foo')->toSql()); 406 | } 407 | 408 | public function testOrderByRandom() 409 | { 410 | $db = $this->prepareDatabase(); 411 | 412 | $this->assertEquals( 413 | 'SELECT * FROM `items` ORDER BY RANDOM()', 414 | $db->table('items')->orderByRandom()->toSql() 415 | ); 416 | } 417 | 418 | public function testSelectingColumnAsAlias() 419 | { 420 | $db = $this->prepareDatabase(); 421 | 422 | $this->assertEquals( 423 | 'SELECT `foo` as `bar`, `toast` FROM `items`', 424 | $db->table('items')->select([['foo', 'bar'], 'toast'])->toSql() 425 | ); 426 | } 427 | 428 | public function testOverwritingRepository() 429 | { 430 | $db = $this->prepareDatabase(); 431 | 432 | $this->assertInstanceOf(ContainerRepository::class, $db->getRepository()); 433 | 434 | $db->setRepository(new ArrayRepository([])); 435 | 436 | $this->assertInstanceOf(ArrayRepository::class, $db->getRepository()); 437 | } 438 | 439 | public function testWhereStatements() 440 | { 441 | $db = $this->prepareDatabase(); 442 | 443 | $this->assertEquals( 444 | 'SELECT * FROM `table` WHERE `foo` = "bar" AND `bar` = "baz"', 445 | $db->table('table')->where('foo', 'bar')->where('bar', 'baz')->toSql() 446 | ); 447 | } 448 | 449 | protected function prepareDatabase() 450 | { 451 | $db = new Database( 452 | new ContainerRepository( 453 | new Container(), 454 | 'db.' 455 | ) 456 | ); 457 | 458 | $this->setDatabaseConnectionsAndDrivers($db); 459 | $db->setDefaultConnection('sqlite'); 460 | 461 | $this->runDatabaseMigrations($db); 462 | $this->runDatabaseSeeder($db); 463 | 464 | return $db; 465 | } 466 | 467 | protected function runDatabaseMigrations(Database $db) 468 | { 469 | $db->statement('CREATE TABLE `items` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(55))'); 470 | } 471 | 472 | protected function runDatabaseSeeder(Database $db) 473 | { 474 | $db->statement("INSERT INTO `items` (`name`) VALUES ('foo')"); 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /src/Fist/Database/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.6.4" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~5.4", 22 | "fist/testing": "self.version", 23 | "fist/container": "self.version" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Fist\\Facade\\": "src/" 28 | } 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "1.0-dev" 33 | } 34 | }, 35 | "suggest": { 36 | "fist/container": "Required for using ContainerFacade" 37 | }, 38 | "minimum-stability": "dev" 39 | } 40 | -------------------------------------------------------------------------------- /src/Fist/Facade/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fist/Facade/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fist/Facade/src/ContainerFacade.php: -------------------------------------------------------------------------------- 1 | make(static::getFacadeAccessor()); 13 | } 14 | 15 | public static function getContainerInstance() 16 | { 17 | return Container::getInstance(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Fist/Facade/src/ContainerFacadeInterface.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', ExampleFacade::foo()); 13 | } 14 | 15 | public function testFacadeParsesArguments() 16 | { 17 | $this->assertEquals('bar', ExampleFacade::text('bar')); 18 | } 19 | 20 | /** 21 | * @expectedException Fist\Facade\InvalidArgumentException 22 | * @expectedExceptionMessage Method [notExistingMethod] does not exists. 23 | */ 24 | public function testFacadeThrowsException() 25 | { 26 | ExampleFacade::notExistingMethod(); 27 | } 28 | 29 | public function testContainerFacadeBuilds() 30 | { 31 | $container = new Container(); 32 | 33 | Container::setInstance($container); 34 | 35 | $container->instance('example', new ExampleInstance()); 36 | 37 | $this->assertEquals('foo', ExampleContainerFacade::foo()); 38 | } 39 | 40 | /** 41 | * @expectedException ReflectionException 42 | * @expectedExceptionMessage Class example does not exist 43 | */ 44 | public function testContainerFacadeThrowException() 45 | { 46 | $container = new Container(); 47 | 48 | Container::setInstance($container); 49 | 50 | $this->assertEquals('foo', ExampleContainerFacade::foo()); 51 | } 52 | } 53 | 54 | class ExampleInstance 55 | { 56 | public function foo() 57 | { 58 | return 'foo'; 59 | } 60 | 61 | public function text($text) 62 | { 63 | return $text; 64 | } 65 | } 66 | 67 | class ExampleFacade extends Facade 68 | { 69 | public static function getFacadeInstance() 70 | { 71 | return new ExampleInstance(); 72 | } 73 | } 74 | 75 | class ExampleContainerFacade extends ContainerFacade 76 | { 77 | public static function getFacadeAccessor() 78 | { 79 | return 'example'; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Fist/Facade/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.6.4" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~5.4", 22 | "fist/testing": "self.version", 23 | "fist/container": "self.version" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Fist\\Repository\\": "src/" 28 | } 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "1.0-dev" 33 | } 34 | }, 35 | "suggest": { 36 | "fist/container": "Required for using ContainerRepository" 37 | }, 38 | "minimum-stability": "dev" 39 | } 40 | -------------------------------------------------------------------------------- /src/Fist/Repository/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fist/Repository/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fist/Repository/src/ArrayRepository.php: -------------------------------------------------------------------------------- 1 | items = $items; 14 | 15 | $this->separator = $separator; 16 | } 17 | 18 | public function get($key, $default = null) 19 | { 20 | if ($this->has($key)) { 21 | if (! is_null($this->separator)) { 22 | return $this->getSeparated($key, $this->items); 23 | } 24 | 25 | return $this->items[$key]; 26 | } 27 | 28 | return $default; 29 | } 30 | 31 | public function set($key, $value) 32 | { 33 | if (! is_null($this->separator)) { 34 | $this->setSeparated($key, $value, $this->items); 35 | } else { 36 | $this->items[$key] = $value; 37 | } 38 | } 39 | 40 | public function has($key) 41 | { 42 | if (! is_null($this->separator)) { 43 | return $this->hasSeparated($key, $this->items); 44 | } 45 | 46 | return isset($this->items[$key]); 47 | } 48 | 49 | public function all() 50 | { 51 | return $this->items; 52 | } 53 | 54 | protected function getSeparated($key, array &$items) 55 | { 56 | $parts = explode($this->separator, $key); 57 | $part = array_shift($parts); 58 | 59 | if (count($parts) == 0) { 60 | return $items[$part]; 61 | } 62 | 63 | return $this->getSeparated( 64 | implode($this->separator, $parts), 65 | $items[$part] 66 | ); 67 | } 68 | 69 | protected function setSeparated($key, $value, array &$items) 70 | { 71 | $parts = explode($this->separator, $key); 72 | $part = array_shift($parts); 73 | 74 | if (count($parts) == 0) { 75 | return $items[$part] = $value; 76 | } 77 | 78 | if (! isset($items[$part]) || ! is_array($items[$part])) { 79 | $items[$part] = []; 80 | } 81 | 82 | return $this->setSeparated( 83 | implode($this->separator, $parts), 84 | $value, 85 | $items[$part] 86 | ); 87 | } 88 | 89 | protected function hasSeparated($key, array $items) 90 | { 91 | $parts = explode($this->separator, $key); 92 | $part = array_shift($parts); 93 | 94 | if (! isset($items[$part]) || count($parts) == 0) { 95 | return isset($items[$part]); 96 | } 97 | 98 | return $this->hasSeparated( 99 | implode($this->separator, $parts), 100 | $items[$part] 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Fist/Repository/src/ContainerRepository.php: -------------------------------------------------------------------------------- 1 | container = $container; 17 | 18 | $this->prefix = $prefix; 19 | } 20 | 21 | public function get($key, $default = null) 22 | { 23 | if ($this->has($key)) { 24 | return $this->container->make($this->prefix.$key); 25 | } 26 | 27 | return $default; 28 | } 29 | 30 | public function set($key, $value) 31 | { 32 | if ($value instanceof Closure) { 33 | // I'm still unsure whether or not to use 'singleton' over 'bind' in this ase. 34 | $this->container->bind($this->prefix.$key, $value); 35 | } else { 36 | $this->container->instance($this->prefix.$key, $value); 37 | } 38 | } 39 | 40 | public function has($key) 41 | { 42 | return $this->container->bound($this->prefix.$key); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Fist/Repository/src/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 14 | RepositoryInterface::class, 15 | new ArrayRepository() 16 | ); 17 | 18 | $this->assertInstanceOf( 19 | RepositoryInterface::class, 20 | new ContainerRepository( 21 | new Container() 22 | ) 23 | ); 24 | } 25 | 26 | public function testArrayRepositoryGetter() 27 | { 28 | $array = [ 29 | 'foo' => 'bar', 30 | 'bar' => 'baz', 31 | 'foobar' => [ 32 | 'foo' => 'bar', 33 | ], 34 | ]; 35 | 36 | $repository = new ArrayRepository($array); 37 | 38 | $this->assertEquals('bar', $repository->get('foo')); 39 | $this->assertEquals('baz', $repository->get('bar')); 40 | $this->assertEquals(null, $repository->get('baz')); 41 | $this->assertEquals('baz', $repository->get('baz', 'baz')); 42 | $this->assertEquals('bar', $repository->get('foobar.foo')); 43 | } 44 | 45 | public function testArrayRepositorySetter() 46 | { 47 | $array = [ 48 | 'foo' => 'bar', 49 | 'bar' => 'baz', 50 | 'foobar' => [ 51 | 'foo' => 'bar', 52 | ], 53 | ]; 54 | 55 | $repository = new ArrayRepository($array); 56 | 57 | $this->assertEquals('bar', $repository->get('foo')); 58 | 59 | $repository->set('foo', 'foobar'); 60 | 61 | $this->assertEquals('foobar', $repository->get('foo')); 62 | 63 | $this->assertEquals(null, $repository->get('baz')); 64 | 65 | $repository->set('baz', 'baz'); 66 | 67 | $this->assertEquals('baz', $repository->get('baz')); 68 | 69 | $this->assertEquals('bar', $repository->get('foobar.foo')); 70 | 71 | $repository->set('foobar.foo', 'baz'); 72 | 73 | $this->assertEquals('baz', $repository->get('foobar.foo')); 74 | 75 | $this->assertEquals(null, $repository->get('foobar.bar')); 76 | 77 | $repository->set('foobar.bar', 'baz'); 78 | 79 | $this->assertEquals('baz', $repository->get('foobar.bar')); 80 | } 81 | 82 | public function testContainerRepositoryGetter() 83 | { 84 | $repository = new ContainerRepository( 85 | $container = new Container() 86 | ); 87 | 88 | $container->instance('foo', 'bar'); 89 | $container->instance('bar', 'baz'); 90 | $container->instance('foobar.foo', 'bar'); 91 | 92 | $this->assertEquals('bar', $repository->get('foo')); 93 | $this->assertEquals('baz', $repository->get('bar')); 94 | $this->assertEquals(null, $repository->get('baz')); 95 | $this->assertEquals('baz', $repository->get('baz', 'baz')); 96 | $this->assertEquals('bar', $repository->get('foobar.foo')); 97 | } 98 | 99 | public function testContainerRepositorySetter() 100 | { 101 | $repository = new ContainerRepository( 102 | $container = new Container() 103 | ); 104 | 105 | $container->instance('foo', 'bar'); 106 | $container->instance('bar', 'baz'); 107 | $container->instance('foobar.foo', 'bar'); 108 | 109 | $this->assertEquals('bar', $repository->get('foo')); 110 | 111 | $repository->set('foo', 'foobar'); 112 | 113 | $this->assertEquals('foobar', $repository->get('foo')); 114 | 115 | $this->assertEquals(null, $repository->get('baz')); 116 | 117 | $repository->set('baz', 'baz'); 118 | 119 | $this->assertEquals('baz', $repository->get('baz')); 120 | 121 | $this->assertEquals('bar', $repository->get('foobar.foo')); 122 | 123 | $repository->set('foobar.foo', 'baz'); 124 | 125 | $this->assertEquals('baz', $repository->get('foobar.foo')); 126 | 127 | $this->assertEquals(null, $repository->get('foobar.bar')); 128 | 129 | $repository->set('foobar.bar', 'baz'); 130 | 131 | $this->assertEquals('baz', $repository->get('foobar.bar')); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Fist/Repository/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.6.4" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~5.4", 22 | "fist/testing": "self.version" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Fist\\Routing\\": "src/" 27 | } 28 | }, 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "1.0-dev" 32 | } 33 | }, 34 | "minimum-stability": "dev" 35 | } 36 | -------------------------------------------------------------------------------- /src/Fist/Routing/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fist/Routing/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fist/Routing/src/MethodNotAllowedException.php: -------------------------------------------------------------------------------- 1 | methods = $methods; 22 | 23 | $this->uri = $uri; 24 | 25 | $this->closure = $closure; 26 | } 27 | 28 | public function getMethods() 29 | { 30 | return $this->methods; 31 | } 32 | 33 | public function hasMethod($method) 34 | { 35 | return in_array(strtolower($method), $this->methods); 36 | } 37 | 38 | public function matches($uri) 39 | { 40 | return (bool) preg_match("#{$this->generatePattern()}$#", $uri); 41 | } 42 | 43 | public function generatePattern() 44 | { 45 | if (is_null($this->pattern)) { 46 | $this->pattern = preg_replace_callback('~\{(.*?)\}~s', function ($pattern) { 47 | $content = $pattern[1]; 48 | 49 | if (strpos($content, ':')) { 50 | $parts = explode(':', $content); 51 | 52 | return '('.$parts[1].')'; 53 | } 54 | 55 | if (isset($this->regex[$content])) { 56 | return $this->regex[$content]; 57 | } 58 | 59 | return '(.+)'; 60 | }, $this->uri); 61 | } 62 | 63 | return $this->pattern; 64 | } 65 | 66 | public function where($name, $value) 67 | { 68 | $this->regex[$name] = $value; 69 | } 70 | 71 | public function callAction() 72 | { 73 | $action = $this->closure; 74 | 75 | return $action(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Fist/Routing/src/Router.php: -------------------------------------------------------------------------------- 1 | routes[] = $route; 18 | 19 | return $route; 20 | } 21 | 22 | public function get($uri, Closure $closure) 23 | { 24 | return $this->add(['get', 'head'], $uri, $closure); 25 | } 26 | 27 | public function post($uri, Closure $closure) 28 | { 29 | return $this->add(['post'], $uri, $closure); 30 | } 31 | 32 | public function put($uri, Closure $closure) 33 | { 34 | return $this->add(['put'], $uri, $closure); 35 | } 36 | 37 | public function patch($uri, Closure $closure) 38 | { 39 | return $this->add(['patch'], $uri, $closure); 40 | } 41 | 42 | public function delete($uri, Closure $closure) 43 | { 44 | return $this->add(['delete'], $uri, $closure); 45 | } 46 | 47 | public function head($uri, Closure $closure) 48 | { 49 | return $this->add(['head'], $uri, $closure); 50 | } 51 | 52 | public function connect($uri, Closure $closure) 53 | { 54 | return $this->add(['connect'], $uri, $closure); 55 | } 56 | 57 | public function options($uri, Closure $closure) 58 | { 59 | return $this->add(['options'], $uri, $closure); 60 | } 61 | 62 | public function trace($uri, Closure $closure) 63 | { 64 | return $this->add(['trace'], $uri, $closure); 65 | } 66 | 67 | public function any($uri, Closure $closure) 68 | { 69 | return $this->add(['get', 'post', 'put', 'patch', 'delete', 'head', 'connect', 'options', 'trace'], $uri, $closure); 70 | } 71 | 72 | protected function prepareQueryString($uri) 73 | { 74 | return rawurldecode( 75 | $this->stripQueryString($uri) 76 | ); 77 | } 78 | 79 | protected function stripQueryString($uri) 80 | { 81 | // Strip query string (?foo=bar) and decode URI 82 | $pos = strpos($uri, '?'); 83 | if ($pos !== false) { 84 | return substr($uri, 0, $pos); 85 | } 86 | 87 | return $uri; 88 | } 89 | 90 | public function dispatch($method = null, $uri = null) 91 | { 92 | if (is_null($method)) { 93 | $method = getenv('REQUEST_METHOD'); 94 | } 95 | 96 | $method = strtolower($method); 97 | 98 | if (is_null($uri)) { 99 | $uri = getenv('REQUEST_URI'); 100 | } 101 | 102 | $uri = $this->prepareQueryString($uri); 103 | 104 | $methodMismatch = false; 105 | 106 | foreach ($this->routes as $route) { 107 | if ($route->matches($uri)) { 108 | if ($route->hasMethod($method)) { 109 | return $route->callAction(); 110 | } 111 | 112 | $methodMismatch = true; 113 | } 114 | } 115 | 116 | if ($methodMismatch) { 117 | throw new MethodNotAllowedException('Method not allowed.'); 118 | } 119 | 120 | throw new NotFoundException('Route not found.'); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Fist/Routing/tests/RoutingTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Router::class, $router); 14 | } 15 | 16 | protected function addRoute(Router $router, $method, array $methods = null, $uri = 'foo', Closure $closure = null) 17 | { 18 | if (is_null($methods)) { 19 | if (strtolower($method) == 'get') { 20 | $methods = ['get', 'head']; 21 | } else { 22 | $methods = [$method]; 23 | } 24 | } 25 | 26 | if (is_null($closure)) { 27 | $closure = function () { 28 | return 'foo'; 29 | }; 30 | } 31 | 32 | $route = $router->$method($uri, $closure); 33 | 34 | $this->assertEquals(array_map('strtolower', $methods), $route->getMethods()); 35 | 36 | foreach ($methods as $m) { 37 | $this->assertTrue($route->hasMethod($m)); 38 | } 39 | 40 | return $route; 41 | } 42 | 43 | public function testAddingGetRoute() 44 | { 45 | $this->addRoute( 46 | new Router(), 47 | 'get' 48 | ); 49 | } 50 | 51 | public function testAddingPostRoute() 52 | { 53 | $this->addRoute( 54 | new Router(), 55 | 'post' 56 | ); 57 | } 58 | 59 | public function testAddingPutRoute() 60 | { 61 | $this->addRoute( 62 | new Router(), 63 | 'put' 64 | ); 65 | } 66 | 67 | public function testAddingPatchRoute() 68 | { 69 | $this->addRoute( 70 | $router = new Router(), 71 | 'patch' 72 | ); 73 | } 74 | 75 | public function testAddingDeleteRoute() 76 | { 77 | $this->addRoute( 78 | new Router(), 79 | 'delete' 80 | ); 81 | } 82 | 83 | public function testAddingHeadRoute() 84 | { 85 | $this->addRoute( 86 | new Router(), 87 | 'head' 88 | ); 89 | } 90 | 91 | public function testAddingConnectRoute() 92 | { 93 | $this->addRoute( 94 | new Router(), 95 | 'connect' 96 | ); 97 | } 98 | 99 | public function testAddingOptionsRoute() 100 | { 101 | $this->addRoute( 102 | new Router(), 103 | 'options' 104 | ); 105 | } 106 | 107 | public function testAddingTraceRoute() 108 | { 109 | $this->addRoute( 110 | new Router(), 111 | 'trace' 112 | ); 113 | } 114 | 115 | public function testAddingAnyRoute() 116 | { 117 | $this->addRoute( 118 | new Router(), 119 | 'any', 120 | ['get', 'post', 'put', 'patch', 'delete', 'head', 'connect', 'options', 'trace'] 121 | ); 122 | } 123 | 124 | public function testRouteMatches() 125 | { 126 | $this->addRoute( 127 | $router = new Router(), 128 | 'get' 129 | ); 130 | 131 | $match = $router->dispatch('get', 'http://localhost/foo'); 132 | 133 | $this->assertEquals('foo', $match); 134 | } 135 | 136 | public function testRouteMatchesUppercaseMethods() 137 | { 138 | $this->addRoute( 139 | $router = new Router(), 140 | 'get' 141 | ); 142 | 143 | $match = $router->dispatch('GET', 'http://localhost/foo'); 144 | 145 | $this->assertEquals('foo', $match); 146 | 147 | $this->addRoute( 148 | $router = new Router(), 149 | 'GET' 150 | ); 151 | 152 | $match = $router->dispatch('get', 'http://localhost/foo'); 153 | 154 | $this->assertEquals('foo', $match); 155 | } 156 | 157 | /** 158 | * @expectedException Fist\Routing\NotFoundException 159 | * @expectedExceptionMessage Route not found. 160 | */ 161 | public function testRouteFailsMatchingUri() 162 | { 163 | $this->addRoute( 164 | $router = new Router(), 165 | 'get' 166 | ); 167 | 168 | $router->dispatch('get', 'http://localhost/bar'); 169 | } 170 | 171 | /** 172 | * @expectedException Fist\Routing\MethodNotAllowedException 173 | * @expectedExceptionMessage Method not allowed. 174 | */ 175 | public function testRouteFailsMatchingMethod() 176 | { 177 | $this->addRoute( 178 | $router = new Router(), 179 | 'get' 180 | ); 181 | 182 | $router->dispatch('post', 'http://localhost/foo'); 183 | } 184 | 185 | public function testRouteWithSlashes() 186 | { 187 | $router = new Router(); 188 | 189 | $this->addRoute($router, 'get', null, 'foo/bar/baz'); 190 | 191 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/foo/bar/baz')); 192 | } 193 | 194 | public function testRouteWithSimpleRegex() 195 | { 196 | $router = new Router(); 197 | 198 | $this->addRoute($router, 'get', null, 'users/(.+)'); 199 | 200 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/mark')); 201 | } 202 | 203 | public function testRouteWithNamedValues() 204 | { 205 | $router = new Router(); 206 | 207 | $this->addRoute($router, 'get', null, 'users/{name}'); 208 | 209 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/mark')); 210 | } 211 | 212 | public function testRouteWithMultipleNamedValues() 213 | { 214 | $router = new Router(); 215 | 216 | $this->addRoute($router, 'get', null, 'users/{name}/{id}'); 217 | 218 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/mark/1')); 219 | } 220 | 221 | public function testRouteWithNamedRegex() 222 | { 223 | $router = new Router(); 224 | 225 | $this->addRoute($router, 'get', null, 'users/{name:[0-9]}'); 226 | 227 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/1')); 228 | } 229 | 230 | /** 231 | * @expectedException Fist\Routing\NotFoundException 232 | * @expectedExceptionMessage Route not found. 233 | */ 234 | public function testRouteWithNamedRegexFailsSinceItAllowsOnlyNumbers() 235 | { 236 | $router = new Router(); 237 | 238 | $this->addRoute($router, 'get', null, 'users/{name:[0-9]}'); 239 | 240 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/mark')); 241 | } 242 | 243 | public function testRouteWithWhereStatements() 244 | { 245 | $router = new Router(); 246 | 247 | $this->addRoute($router, 'get', null, 'users/{name}') 248 | ->where('name', '[0-9]'); 249 | 250 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/1')); 251 | } 252 | 253 | /** 254 | * @expectedException Fist\Routing\NotFoundException 255 | * @expectedExceptionMessage Route not found. 256 | */ 257 | public function testRouteWithWhereStatementsFailsUsingString() 258 | { 259 | $router = new Router(); 260 | 261 | $this->addRoute($router, 'get', null, 'users/{name}') 262 | ->where('name', '[0-9]'); 263 | 264 | $this->assertEquals('foo', $router->dispatch('get', 'http://localhost/users/mark')); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/Fist/Routing/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.6.4" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~5.4" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Fist\\Testing\\": "src/" 26 | } 27 | }, 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "1.0-dev" 31 | } 32 | }, 33 | "minimum-stability": "dev" 34 | } 35 | -------------------------------------------------------------------------------- /src/Fist/Testing/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fistlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fist/Testing/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fist/Testing/src/TestCase.php: -------------------------------------------------------------------------------- 1 | fail('Method did not throw exception as expected.'); 17 | } catch (PHPUnit_Framework_AssertionFailedError $e) { 18 | throw $e; 19 | } catch (Exception $e) { 20 | if (! is_null($class)) { 21 | $this->assertEquals($class, get_class($e)); 22 | } 23 | 24 | if (is_array($messages)) { 25 | $this->assertTrue(in_array($e->getMessage(), $messages)); 26 | } elseif (! is_null($messages)) { 27 | $this->assertEquals($messages, $e->getMessage()); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Fist/Testing/src/WithDatabase.php: -------------------------------------------------------------------------------- 1 | testConnections as $connection => $values) { 13 | $this->setDatabaseConnection($database, $connection, $values); 14 | 15 | $driver = isset($values['driver']) ? $values['driver'] : null; 16 | 17 | $this->setDatabaseDriver($database, $driver); 18 | } 19 | } 20 | 21 | protected function setDatabaseConnection(Database $database, $name, $value = null) 22 | { 23 | if (is_null($value)) { 24 | $value = $this->testConnections[$name]; 25 | } 26 | 27 | $database->setConnection($name, $value); 28 | } 29 | 30 | protected function setDatabaseDriver(Database $database, $name) 31 | { 32 | $class = 'Fist\\Database\\Connectors\\'.ucfirst($name).'Connection'; 33 | 34 | $database->setDriver($name, $class); 35 | } 36 | 37 | protected function runOnDatabaseConnections(Database $database, array $connections, Closure $closure) 38 | { 39 | foreach ($connections as $connection) { 40 | $closure($database->connection($connection)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Fist/Testing/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |