├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── AbstractSpecification.php ├── AndSpec.php ├── Commands │ └── SpecificationGeneratorCommand.php ├── CompositeSpecification.php ├── NotSpec.php ├── OrSpec.php ├── Providers │ └── SpecificationServiceProvider.php ├── SpecificationInterface.php ├── config │ └── specification.php └── views │ └── specification.blade.php └── tests ├── DummySpecifications ├── AgeOfPersonSpecification.php └── MalePersonSpecification.php ├── SpecificationTest.php └── bootstrap.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap/compiled.php 2 | .env.*.php 3 | .env.php 4 | .env 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## NEXT - YYYY-MM-DD 4 | To look at returning failed specifications 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chalcedonyt/laravel-specification/2f9ac005c05157abc381482c4689059b2731fd71/CONTRIBUTING.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Timothy Teoh 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 13 | > all 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 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-specification 2 | 3 | 4 | An adaptation of the Specification Pattern as done by https://github.com/domnikl/DesignPatternsPHP, adding an artisan command to quickly create specifications. 5 | 6 | ## Install 7 | 8 | Via Composer (please change your `minimum-stability` to "dev") 9 | 10 | ``` bash 11 | $ composer require chalcedonyt/laravel-specification 12 | ``` 13 | Then run `composer update`. Once composer is finished, add the service provider to the `providers` array in `app/config/app.php`: 14 | ``` 15 | Chalcedonyt\Specification\Providers\SpecificationServiceProvider::class 16 | ``` 17 | 18 | ## Generating Specifications 19 | 20 | An artisan command will be added to quickly create specifications. 21 | ``` php 22 | php artisan make:specification [NameOfSpecification] 23 | ``` 24 | Adding a `--parameters` flag will prompts for parameters to be inserted into the constructor when generated: 25 | ``` 26 | Enter the class or variable name for parameter 0 (Examples: \App\User or $value) [Blank to stop entering parameters] [(no_param)]: 27 | > \App\User 28 | 29 | Enter the class or variable name for parameter 1 (Examples: \App\User or $value) [Blank to stop entering parameters] [(no_param)]: 30 | > $my_value 31 | ``` 32 | Results in 33 | 34 | ```php 35 | class NewSpecification extends AbstractSpecification 36 | { 37 | 38 | /** 39 | * @var \App\User 40 | */ 41 | protected $user; 42 | 43 | /** 44 | * @var 45 | */ 46 | protected $myValue; 47 | 48 | 49 | /** 50 | * 51 | * @param \App\User $user 52 | * @param $myValue 53 | * 54 | */ 55 | public function __construct(\App\User $user, $my_value) 56 | { 57 | $this->user = $user; 58 | $this->myValue = $my_value; 59 | } 60 | 61 | /** 62 | * Tests an object and returns a boolean value 63 | * 64 | * @var mixed 65 | */ 66 | 67 | public function isSatisfiedBy($candidate) 68 | { 69 | //return a boolean value 70 | } 71 | 72 | } 73 | ``` 74 | 75 | ## Usage 76 | ``` php 77 | class AgeOfPersonSpecification extends \Chalcedonyt\Specification\AbstractSpecification 78 | { 79 | 80 | protected $minAge; 81 | protected $maxAge; 82 | 83 | /** 84 | * Set properties here for a parameterized specification. 85 | * 86 | */ 87 | public function __construct($min_age, $max_age) 88 | { 89 | $this->minAge = $min_age; 90 | $this->maxAge = $max_age; 91 | } 92 | 93 | /** 94 | * Tests an object and returns a boolean value 95 | * 96 | * @var Array $candidate 97 | */ 98 | 99 | public function isSatisfiedBy($candidate) 100 | { 101 | return $this->minAge <= $candidate['age'] && $this->maxAge >= $candidate['age']; 102 | } 103 | 104 | } 105 | ``` 106 | 107 | 108 | 109 | ```php 110 | class MalePersonSpecification extends \Chalcedonyt\Specification\AbstractSpecification 111 | { 112 | const GENDER_MALE = 1; 113 | const GENDER_FEMALE = 2; 114 | 115 | /** 116 | * Tests an object and returns a boolean value 117 | * 118 | * @var Array candidate 119 | */ 120 | 121 | public function isSatisfiedBy($candidate) 122 | { 123 | return $candidate['gender'] == self::GENDER_MALE; 124 | } 125 | } 126 | ``` 127 | 128 | ```php 129 | $adult_spec = new AgeOfPersonSpecification(21, 80); 130 | $teenager_spec = new AgeOfPersonSpecification(13, 19); 131 | $puberty_spec = new AgeOfPersonSpecification(15, 19); 132 | 133 | $male_spec = new MalePersonSpecification; 134 | $female_spec = new NotSpec($male_spec); 135 | 136 | $young_teenage_female = ['age' => 13, 'gender' => MalePersonSpecification::GENDER_FEMALE ]; 137 | $teenage_female = ['age' => 15, 'gender' => MalePersonSpecification::GENDER_FEMALE ]; 138 | $adult_female = ['age' => 22, 'gender' => MalePersonSpecification::GENDER_FEMALE ]; 139 | 140 | $nested_female_spec = $puberty_spec->andSpec( $teenager_spec->andSpec( $female_spec ) ); 141 | $this->assertEquals( $nested_female_spec->isSatisfiedBy( $teenage_female ), true ); 142 | $this->assertEquals( $nested_female_spec->isSatisfiedBy( $young_teenage_female ), false ); 143 | 144 | $any_young_female_spec = $female_spec->andSpec( $teenager_spec->orSpec( $puberty_spec )); 145 | $this->assertEquals( $nested_female_spec->isSatisfiedBy( $teenage_female ), true ); 146 | $this->assertEquals( $nested_female_spec->isSatisfiedBy( $adult_female ), false ); 147 | ``` 148 | 149 | You may also retrieve unfulfilled specifications via the `remainderUnsatisfiedBy` property 150 | 151 | ```php 152 | $any_age_spec = new AgeOfPersonSpecification(1, 80); 153 | 154 | $male_spec = new MalePersonSpecification; 155 | $female_spec = new NotSpec($male_spec); 156 | 157 | $male = ['age' => 16, 'gender' => MalePersonSpecification::GENDER_MALE ]; 158 | 159 | $any_young_female_spec = new AndSpec( $female_spec, $any_age_spec ); 160 | $this->assertEquals( $any_young_female_spec->isSatisfiedBy( $male ), false ); 161 | 162 | //returns the $female_spec 163 | $unfulfilled_spec = $any_young_female_spec->remainderUnsatisfiedBy( $male ); 164 | $inverse_female_spec = new NotSpec( $unfulfilled_spec ); 165 | $this->assertEquals( $inverse_female_spec->isSatisfiedBy( $male ), true ); 166 | ``` 167 | ## Change log 168 | 169 | * 0.4.4 You can now create a Specification inside a directory by specifying it in the classname, e.g. `php artisan make:specification MyDir\\MySpec` 170 | * 0.4.2 Removed the `isSatisfiedBy` method from the abstract and interface. This allows type hinting on the $candidate. 171 | * 0.4.1 Tweaked the generated views to use camel_case on any parameters. 172 | * 0.4 Updated console command. You may now specify constructor parameters for the specification generator by entering the `--parameters` flag 173 | * 0.3 Removed functionality to type-hint the argument to isSatisfiedBy, as PHP doesn't allow overloading abstract methods. 174 | * 0.2 Added `remainderUnsatisfiedBy` functions 175 | 176 | ## Testing 177 | 178 | ## Credits 179 | 180 | - Dominic Liebler [https://github.com/domnikl/DesignPatternsPHP] 181 | - C# Specification Framework by Firzen [https://specification.codeplex.com/] 182 | - The original Specification Pattern document by Eric Evans and Martin Fowler [http://www.martinfowler.com/apsupp/spec.pdf] 183 | 184 | ## License 185 | 186 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 187 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chalcedonyt/laravel-specification", 3 | "description": "Implementation of the specification pattern", 4 | "keywords": [ 5 | "laravel", 6 | "specification", 7 | "specification pattern" 8 | ], 9 | "homepage": "https://github.com/chalcedonyt/laravel-specification", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Tim Teoh", 14 | "email": "chalcedonyt@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "illuminate/support": "~5.1", 20 | "php" : ">=5.3.0" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit" : "4.*" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Chalcedonyt\\Specification\\": "src" 28 | } 29 | }, 30 | "scripts": { 31 | "test": "phpunit" 32 | }, 33 | 34 | "extra": { 35 | "branch-alias": { 36 | "dev-master": "0.x-dev" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/AbstractSpecification.php: -------------------------------------------------------------------------------- 1 | isSatisfiedBy( $candidate )) 19 | return $this; 20 | return null; 21 | } 22 | 23 | /** 24 | * Creates a new logical AND specification 25 | * 26 | * @param SpecificationInterface $spec 27 | * 28 | * @return SpecificationInterface 29 | */ 30 | public function andSpec(SpecificationInterface $spec) 31 | { 32 | return new AndSpec($this, $spec); 33 | } 34 | 35 | /** 36 | * Creates a new logical OR composite specification 37 | * 38 | * @param SpecificationInterface $spec 39 | * 40 | * @return SpecificationInterface 41 | */ 42 | public function orSpec(SpecificationInterface $spec) 43 | { 44 | return new OrSpec($this, $spec); 45 | } 46 | 47 | /** 48 | * Creates a new logical NOT specification 49 | * 50 | * @return SpecificationInterface 51 | */ 52 | public function notSpec() 53 | { 54 | return new NotSpec($this); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/AndSpec.php: -------------------------------------------------------------------------------- 1 | left = $left; 30 | $this -> specifications[]= $left; 31 | } 32 | if( $right ) 33 | { 34 | $this -> right = $right; 35 | $this -> specifications[]= $right; 36 | } 37 | } 38 | 39 | /** 40 | * Checks if the composite AND of specifications passes 41 | * 42 | * @param mixed $candidate 43 | * 44 | * @return bool 45 | */ 46 | public function isSatisfiedBy($candidate) 47 | { 48 | foreach( $this -> specifications as $specification ) 49 | { 50 | if( !$specification -> isSatisfiedBy( $candidate )) 51 | return false; 52 | } 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Commands/SpecificationGeneratorCommand.php: -------------------------------------------------------------------------------- 1 | config = $config; 61 | $this->view = $view; 62 | $this->file = $file; 63 | } 64 | 65 | /** 66 | * Execute the console command. 67 | * 68 | * @return mixed 69 | */ 70 | public function handle() 71 | { 72 | try { 73 | // replace all space after ucwords 74 | $classname = preg_replace('/\s+/', '', ucwords($this->argument('classname'))); 75 | $namespace = $this->config->get('specification.namespace'); 76 | $directory = $this->appPath($this->config->get('specification.directory')); 77 | 78 | //retrieves store directory configuration 79 | if( strpos($classname, '\\') !== false ){ 80 | $class_dirs = substr($classname, 0, strrpos( $classname, '\\')); 81 | $directory = $directory.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class_dirs); 82 | $namespace = $namespace.'\\'.$class_dirs; 83 | $classname = substr($classname, strrpos($classname, '\\') + 1); 84 | } 85 | 86 | is_dir($directory) ?: $this->file->makeDirectory($directory, 0755, true); 87 | 88 | $create = true; 89 | $parameters = collect([]); 90 | $parameter_string = ''; 91 | /** 92 | * if we are entering paramters 93 | */ 94 | if( $this -> option('parameters')){ 95 | 96 | $i = 0; 97 | while($parameter = $this -> ask("Enter the class or variable name for parameter ".($i++)." (Examples: \App\User or \$user) [Blank to stop entering parameters]", self::NO_PARAMETER_SPECIFIED)){ 98 | if( $parameter == self::NO_PARAMETER_SPECIFIED ) 99 | break; 100 | 101 | //if class starts with $, don't type hint 102 | if( strpos($parameter, '$') === 0 ){ 103 | $parameter_class = null; 104 | $parameter_name = str_replace('$','',$parameter); 105 | } else{ 106 | /** 107 | * Extract the last element of the class after "\", e.g. App\User -> $user 108 | */ 109 | $derive_variable_name = function() use ($parameter){ 110 | $parts = explode("\\", $parameter); 111 | return end( $parts ); 112 | }; 113 | $parameter_class = $parameter; 114 | $parameter_name = strtolower( $derive_variable_name() ); 115 | } 116 | $parameters -> push(['class' => $parameter_class, 'name' => $parameter_name]); 117 | } 118 | 119 | if( $parameters -> count()) 120 | { 121 | $parameter_string_array = []; 122 | $parameters -> each(function( $p ) use( &$parameter_string_array){ 123 | if( $p['class']) 124 | $parameter_string_array[]=$p['class'].' $'.$p['name']; 125 | else 126 | $parameter_string_array[]='$'.$p['name']; 127 | }); 128 | $parameter_string = implode(', ', $parameter_string_array); 129 | } 130 | } 131 | 132 | 133 | $object_variable = '$candidate'; 134 | if ($this->file->exists("{$directory}/{$classname}.php")) { 135 | if ($usrResponse = strtolower($this->ask("The file ['{$classname}'] already exists, overwrite? [y/n]", 136 | null)) 137 | ) { 138 | switch ($usrResponse) { 139 | case 'y' : 140 | $tempFileName = "{$directory}/{$classname}.php"; 141 | 142 | $prefix = '_'; 143 | while ($this->file->exists($tempFileName)) { 144 | $prefix .= '_'; 145 | $tempFileName = "{$directory}/{$prefix}{$classname}.php"; 146 | } 147 | rename("{$directory}/{$classname}.php", $tempFileName); 148 | break; 149 | default: 150 | $this->info('No file has been created.'); 151 | $create = false; 152 | } 153 | } 154 | 155 | } 156 | $args = ['namespace' => $namespace, 157 | 'classname' => $classname, 158 | 'parameter_string' => $parameter_string, 159 | 'parameters' => $parameters -> all(), 160 | 'object_variable' => $object_variable ]; 161 | 162 | // loading template from views 163 | $view = $this->view->make('specification::specification',$args); 164 | 165 | 166 | if ($create) { 167 | $this->file->put("{$directory}/{$classname}.php", $view->render()); 168 | $this->info("The class {$classname} generated successfully."); 169 | } 170 | 171 | 172 | } catch (\Exception $e) { 173 | $this->error('Specification creation failed: '.$e -> getMessage()); 174 | } 175 | 176 | 177 | } 178 | 179 | /** 180 | * get application path 181 | * 182 | * @param $path 183 | * 184 | * @return string 185 | */ 186 | private function appPath($path) 187 | { 188 | return base_path('/app/' . $path); 189 | } 190 | 191 | /** 192 | * @return array 193 | */ 194 | protected function getArguments() 195 | { 196 | return array( 197 | array('name', InputArgument::REQUIRED, 'Name of the specification class'), 198 | ); 199 | } 200 | 201 | /** 202 | * @return array 203 | */ 204 | protected function getOptions() 205 | { 206 | 207 | return array( 208 | array( 209 | 'directory', 210 | null, 211 | InputOption::VALUE_OPTIONAL, 212 | 'specification store directory (relative to App\)', 213 | null 214 | ), 215 | array( 216 | 'namespace', 217 | null, 218 | InputOption::VALUE_OPTIONAL, 219 | 'specification namespace', 220 | null 221 | ), 222 | ); 223 | } 224 | 225 | 226 | } 227 | -------------------------------------------------------------------------------- /src/CompositeSpecification.php: -------------------------------------------------------------------------------- 1 | isSatisfiedBy( $candidate )) 19 | return null; 20 | else return $this -> remainderUnsatisfiedAsCompositeSpecification( $candidate ); 21 | } 22 | 23 | /** 24 | * Constructs a CompositeSpecification out of the specifications that have not been satisfied 25 | * 26 | * @param mixed $candidate 27 | * @return CompositeSpecification 28 | */ 29 | private function remainderUnsatisfiedAsCompositeSpecification( $candidate ) 30 | { 31 | $composite_spec = new static; 32 | $composite_spec -> specifications = $this -> remaindersUnsatisfiedBy( $candidate ); 33 | return $composite_spec; 34 | } 35 | 36 | /** 37 | * Evaluates every specification and return the ones that fail 38 | * 39 | * @param mixed $candidate 40 | * @return Array Array of SpecificationInterface 41 | */ 42 | private function remaindersUnsatisfiedBy( $candidate ) 43 | { 44 | $unsatisfied = []; 45 | foreach( $this -> specifications as $specification ){ 46 | if( !$specification -> isSatisfiedBy($candidate)) 47 | $unsatisfied[]= $specification; 48 | } 49 | return $unsatisfied; 50 | } 51 | 52 | } 53 | 54 | ?> 55 | -------------------------------------------------------------------------------- /src/NotSpec.php: -------------------------------------------------------------------------------- 1 | spec = $spec; 20 | } 21 | 22 | /** 23 | * Returns the negated result of the wrapped specification 24 | * 25 | * @param Item $candidate 26 | * 27 | * @return bool 28 | */ 29 | public function isSatisfiedBy($candidate) 30 | { 31 | return !$this->spec->isSatisfiedBy($candidate); 32 | } 33 | 34 | public function remainderUnsatisfiedBy( $candidate ) 35 | { 36 | if( $this -> spec -> isSatisfiedBy( $candidate ) ){ 37 | return $this -> spec; 38 | } 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/OrSpec.php: -------------------------------------------------------------------------------- 1 | left = $left; 30 | $this -> specifications[]= $left; 31 | } 32 | if( $right ) 33 | { 34 | $this -> right = $right; 35 | $this -> specifications[]= $right; 36 | } 37 | } 38 | 39 | /** 40 | * Returns the evaluation of all wrapped specifications as a logical OR 41 | * 42 | * @param mixed $candidate 43 | * 44 | * @return bool 45 | */ 46 | public function isSatisfiedBy($candidate) 47 | { 48 | foreach( $this -> specifications as $specification ) 49 | { 50 | if( $specification -> isSatisfiedBy( $candidate )) 51 | return true; 52 | } 53 | return false; 54 | } 55 | } 56 | ?> 57 | -------------------------------------------------------------------------------- /src/Providers/SpecificationServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([$source_config => '../config/specification.php'], 'config'); 18 | $this->loadViewsFrom(__DIR__ . '/../views', 'specification'); 19 | } 20 | 21 | /** 22 | * Register any package services. 23 | * 24 | * @return void 25 | */ 26 | public function register() 27 | { 28 | $source_config = __DIR__ . '/../config/specification.php'; 29 | $this->mergeConfigFrom($source_config, 'specification'); 30 | 31 | // register our command here 32 | 33 | $this->app->singleton('command.specification.generate', 34 | function ($app) { 35 | return new SpecificationGeneratorCommand($app['config'], $app['view'], $app['files']); 36 | } 37 | ); 38 | $this->commands('command.specification.generate'); 39 | } 40 | 41 | /** 42 | * Get the services provided by the provider. 43 | * 44 | * @return array 45 | */ 46 | public function provides() 47 | { 48 | return ['specification', 'command.specification.generate']; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SpecificationInterface.php: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /src/config/specification.php: -------------------------------------------------------------------------------- 1 | env('SPECIFICATION_NAMESPACE', 'App\Specifications'), 10 | /* 11 | |---------------------------------------------------------------------------------------------------------- 12 | | Transformers store directory 13 | | path relative to App/ 14 | |---------------------------------------------------------------------------------------------------------- 15 | */ 16 | 17 | 'directory' => env('SPECIFICATION_DIRECTORY', 'Specifications/') 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /src/views/specification.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | namespace {{ $namespace }}; 5 | 6 | use Chalcedonyt\Specification\AbstractSpecification; 7 | 8 | class {{ $classname }} extends AbstractSpecification 9 | { 10 | 11 | @if (count($parameters)) 12 | @foreach( $parameters as $param) 13 | /** 14 | * @var {{$param['class']}} 15 | */ 16 | protected ${{camel_case($param['name'])}}; 17 | 18 | @endforeach 19 | @endif 20 | 21 | /** 22 | * 23 | @if (!count($parameters)) 24 | * Set properties here for a parameterized specification. 25 | @else 26 | @foreach( $parameters as $param) 27 | * @param {{$param['class']}} ${{camel_case($param['name'])}} 28 | @endforeach 29 | @endif 30 | */ 31 | public function __construct({{$parameter_string}}) 32 | { 33 | @if (count($parameters)) 34 | @foreach( $parameters as $param) 35 | $this -> {{camel_case($param['name'])}} = ${{$param['name']}}; 36 | @endforeach 37 | @endif 38 | } 39 | 40 | /** 41 | * Tests an object and returns a boolean value 42 | * @param mixed 43 | * @return Boolean 44 | */ 45 | 46 | public function isSatisfiedBy({{$object_variable}}) 47 | { 48 | //return a boolean value 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tests/DummySpecifications/AgeOfPersonSpecification.php: -------------------------------------------------------------------------------- 1 | minAge = $min_age; 17 | $this -> maxAge = $max_age; 18 | } 19 | 20 | /** 21 | * Tests an object and returns a boolean value 22 | * 23 | * @var Array $object 24 | */ 25 | 26 | public function isSatisfiedBy( Array $object) 27 | { 28 | return $this -> minAge <= $object['age'] && $this -> maxAge >= $object['age']; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/DummySpecifications/MalePersonSpecification.php: -------------------------------------------------------------------------------- 1 | andSpec( $male_spec ); 27 | $age_or_male_spec = $age_spec -> orSpec( $male_spec ); 28 | $age_not_male_spec = $male_spec -> notSpec(); 29 | 30 | $person = ['age' => 20, 'gender' => MalePersonSpecification::GENDER_MALE]; 31 | $person2 = ['age' => 20, 'gender' => MalePersonSpecification::GENDER_FEMALE]; 32 | 33 | $this -> assertEquals( $age_and_male_spec -> isSatisfiedBy( $person ), true ); 34 | $this -> assertEquals( $age_and_male_spec -> isSatisfiedBy( $person2 ), false ); 35 | $this -> assertEquals( $age_or_male_spec -> isSatisfiedBy( $person2 ), true ); 36 | $this -> assertEquals( $age_not_male_spec -> isSatisfiedBy( $person2 ), true ); 37 | 38 | 39 | } 40 | /** 41 | * Testing nested specs 42 | */ 43 | 44 | public function testNestedSpecs() 45 | { 46 | $adult_spec = new AgeOfPersonSpecification(21, 80); 47 | $teenager_spec = new AgeOfPersonSpecification(13, 19); 48 | $puberty_spec = new AgeOfPersonSpecification(15, 19); 49 | 50 | $male_spec = new MalePersonSpecification; 51 | $female_spec = new NotSpec($male_spec); 52 | 53 | $young_teenage_female = ['age' => 13, 'gender' => MalePersonSpecification::GENDER_FEMALE ]; 54 | $teenage_female = ['age' => 15, 'gender' => MalePersonSpecification::GENDER_FEMALE ]; 55 | $adult_female = ['age' => 22, 'gender' => MalePersonSpecification::GENDER_FEMALE ]; 56 | 57 | $nested_female_spec = $puberty_spec -> andSpec( $teenager_spec -> andSpec( $female_spec ) ); 58 | $this -> assertEquals( $nested_female_spec -> isSatisfiedBy( $teenage_female ), true ); 59 | $this -> assertEquals( $nested_female_spec -> isSatisfiedBy( $young_teenage_female ), false ); 60 | 61 | $any_young_female_spec = $female_spec -> andSpec( $teenager_spec -> orSpec( $puberty_spec )); 62 | $this -> assertEquals( $nested_female_spec -> isSatisfiedBy( $teenage_female ), true ); 63 | $this -> assertEquals( $nested_female_spec -> isSatisfiedBy( $adult_female ), false ); 64 | 65 | } 66 | 67 | /** 68 | * Testing remainder 69 | */ 70 | public function testRemainderUnsatisfiedBy(){ 71 | $any_age_spec = new AgeOfPersonSpecification(1, 80); 72 | 73 | $male_spec = new MalePersonSpecification; 74 | $female_spec = new NotSpec($male_spec); 75 | 76 | $male = ['age' => 16, 'gender' => MalePersonSpecification::GENDER_MALE ]; 77 | 78 | $any_young_female_spec = new AndSpec( $female_spec, $any_age_spec ); 79 | $this -> assertEquals( $any_young_female_spec -> isSatisfiedBy( $male ), false ); 80 | 81 | //returns the $female_spec 82 | $unfulfilled_spec = $any_young_female_spec -> remainderUnsatisfiedBy( $male ); 83 | $inverse_female_spec = new NotSpec( $unfulfilled_spec ); 84 | $this -> assertEquals( $inverse_female_spec -> isSatisfiedBy( $male ), true ); 85 | 86 | } 87 | 88 | } 89 | 90 | ?> 91 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 8 | --------------------------------------------------------------------------------