├── src ├── Test │ ├── Fixtures │ │ ├── Bar.php │ │ ├── Foo.php │ │ └── StringBinding.php │ ├── AbstractPersistentDiscoveryTest.php │ ├── AbstractDiscoveryTest.php │ ├── AbstractBindingTest.php │ └── AbstractEditableDiscoveryTest.php ├── Api │ ├── Binding │ │ ├── Initializer │ │ │ ├── NotInitializedException.php │ │ │ └── BindingInitializer.php │ │ └── Binding.php │ ├── Type │ │ ├── NoSuchTypeException.php │ │ ├── DuplicateTypeException.php │ │ ├── BindingNotAcceptedException.php │ │ ├── MissingParameterException.php │ │ ├── NoSuchParameterException.php │ │ ├── BindingParameter.php │ │ └── BindingType.php │ ├── Discovery.php │ └── EditableDiscovery.php ├── Binding │ ├── ClassBinding.php │ └── AbstractBinding.php ├── NullDiscovery.php ├── AbstractEditableDiscovery.php ├── InMemoryDiscovery.php ├── KeyValueStoreDiscovery.php └── JsonDiscovery.php ├── LICENSE ├── composer.json ├── CHANGELOG.md └── README.md /src/Test/Fixtures/Bar.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test\Fixtures; 13 | 14 | /** 15 | * @since 1.0 16 | * 17 | * @author Bernhard Schussek 18 | * 19 | * @internal 20 | */ 21 | interface Bar 22 | { 23 | const clazz = __CLASS__; 24 | } 25 | -------------------------------------------------------------------------------- /src/Test/Fixtures/Foo.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test\Fixtures; 13 | 14 | /** 15 | * @since 1.0 16 | * 17 | * @author Bernhard Schussek 18 | * 19 | * @internal 20 | */ 21 | interface Foo 22 | { 23 | const clazz = __CLASS__; 24 | } 25 | -------------------------------------------------------------------------------- /src/Api/Binding/Initializer/NotInitializedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Binding\Initializer; 13 | 14 | use RuntimeException; 15 | 16 | /** 17 | * Thrown if a binding is used before being initialized. 18 | * 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | class NotInitializedException extends RuntimeException 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bernhard Schussek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/Api/Type/NoSuchTypeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a binding type was not found. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class NoSuchTypeException extends RuntimeException 25 | { 26 | /** 27 | * Creates an exception for a type name. 28 | * 29 | * @param string $typeName The name of the type. 30 | * @param Exception|null $cause The exception that caused this exception. 31 | * 32 | * @return static The created exception. 33 | */ 34 | public static function forTypeName($typeName, Exception $cause = null) 35 | { 36 | return new static(sprintf( 37 | 'The type "%s" does not exist.', 38 | $typeName 39 | ), 0, $cause); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Api/Type/DuplicateTypeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a duplicate binding type is detected. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class DuplicateTypeException extends RuntimeException 25 | { 26 | /** 27 | * Creates an exception for a type name. 28 | * 29 | * @param string $typeName The name of the type. 30 | * @param Exception|null $cause The exception that caused this exception. 31 | * 32 | * @return static The created exception. 33 | */ 34 | public static function forTypeName($typeName, Exception $cause = null) 35 | { 36 | return new static(sprintf( 37 | 'The type "%s" is already defined.', 38 | $typeName 39 | ), 0, $cause); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puli/discovery", 3 | "description": "Publishes and discovers artifacts across Puli packages.", 4 | "homepage": "http://puli.io", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Bernhard Schussek", 9 | "email": "bschussek@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^5.3.9|^7.0", 14 | "webmozart/assert": "^1.0", 15 | "webmozart/expression": "^1.0", 16 | "webmozart/json": "^1.2" 17 | }, 18 | "require-dev": { 19 | "webmozart/key-value-store": "^1.0-beta7", 20 | "phpunit/phpunit": "^4.6", 21 | "sebastian/version": "^1.0.1" 22 | }, 23 | "suggest": { 24 | "webmozart/json": "to use the JSON discovery", 25 | "webmozart/key-value-store": "to use the key-value store discovery" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Puli\\Discovery\\": "src/" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Puli\\Discovery\\Tests\\": "tests/" 35 | } 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "1.0-dev" 40 | } 41 | }, 42 | "support": { 43 | "issues": "https://github.com/puli/issues/issues" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Api/Type/BindingNotAcceptedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a binding type does not accept a binding. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class BindingNotAcceptedException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception. 28 | * 29 | * @param string $typeName The name of the binding type. 30 | * @param string $bindingClass The class name of the binding. 31 | * @param Exception|null $cause The exception that caused this 32 | * exception. 33 | * 34 | * @return static The created exception. 35 | */ 36 | public static function forBindingClass($typeName, $bindingClass, Exception $cause = null) 37 | { 38 | return new static(sprintf( 39 | 'The type "%s" does accept bindings of class "%s".', 40 | $typeName, 41 | $bindingClass 42 | ), 0, $cause); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Api/Type/MissingParameterException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a binding parameter is missing. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class MissingParameterException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception for the given parameter name. 28 | * 29 | * @param string $parameterName The name of the parameter that was 30 | * missing. 31 | * @param string $typeName The name of the type that the 32 | * parameter was searched on. 33 | * @param Exception|null $cause The exception that caused this 34 | * exception. 35 | * 36 | * @return static The created exception. 37 | */ 38 | public static function forParameterName($parameterName, $typeName, Exception $cause = null) 39 | { 40 | return new static(sprintf( 41 | 'The parameter "%s" is required for type "%s".', 42 | $parameterName, 43 | $typeName 44 | ), 0, $cause); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Api/Type/NoSuchParameterException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a binding parameter was not found. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class NoSuchParameterException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception for the given parameter name. 28 | * 29 | * @param string $parameterName The name of the parameter that was 30 | * not found. 31 | * @param string $typeName The name of the type that the 32 | * parameter was searched on. 33 | * @param Exception|null $cause The exception that caused this 34 | * exception. 35 | * 36 | * @return static The created exception. 37 | */ 38 | public static function forParameterName($parameterName, $typeName, Exception $cause = null) 39 | { 40 | return new static(sprintf( 41 | 'The parameter "%s" does not exist for type "%s".', 42 | $parameterName, 43 | $typeName 44 | ), 0, $cause); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Test/Fixtures/StringBinding.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test\Fixtures; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Binding\AbstractBinding; 16 | 17 | /** 18 | * @since 1.0 19 | * 20 | * @author Bernhard Schussek 21 | * 22 | * @internal 23 | */ 24 | class StringBinding extends AbstractBinding 25 | { 26 | private $string; 27 | 28 | public function __construct($string, $typeName, array $parameterValues = array()) 29 | { 30 | parent::__construct($typeName, $parameterValues); 31 | 32 | $this->string = $string; 33 | } 34 | 35 | public function getString() 36 | { 37 | return $this->string; 38 | } 39 | 40 | public function equals(Binding $other) 41 | { 42 | if (!parent::equals($other)) { 43 | return false; 44 | } 45 | 46 | /* @var StringBinding $other */ 47 | return $this->string === $other->string; 48 | } 49 | 50 | protected function preSerialize(array &$data) 51 | { 52 | parent::preSerialize($data); 53 | 54 | $data[] = $this->string; 55 | } 56 | 57 | protected function postUnserialize(array &$data) 58 | { 59 | $this->string = array_pop($data); 60 | 61 | parent::postUnserialize($data); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Api/Binding/Initializer/BindingInitializer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Binding\Initializer; 13 | 14 | use InvalidArgumentException; 15 | use Puli\Discovery\Api\Binding\Binding; 16 | 17 | /** 18 | * Initializes a {@link Binding} class. 19 | * 20 | * Binding initializers can be used to inject dependencies into newly 21 | * constructed or unserialized {@link Binding} instances. 22 | * 23 | * @since 1.0 24 | * 25 | * @author Bernhard Schussek 26 | */ 27 | interface BindingInitializer 28 | { 29 | /** 30 | * Returns whether the initializer accepts binding of the given class. 31 | * 32 | * @param Binding|string $binding The binding or the fully-qualified name of 33 | * the binding class. 34 | * 35 | * @return bool Returns `true` if bindings of that class can be initialized 36 | * and `false` otherwise. 37 | */ 38 | public function acceptsBinding($binding); 39 | 40 | /** 41 | * Returns the binding class name that can be initialized. 42 | * 43 | * @return string The accepted binding class name. 44 | */ 45 | public function getAcceptedBindingClass(); 46 | 47 | /** 48 | * Initializes a binding. 49 | * 50 | * @param Binding $binding The binding to initialize. 51 | * 52 | * @throws InvalidArgumentException If the passed binding is not supported. 53 | * Use {@link acceptsBinding()} to check 54 | * whether a binding is supported before 55 | * calling this method. 56 | */ 57 | public function initializeBinding(Binding $binding); 58 | } 59 | -------------------------------------------------------------------------------- /src/Binding/ClassBinding.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Binding; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Type\MissingParameterException; 16 | use Puli\Discovery\Api\Type\NoSuchParameterException; 17 | 18 | /** 19 | * Binds a class name to a binding type. 20 | * 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | class ClassBinding extends AbstractBinding 26 | { 27 | /** 28 | * @var string 29 | */ 30 | private $className; 31 | 32 | /** 33 | * Creates a new class binding. 34 | * 35 | * @param string $className The fully-qualified name of the bound 36 | * class. 37 | * @param string $typeName The name of the type to bind against. 38 | * @param array $parameterValues The values of the parameters defined 39 | * for the type. 40 | * 41 | * @throws NoSuchParameterException If an invalid parameter was passed. 42 | * @throws MissingParameterException If a required parameter was not passed. 43 | */ 44 | public function __construct($className, $typeName, array $parameterValues = array()) 45 | { 46 | parent::__construct($typeName, $parameterValues); 47 | 48 | $this->className = $className; 49 | } 50 | 51 | /** 52 | * Returns the name of the bound class. 53 | * 54 | * @return string The fully-qualified class name. 55 | */ 56 | public function getClassName() 57 | { 58 | return $this->className; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function equals(Binding $other) 65 | { 66 | if (!parent::equals($other)) { 67 | return false; 68 | } 69 | 70 | /* @var ClassBinding $other */ 71 | return $this->className === $other->className; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | protected function preSerialize(array &$data) 78 | { 79 | parent::preSerialize($data); 80 | 81 | $data[] = $this->className; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | protected function postUnserialize(array &$data) 88 | { 89 | $this->className = array_pop($data); 90 | 91 | parent::postUnserialize($data); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/NullDiscovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\EditableDiscovery; 16 | use Puli\Discovery\Api\Type\BindingType; 17 | use Puli\Discovery\Api\Type\NoSuchTypeException; 18 | use Webmozart\Expression\Expression; 19 | 20 | /** 21 | * A discovery that does nothing. 22 | * 23 | * This discovery can be used if you need to inject a discovery instance in 24 | * some code, but you don't want that discovery to do anything (for example 25 | * in tests). 26 | * 27 | * @since 1.0 28 | * 29 | * @author Bernhard Schussek 30 | */ 31 | class NullDiscovery implements EditableDiscovery 32 | { 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function findBindings($typeName, Expression $expr = null) 37 | { 38 | return array(); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function hasBindings($typeName = null, Expression $expr = null) 45 | { 46 | return false; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getBindings() 53 | { 54 | return array(); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function hasBindingType($typeName) 61 | { 62 | return false; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function getBindingType($typeName) 69 | { 70 | throw NoSuchTypeException::forTypeName($typeName); 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function hasBindingTypes() 77 | { 78 | return false; 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function getBindingTypes() 85 | { 86 | return array(); 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function addBinding(Binding $binding) 93 | { 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function removeBindings($typeName = null, Expression $expr = null) 100 | { 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function addBindingType(BindingType $type) 107 | { 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function removeBindingType($typeName) 114 | { 115 | } 116 | 117 | /** 118 | * {@inheritdoc} 119 | */ 120 | public function removeBindingTypes() 121 | { 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Api/Type/BindingParameter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use RuntimeException; 15 | use Webmozart\Assert\Assert; 16 | 17 | /** 18 | * A parameter that can be set during binding. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | final class BindingParameter 25 | { 26 | /** 27 | * Flag: The parameter is optional. 28 | */ 29 | const OPTIONAL = 0; 30 | 31 | /** 32 | * Flag: The parameter is required. 33 | */ 34 | const REQUIRED = 1; 35 | 36 | /** 37 | * @var string 38 | */ 39 | private $name; 40 | 41 | /** 42 | * @var int 43 | */ 44 | private $flags; 45 | 46 | /** 47 | * @var mixed 48 | */ 49 | private $defaultValue; 50 | 51 | /** 52 | * Creates a new parameter. 53 | * 54 | * @param string $name The parameter name. 55 | * @param int $flags A bitwise combination of the flag constants 56 | * in this class. 57 | * @param mixed $defaultValue The parameter's default value. 58 | */ 59 | public function __construct($name, $flags = self::OPTIONAL, $defaultValue = null) 60 | { 61 | Assert::stringNotEmpty($name, 'The parameter name must be a non-empty string. Got: %s'); 62 | Assert::startsWithLetter($name, 'The parameter name must start with a letter. Got: %s'); 63 | Assert::nullOrInteger($flags, 'The parameter "$flags" must be an integer or null. Got: %s'); 64 | 65 | if (($flags & self::REQUIRED) && null !== $defaultValue) { 66 | throw new RuntimeException('Required parameters must not have default values.'); 67 | } 68 | 69 | $this->name = $name; 70 | $this->flags = $flags; 71 | $this->defaultValue = $defaultValue; 72 | } 73 | 74 | /** 75 | * Returns the name of the parameter. 76 | * 77 | * @return string The parameter name. 78 | */ 79 | public function getName() 80 | { 81 | return $this->name; 82 | } 83 | 84 | /** 85 | * Returns the flags passed to the constructor. 86 | * 87 | * @return int A bitwise combination of the flag constants in this class. 88 | */ 89 | public function getFlags() 90 | { 91 | return $this->flags; 92 | } 93 | 94 | /** 95 | * Returns the default value of the parameter. 96 | * 97 | * @return mixed The default value. 98 | */ 99 | public function getDefaultValue() 100 | { 101 | return $this->defaultValue; 102 | } 103 | 104 | /** 105 | * Returns whether the parameter is required. 106 | * 107 | * @return bool Returns `true` if the parameter is required and `false` 108 | * otherwise. 109 | */ 110 | public function isRequired() 111 | { 112 | return (bool) ($this->flags & self::REQUIRED); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Api/Discovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Type\BindingType; 16 | use Puli\Discovery\Api\Type\NoSuchTypeException; 17 | use Webmozart\Expression\Expression; 18 | 19 | /** 20 | * Discovers artifacts. 21 | * 22 | * The discovery allows to bind and retrieve "artifacts" using binding types 23 | * known to the binder and the retriever. Artifacts can be anything, for 24 | * example class names or Puli resources. 25 | * 26 | * Bindings can be accessed with the {@link findBindings()} method. Here is an 27 | * example for accessing class bindings bound to a binding type with the name 28 | * `Example\Engine\Plugin`: 29 | * 30 | * ```php 31 | * use Example\Engine\Plugin; 32 | * 33 | * foreach ($discovery->findBindings(Plugin::class) as $binding) { 34 | * $className = $binding->getClassName(); 35 | * 36 | * // instantiate the plugin 37 | * $plugin = new $className(); 38 | * 39 | * // ... 40 | * } 41 | * ``` 42 | * 43 | * Use instances of {@link EditableDiscovery} to add bindings and binding types 44 | * to a discovery. 45 | * 46 | * @since 1.0 47 | * 48 | * @author Bernhard Schussek 49 | */ 50 | interface Discovery 51 | { 52 | /** 53 | * Returns all bindings bound to the given binding type. 54 | * 55 | * You can optionally pass parameter values to only return bindings with 56 | * the given parameter values. 57 | * 58 | * This method returns an empty array if the given type is not defined. 59 | * 60 | * @param string $typeName The name of the binding type. 61 | * @param Expression|null $expr The expression to filter by. 62 | * 63 | * @return Binding[] The matching bindings. 64 | */ 65 | public function findBindings($typeName, Expression $expr = null); 66 | 67 | /** 68 | * Returns whether the discovery contains bindings. 69 | * 70 | * You can optionally pass the name of a binding type and parameter values 71 | * to only check for bindings with that binding type/parameter values. 72 | * If you pass parameter values, you must also pass a type name. 73 | * 74 | * This method returns `false` if the passed type does not exist. 75 | * 76 | * @param string|null $typeName The name of the binding type. 77 | * @param Expression|null $expr The expression to filter by. 78 | * 79 | * @return bool Returns whether the discovery contains matching bindings. 80 | */ 81 | public function hasBindings($typeName = null, Expression $expr = null); 82 | 83 | /** 84 | * Returns all bindings. 85 | * 86 | * @return Binding[] The bindings. 87 | */ 88 | public function getBindings(); 89 | 90 | /** 91 | * Returns whether a binding type exists. 92 | * 93 | * @param string $typeName The name of a binding type. 94 | * 95 | * @return bool Returns `true` if the binding type exists and `false` 96 | * otherwise. 97 | */ 98 | public function hasBindingType($typeName); 99 | 100 | /** 101 | * Returns the binding type with the given name. 102 | * 103 | * @param string $typeName The name of a binding type. 104 | * 105 | * @return BindingType The binding type. 106 | * 107 | * @throws NoSuchTypeException If a type with that name does not exist. 108 | */ 109 | public function getBindingType($typeName); 110 | 111 | /** 112 | * Returns whether any binding types have been defined. 113 | * 114 | * @return bool Returns `true` if the discovery contains binding types and 115 | * `false` otherwise. 116 | */ 117 | public function hasBindingTypes(); 118 | 119 | /** 120 | * Returns all defined binding types. 121 | * 122 | * @return BindingType[] The defined binding types. 123 | */ 124 | public function getBindingTypes(); 125 | } 126 | -------------------------------------------------------------------------------- /src/Api/EditableDiscovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Type\BindingNotAcceptedException; 16 | use Puli\Discovery\Api\Type\BindingType; 17 | use Puli\Discovery\Api\Type\DuplicateTypeException; 18 | use Puli\Discovery\Api\Type\MissingParameterException; 19 | use Puli\Discovery\Api\Type\NoSuchParameterException; 20 | use Puli\Discovery\Api\Type\NoSuchTypeException; 21 | use Webmozart\Expression\Expression; 22 | 23 | /** 24 | * A discovery that supports the addition and removal of bindings and types. 25 | * 26 | * Binding types have a name and optionally one or more parameters. Binding 27 | * types can be added with the {@link addBindingType()} method: 28 | * 29 | * ```php 30 | * use Puli\Discovery\Api\Type\BindingParameter; 31 | * use Puli\Discovery\Api\Type\BindingType; 32 | * 33 | * $discovery->addBindingType(new BindingType(Extension::class, ClassBinding::class, array( 34 | * new BindingParameter('alias'), 35 | * )); 36 | * ``` 37 | * 38 | * Bindings can be added for these types with the {@link addBinding()} method: 39 | * 40 | * ```php 41 | * $discovery->addBinding( 42 | * new ClassBinding(BlogExtension::class, Extension::class, array( 43 | * 'alias' => 'blog', 44 | * ), 45 | * ); 46 | * ``` 47 | * 48 | * Use {@link findBindings()} to retrieve bindings for a given type: 49 | * 50 | * ```php 51 | * $bindings = $discovery->findBindings(Extension::class); 52 | * 53 | * foreach ($bindings as $binding) { 54 | * $className = $binding->getClassName(); 55 | * $loader->loadExtension(new $className()); 56 | * } 57 | * ``` 58 | * 59 | * @since 1.0 60 | * 61 | * @author Bernhard Schussek 62 | */ 63 | interface EditableDiscovery extends Discovery 64 | { 65 | /** 66 | * Adds a binding to the discovery. 67 | * 68 | * The type of the binding must have been added to the discovery. 69 | * 70 | * Duplicate bindings are allowed. 71 | * 72 | * @param Binding $binding The binding to add. 73 | * 74 | * @throws NoSuchParameterException If an invalid parameter was passed. 75 | * @throws MissingParameterException If a required parameter was not passed. 76 | * @throws NoSuchTypeException If the type of the binding does not 77 | * exist. 78 | * @throws BindingNotAcceptedException If the type of the binding does not 79 | * accept the binding. 80 | */ 81 | public function addBinding(Binding $binding); 82 | 83 | /** 84 | * Removes all bindings from the discovery. 85 | * 86 | * You can optionally filter bindings by type and parameter values. If you 87 | * pass parameter values, you must pass a type as well. 88 | * 89 | * If no matching bindings are found or if the type does not exist this 90 | * method does nothing. 91 | * 92 | * @param string|null $typeName The name of the binding type or `null` 93 | * to remove all bindings. 94 | * @param Expression|null $expr The expression to filter by. 95 | */ 96 | public function removeBindings($typeName = null, Expression $expr = null); 97 | 98 | /** 99 | * Adds a binding type to the discovery. 100 | * 101 | * @param BindingType $type The type to add. 102 | * 103 | * @throws DuplicateTypeException If a binding type with the same name exists. 104 | */ 105 | public function addBindingType(BindingType $type); 106 | 107 | /** 108 | * Removes a binding type from the discovery. 109 | * 110 | * If the binding type is not found, this method does nothing. 111 | * 112 | * All bindings for the type are removed as well. 113 | * 114 | * @param string $typeName The name of the binding type. 115 | */ 116 | public function removeBindingType($typeName); 117 | 118 | /** 119 | * Removes all binding types and bindings from the discovery. 120 | */ 121 | public function removeBindingTypes(); 122 | } 123 | -------------------------------------------------------------------------------- /src/Api/Binding/Binding.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Binding; 13 | 14 | use Puli\Discovery\Api\Binding\Initializer\NotInitializedException; 15 | use Puli\Discovery\Api\Type\BindingNotAcceptedException; 16 | use Puli\Discovery\Api\Type\BindingType; 17 | use Puli\Discovery\Api\Type\MissingParameterException; 18 | use Puli\Discovery\Api\Type\NoSuchParameterException; 19 | use Serializable; 20 | 21 | /** 22 | * Binds an artifact to a binding type. 23 | * 24 | * @since 1.0 25 | * 26 | * @author Bernhard Schussek 27 | */ 28 | interface Binding extends Serializable 29 | { 30 | /** 31 | * Initializes the binding. 32 | * 33 | * This method must be called after constructing or unserializing the 34 | * binding. 35 | * 36 | * @param BindingType $type The binding type. 37 | * 38 | * @throws NoSuchParameterException If a parameter is set that does not 39 | * exist on the loaded type. 40 | * @throws MissingParameterException If a required parameter of the loaded 41 | * type is not set on the binding. 42 | * @throws BindingNotAcceptedException If the passed type does not accept 43 | * the binding. 44 | */ 45 | public function initialize(BindingType $type); 46 | 47 | /** 48 | * Returns whether the binding is initialized. 49 | * 50 | * @return bool Returns `true` if the binding is initialized and `false` 51 | * otherwise. 52 | */ 53 | public function isInitialized(); 54 | 55 | /** 56 | * Returns whether the binding equals another binding. 57 | * 58 | * @param Binding $other The other binding. 59 | * 60 | * @return bool Returns `true` if the binding equals the other binding and 61 | * `false` otherwise. 62 | */ 63 | public function equals(Binding $other); 64 | 65 | /** 66 | * Returns the name of the bound type. 67 | * 68 | * @return string The name of the bound type. 69 | */ 70 | public function getTypeName(); 71 | 72 | /** 73 | * Returns the bound type. 74 | * 75 | * @return BindingType The bound type. 76 | * 77 | * @throws NotInitializedException If the binding has not yet been 78 | * initialized. 79 | */ 80 | public function getType(); 81 | 82 | /** 83 | * Returns the parameters of the binding. 84 | * 85 | * @param bool $includeDefault Whether to include the default values set 86 | * in the binding type. 87 | * 88 | * @return array The parameter values of the binding. 89 | */ 90 | public function getParameterValues($includeDefault = true); 91 | 92 | /** 93 | * Returns whether parameters are set. 94 | * 95 | * @param bool $includeDefault Whether to include the default values set 96 | * in the binding type. 97 | * 98 | * @return bool Returns whether the binding has any parameter values set. 99 | */ 100 | public function hasParameterValues($includeDefault = true); 101 | 102 | /** 103 | * Returns a parameter with a given name. 104 | * 105 | * @param string $parameterName The parameter name. 106 | * @param bool $includeDefault Whether to include the default values set 107 | * in the binding type. 108 | * 109 | * @return mixed The value of the parameter. 110 | * 111 | * @throws NoSuchParameterException If the parameter does not exist. 112 | */ 113 | public function getParameterValue($parameterName, $includeDefault = true); 114 | 115 | /** 116 | * Returns whether the parameter with the given name exists. 117 | * 118 | * @param string $parameterName The parameter name. 119 | * @param bool $includeDefault Whether to include the default values set 120 | * in the binding type. 121 | * 122 | * @return bool Whether that parameter exists. 123 | */ 124 | public function hasParameterValue($parameterName, $includeDefault = true); 125 | } 126 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | * 1.0.0-beta10 (@release_date@) 5 | 6 | * decoupled from ramsey/uuid 7 | * changed `BindingType` to require exactly one accepted binding class 8 | * implemented `Serializable` in `BindingType` to reduce serialized size 9 | * moved classes that depend on puli/repository in that package 10 | * `EditableDiscovery::addBinding()` now accepts duplicates 11 | 12 | * 1.0.0-beta9 (2016-01-14) 13 | 14 | * decoupled from puli/repository 15 | * decoupled from webmozart/glob 16 | * added `JsonDiscovery` 17 | 18 | * 1.0.0-beta8 (2015-10-05) 19 | 20 | * removed `$repo` argument from the constructors of `Discovery` implementations 21 | * added `$initializers` argument to the constructors of `Discovery` 22 | implementations 23 | * renamed `ResourceDiscovery` to `Discovery` 24 | * renamed `ResourceDiscovery::findByType()` to `Discovery::findBindings()` 25 | * renamed `ResourceDiscovery::isTypeDefined()` to `Discovery::hasBindingType()` 26 | * renamed `ResourceDiscovery::getDefinedType()` to `Discovery::getBindingType()` 27 | * renamed `ResourceDiscovery::getDefinedTypes()` to `Discovery::getBindingTypes()` 28 | * removed `ResourceDiscovery::findByPath()` 29 | * added `Discovery::hasBindings()` 30 | * added `Discovery::hasBinding()` 31 | * added `Discovery::getBinding()` 32 | * added `Discovery::hasBindingTypes()` 33 | * renamed `EditableDiscovery::bind()` to `EditableDiscovery::addBinding()` 34 | * renamed `EditableDiscovery::unbind()` to `EditableDiscovery::removeBinding()` 35 | * renamed `EditableDiscovery::defineType()` to `EditableDiscovery::addBindingType()` 36 | * renamed `EditableDiscovery::undefineType()` to `EditableDiscovery::removeBindingType()` 37 | * renamed `EditableDiscovery::clear()` to `EditableDiscovery::removeBindingTypes()` 38 | * added `EditableDiscovery::removeBindings()` 39 | * added `Binding` 40 | * removed `ResourceBinding` interface 41 | * added `ResourceBinding` class 42 | * added `BindingInitializer` 43 | * added `NotInitializedException` 44 | * added `NoSuchBindingException` 45 | * added `BindingNotAcceptedException` 46 | * moved `BindingParameter` to `Puli\Discovery\Api\Type` namespace 47 | * moved `BindingType` to `Puli\Discovery\Api\Type` namespace 48 | * moved `DuplicateTypeException` to `Puli\Discovery\Api\Type` namespace 49 | * moved `MissingParameterException` to `Puli\Discovery\Api\Type` namespace 50 | * moved `NoSuchParameterException` to `Puli\Discovery\Api\Type` namespace 51 | * moved `NoSuchTypeException` to `Puli\Discovery\Api\Type` namespace 52 | * added parameter `$acceptedBindings` to constructor of `BindingType` 53 | * added `BindingType::hasParameters()` 54 | * added `BindingType::acceptsBinding()` 55 | * added `BindingType::getAcceptedBindings()` 56 | * removed `ParameterValidator` interface 57 | * renamed `SimpleParameterValidator` class to `ParameterValidator` 58 | * changed `AbstractBinding` to implement `Binding` 59 | * added `ClassBinding` 60 | * removed `EagerBinding` 61 | * removed `LazyBinding` 62 | * added `ResourceBindingInitializer` 63 | * adapted data structures stored by `KeyValueStoreDiscovery` 64 | * added support for search/removal using `Expression` instances 65 | 66 | * 1.0.0-beta7 (2015-08-24) 67 | 68 | * fixed minimum package versions in composer.json 69 | 70 | * 1.0.0-beta6 (2015-08-12) 71 | 72 | * upgraded to webmozart/glob 3.0 73 | 74 | * 1.0.0-beta5 (2015-05-29)R 75 | 76 | * fixed: no exception is thrown by `KeyValueStoreDiscovery::findByPath()` if 77 | the discovery contains the requested type, but no bindings 78 | * fixed: no exception is thrown by `InMemoryDiscovery::findByPath()` if 79 | the discovery contains the requested type, but no bindings 80 | * `ResourceDiscovery::findByPath()` now throws an exception if the path does 81 | not exist 82 | 83 | * 1.0.0-beta4 (2015-04-13) 84 | 85 | * `LazyBinding` does not cache resources anymore in case the repository 86 | contents changed since the last call 87 | * removed `NoQueryMatchesException` 88 | * changed boolean parameter `$required` to integer parameter `$flags` in 89 | `BindingParameter::__construct()` 90 | * removed `$code` arguments from static exception factory methods 91 | * upgraded to webmozart/glob 2.0 92 | 93 | * 1.0.0-beta3 (2015-03-19) 94 | 95 | * replaced `Assert` by webmozart/assert 96 | * renamed `ResourceDiscovery::find()` to `findByType()` 97 | * split `ResourceDiscovery::getBindings()` into `findByPath()` and 98 | `getBindings()` without arguments 99 | 100 | * 1.0.0-beta2 (2015-01-27) 101 | 102 | * added `NullDiscovery` 103 | * changed `ResourceDiscovery::find()` and `getBindings()` to throw a 104 | `NoSuchTypeException` if the type has not been defined 105 | * removed dependency to beberlei/assert 106 | 107 | * 1.0.0-beta (2015-01-12) 108 | 109 | * first release 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Puli Discovery Component 2 | ============================ 3 | 4 | [![Build Status](https://travis-ci.org/puli/discovery.svg?branch=master)](https://travis-ci.org/puli/discovery) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/wmg14bydks4xwqs2/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/discovery/branch/master) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/puli/discovery/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/puli/discovery/?branch=master) 7 | [![Latest Stable Version](https://poser.pugx.org/puli/discovery/v/stable.svg)](https://packagist.org/packages/puli/discovery) 8 | [![Total Downloads](https://poser.pugx.org/puli/discovery/downloads.svg)](https://packagist.org/packages/puli/discovery) 9 | [![Dependency Status](https://www.versioneye.com/php/puli:discovery/1.0.0/badge.svg)](https://www.versioneye.com/php/puli:discovery/1.0.0) 10 | 11 | Latest release: [1.0.0-beta9](https://packagist.org/packages/puli/discovery#1.0.0-beta9) 12 | 13 | PHP >= 5.3.9 14 | 15 | The [Puli] Discovery Component supports binding of Puli resources to *binding 16 | types*. Binding types can be defined with the `addBindingType()` method of the 17 | [`EditableDiscovery`] interface: 18 | 19 | ```php 20 | use Puli\Discovery\Api\Type\BindingType; 21 | use Puli\Discovery\Binding\Initializer\ResourceBindingInitializer; 22 | use Puli\Discovery\InMemoryDiscovery; 23 | 24 | $discovery = new InMemoryDiscovery(array( 25 | // $repo is a Puli\Repository\Api\ResourceRepository instance 26 | new ResourceBindingInitializer($repo), 27 | )); 28 | 29 | $discovery->addBindingType(new BindingType('doctrine/xml-mapping')); 30 | ``` 31 | 32 | Resource Bindings 33 | ----------------- 34 | 35 | Resources in the repository can then be bound to the defined type by passing a 36 | `ResourceBinding` to `addBinding()`: 37 | 38 | ```php 39 | use Puli\Discovery\Binding\ResourceBinding; 40 | 41 | $discovery->addBinding(new ResourceBinding('/app/config/doctrine/*.xml', 'doctrine/xml-mapping')); 42 | ``` 43 | 44 | With `findBindings()`, you can later retrieve all the bindings for the type: 45 | 46 | ```php 47 | foreach ($discovery->findBindings('doctrine/xml-mapping') as $binding) { 48 | foreach ($binding->getResources() as $resource) { 49 | // do something... 50 | } 51 | } 52 | ``` 53 | 54 | The following [`Discovery`] implementations are currently supported: 55 | 56 | * [`InMemoryDiscovery`] 57 | * [`KeyValueStoreDiscovery`] 58 | * [`NullDiscovery`] 59 | 60 | Read the [Resource Discovery] guide in the Puli documentation to learn more 61 | about resource discovery. 62 | 63 | Class Bindings 64 | -------------- 65 | 66 | You can also bind classes to binding types. By convention, the common interface 67 | of all bound classes is used as binding type: 68 | 69 | ```php 70 | $discovery->addBindingType(new BindingType(Plugin::class)); 71 | ``` 72 | 73 | Classes can be bound by adding `ClassBinding` instances: 74 | 75 | ```php 76 | use Puli\Discovery\Binding\ClassBinding; 77 | 78 | $discovery->addBinding(new ClassBinding(MyPlugin::class, Plugin::class)); 79 | ``` 80 | 81 | As before, use `findBindings()` to find all bindings for a binding type: 82 | 83 | ```php 84 | foreach ($discovery->findBindings(Plugin::class) as $binding) { 85 | $pluginClass = $binding->getClassName(); 86 | $plugin = new $pluginClass(); 87 | 88 | // do something... 89 | } 90 | ``` 91 | 92 | Authors 93 | ------- 94 | 95 | * [Bernhard Schussek] a.k.a. [@webmozart] 96 | * [The Community Contributors] 97 | 98 | Installation 99 | ------------ 100 | 101 | Follow the [Installation guide] guide to install Puli in your project. 102 | 103 | Documentation 104 | ------------- 105 | 106 | Read the [Puli Documentation] to learn more about Puli. 107 | 108 | Contribute 109 | ---------- 110 | 111 | Contributions to Puli are always welcome! 112 | 113 | * Report any bugs or issues you find on the [issue tracker]. 114 | * You can grab the source code at Puli’s [Git repository]. 115 | 116 | Support 117 | ------- 118 | 119 | If you are having problems, send a mail to bschussek@gmail.com or shout out to 120 | [@webmozart] on Twitter. 121 | 122 | License 123 | ------- 124 | 125 | All contents of this package are licensed under the [MIT license]. 126 | 127 | [Puli]: http://puli.io 128 | [Bernhard Schussek]: http://webmozarts.com 129 | [The Community Contributors]: https://github.com/puli/discovery/graphs/contributors 130 | [Resource Discovery]: http://docs.puli.io/en/latest/discovery/introduction.html 131 | [Installation guide]: http://docs.puli.io/en/latest/installation.html 132 | [Puli Documentation]: http://docs.puli.io/en/latest/index.html 133 | [issue tracker]: https://github.com/puli/issues/issues 134 | [Git repository]: https://github.com/puli/discovery 135 | [@webmozart]: https://twitter.com/webmozart 136 | [MIT license]: LICENSE 137 | [`EditableDiscovery`]: http://api.puli.io/latest/class-Puli.Discovery.Api.EditableDiscovery.html 138 | [`Discovery`]: http://api.puli.io/latest/class-Puli.Discovery.Api.Discovery.html 139 | [`InMemoryDiscovery`]: http://api.puli.io/latest/class-Puli.Discovery.InMemoryDiscovery.html 140 | [`KeyValueStoreDiscovery`]: http://api.puli.io/latest/class-Puli.Discovery.KeyValueStoreDiscovery.html 141 | [`NullDiscovery`]: http://api.puli.io/latest/class-Puli.Discovery.NullDiscovery.html 142 | -------------------------------------------------------------------------------- /src/AbstractEditableDiscovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Binding\Initializer\BindingInitializer; 16 | use Puli\Discovery\Api\EditableDiscovery; 17 | use Puli\Discovery\Api\Type\BindingNotAcceptedException; 18 | use Webmozart\Assert\Assert; 19 | use Webmozart\Expression\Expression; 20 | 21 | /** 22 | * Base class for editable discoveries. 23 | * 24 | * @since 1.0 25 | * 26 | * @author Bernhard Schussek 27 | */ 28 | abstract class AbstractEditableDiscovery implements EditableDiscovery 29 | { 30 | /** 31 | * @var BindingInitializer[] 32 | */ 33 | private $initializers; 34 | 35 | /** 36 | * @var BindingInitializer[][] 37 | */ 38 | private $initializersByBindingClass = array(); 39 | 40 | /** 41 | * Creates a new discovery. 42 | * 43 | * @param BindingInitializer[] $initializers The binding initializers to 44 | * apply to newly created or 45 | * unserialized bindings. 46 | */ 47 | public function __construct(array $initializers = array()) 48 | { 49 | $this->initializers = $initializers; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function removeBindings($typeName = null, Expression $expr = null) 56 | { 57 | Assert::nullOrStringNotEmpty($typeName, 'The type name must be a non-empty string. Got: %s'); 58 | 59 | if (null !== $typeName) { 60 | if (null !== $expr) { 61 | $this->removeBindingsWithTypeNameThatMatch($typeName, $expr); 62 | } else { 63 | $this->removeBindingsWithTypeName($typeName); 64 | } 65 | } elseif (null !== $expr) { 66 | $this->removeBindingsThatMatch($expr); 67 | } else { 68 | $this->removeAllBindings(); 69 | } 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function hasBindings($typeName = null, Expression $expr = null) 76 | { 77 | Assert::nullOrStringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 78 | 79 | if (null !== $typeName) { 80 | if (null !== $expr) { 81 | return $this->hasBindingsWithTypeNameThatMatch($typeName, $expr); 82 | } 83 | 84 | return $this->hasBindingsWithTypeName($typeName); 85 | } 86 | 87 | if (null !== $expr) { 88 | return $this->hasBindingsThatMatch($expr); 89 | } 90 | 91 | return $this->hasAnyBinding(); 92 | } 93 | 94 | /** 95 | * Removes all bindings from the discovery. 96 | */ 97 | abstract protected function removeAllBindings(); 98 | 99 | /** 100 | * Removes all bindings from the discovery that match an expression. 101 | * 102 | * @param Expression $expr The expression to filter by. 103 | */ 104 | abstract protected function removeBindingsThatMatch(Expression $expr); 105 | 106 | /** 107 | * Removes all bindings bound to the given binding type. 108 | * 109 | * @param string $typeName The name of the binding type. 110 | */ 111 | abstract protected function removeBindingsWithTypeName($typeName); 112 | 113 | /** 114 | * Removes all bindings bound to the given binding type that match an expression. 115 | * 116 | * @param string $typeName The name of the binding type. 117 | * @param Expression $expr The expression to filter by. 118 | */ 119 | abstract protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr); 120 | 121 | /** 122 | * Returns whether the discovery contains bindings. 123 | * 124 | * @return bool Returns `true` if the discovery has bindings and `false` 125 | * otherwise. 126 | */ 127 | abstract protected function hasAnyBinding(); 128 | 129 | /** 130 | * Returns whether the discovery contains bindings that match an expression. 131 | * 132 | * @param Expression $expr The expression to filter by. 133 | * 134 | * @return bool Returns `true` if the discovery has bindings and `false` 135 | * otherwise. 136 | */ 137 | abstract protected function hasBindingsThatMatch(Expression $expr); 138 | 139 | /** 140 | * Returns whether the discovery contains bindings for the given type. 141 | * 142 | * @param string $typeName The name of the binding type. 143 | * 144 | * @return bool Returns `true` if bindings bound to the given binding type 145 | * are found and `false` otherwise. 146 | */ 147 | abstract protected function hasBindingsWithTypeName($typeName); 148 | 149 | /** 150 | * Returns whether the discovery contains bindings for the given type that 151 | * match an expression. 152 | * 153 | * @param string $typeName The name of the binding type. 154 | * @param Expression $expr The expression to filter by. 155 | * 156 | * @return bool Returns `true` if bindings bound to the given binding type 157 | * are found and `false` otherwise. 158 | */ 159 | abstract protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr); 160 | 161 | /** 162 | * Initializes a binding. 163 | * 164 | * @param Binding $binding The binding to initialize. 165 | * 166 | * @throws BindingNotAcceptedException If the loaded type does not accept 167 | * the binding. 168 | */ 169 | protected function initializeBinding(Binding $binding) 170 | { 171 | $binding->initialize($this->getBindingType($binding->getTypeName())); 172 | 173 | $bindingClass = get_class($binding); 174 | 175 | if (!isset($this->initializersByBindingClass[$bindingClass])) { 176 | $this->initializersByBindingClass[$bindingClass] = array(); 177 | 178 | // Find out which initializers accept the binding 179 | foreach ($this->initializers as $initializer) { 180 | if ($initializer->acceptsBinding($bindingClass)) { 181 | $this->initializersByBindingClass[$bindingClass][] = $initializer; 182 | } 183 | } 184 | } 185 | 186 | // Apply all initializers that we found 187 | foreach ($this->initializersByBindingClass[$bindingClass] as $initializer) { 188 | $initializer->initializeBinding($binding); 189 | } 190 | } 191 | 192 | /** 193 | * Initializes multiple bindings. 194 | * 195 | * @param Binding[] $bindings The bindings to initialize. 196 | */ 197 | protected function initializeBindings(array $bindings) 198 | { 199 | foreach ($bindings as $binding) { 200 | $this->initializeBinding($binding); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/InMemoryDiscovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Type\BindingType; 16 | use Puli\Discovery\Api\Type\DuplicateTypeException; 17 | use Puli\Discovery\Api\Type\NoSuchTypeException; 18 | use Webmozart\Assert\Assert; 19 | use Webmozart\Expression\Expr; 20 | use Webmozart\Expression\Expression; 21 | 22 | /** 23 | * A discovery that holds the bindings in memory. 24 | * 25 | * @since 1.0 26 | * 27 | * @author Bernhard Schussek 28 | */ 29 | class InMemoryDiscovery extends AbstractEditableDiscovery 30 | { 31 | /** 32 | * @var BindingType[] 33 | */ 34 | private $types = array(); 35 | 36 | /** 37 | * @var Binding[][] 38 | */ 39 | private $bindingsByTypeName = array(); 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function addBindingType(BindingType $type) 45 | { 46 | if (isset($this->types[$type->getName()])) { 47 | throw DuplicateTypeException::forTypeName($type->getName()); 48 | } 49 | 50 | $this->types[$type->getName()] = $type; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function removeBindingType($typeName) 57 | { 58 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 59 | 60 | unset($this->types[$typeName]); 61 | 62 | $this->removeBindingsWithTypeName($typeName); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function removeBindingTypes() 69 | { 70 | $this->types = array(); 71 | $this->bindingsByTypeName = array(); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function hasBindingType($typeName) 78 | { 79 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 80 | 81 | return isset($this->types[$typeName]); 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function getBindingType($typeName) 88 | { 89 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 90 | 91 | if (!isset($this->types[$typeName])) { 92 | throw NoSuchTypeException::forTypeName($typeName); 93 | } 94 | 95 | return $this->types[$typeName]; 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function hasBindingTypes() 102 | { 103 | return count($this->types) > 0; 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function getBindingTypes() 110 | { 111 | return array_values($this->types); 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function addBinding(Binding $binding) 118 | { 119 | $typeName = $binding->getTypeName(); 120 | 121 | $this->initializeBinding($binding); 122 | 123 | $this->bindingsByTypeName[$typeName][] = $binding; 124 | } 125 | 126 | /** 127 | * {@inheritdoc} 128 | */ 129 | public function findBindings($typeName, Expression $expr = null) 130 | { 131 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 132 | 133 | if (!isset($this->bindingsByTypeName[$typeName])) { 134 | return array(); 135 | } 136 | 137 | $bindings = $this->bindingsByTypeName[$typeName]; 138 | 139 | if (null !== $expr) { 140 | $bindings = Expr::filter($bindings, $expr); 141 | } 142 | 143 | return array_values($bindings); 144 | } 145 | 146 | /** 147 | * {@inheritdoc} 148 | */ 149 | public function getBindings() 150 | { 151 | $bindings = array(); 152 | 153 | foreach ($this->bindingsByTypeName as $bindingsOfType) { 154 | foreach ($bindingsOfType as $binding) { 155 | $bindings[] = $binding; 156 | } 157 | } 158 | 159 | return $bindings; 160 | } 161 | 162 | /** 163 | * {@inheritdoc} 164 | */ 165 | protected function removeAllBindings() 166 | { 167 | $this->bindingsByTypeName = array(); 168 | } 169 | 170 | /** 171 | * {@inheritdoc} 172 | */ 173 | protected function removeBindingsThatMatch(Expression $expr) 174 | { 175 | foreach ($this->bindingsByTypeName as $typeName => $bindings) { 176 | foreach ($bindings as $key => $binding) { 177 | if ($expr->evaluate($binding)) { 178 | unset($this->bindingsByTypeName[$typeName][$key]); 179 | } 180 | } 181 | 182 | if (0 === count($this->bindingsByTypeName[$typeName])) { 183 | unset($this->bindingsByTypeName[$typeName]); 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * {@inheritdoc} 190 | */ 191 | protected function removeBindingsWithTypeName($typeName) 192 | { 193 | if (!isset($this->bindingsByTypeName[$typeName])) { 194 | return; 195 | } 196 | 197 | unset($this->bindingsByTypeName[$typeName]); 198 | } 199 | 200 | /** 201 | * {@inheritdoc} 202 | */ 203 | protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr) 204 | { 205 | if (!isset($this->bindingsByTypeName[$typeName])) { 206 | return; 207 | } 208 | 209 | foreach ($this->bindingsByTypeName[$typeName] as $key => $binding) { 210 | if ($expr->evaluate($binding)) { 211 | unset($this->bindingsByTypeName[$typeName][$key]); 212 | } 213 | } 214 | 215 | if (0 === count($this->bindingsByTypeName[$typeName])) { 216 | unset($this->bindingsByTypeName[$typeName]); 217 | } 218 | } 219 | 220 | /** 221 | * {@inheritdoc} 222 | */ 223 | protected function hasAnyBinding() 224 | { 225 | return count($this->bindingsByTypeName) > 0; 226 | } 227 | 228 | /** 229 | * {@inheritdoc} 230 | */ 231 | protected function hasBindingsThatMatch(Expression $expr) 232 | { 233 | foreach ($this->bindingsByTypeName as $typeName => $bindings) { 234 | foreach ($bindings as $key => $binding) { 235 | if ($expr->evaluate($binding)) { 236 | return true; 237 | } 238 | } 239 | } 240 | 241 | return false; 242 | } 243 | 244 | /** 245 | * {@inheritdoc} 246 | */ 247 | protected function hasBindingsWithTypeName($typeName) 248 | { 249 | return !empty($this->bindingsByTypeName[$typeName]); 250 | } 251 | 252 | /** 253 | * {@inheritdoc} 254 | */ 255 | protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr) 256 | { 257 | if (!isset($this->bindingsByTypeName[$typeName])) { 258 | return false; 259 | } 260 | 261 | foreach ($this->bindingsByTypeName[$typeName] as $binding) { 262 | if ($expr->evaluate($binding)) { 263 | return true; 264 | } 265 | } 266 | 267 | return false; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/Binding/AbstractBinding.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Binding; 13 | 14 | use InvalidArgumentException; 15 | use Puli\Discovery\Api\Binding\Binding; 16 | use Puli\Discovery\Api\Binding\Initializer\NotInitializedException; 17 | use Puli\Discovery\Api\Type\BindingNotAcceptedException; 18 | use Puli\Discovery\Api\Type\BindingType; 19 | use Puli\Discovery\Api\Type\MissingParameterException; 20 | use Puli\Discovery\Api\Type\NoSuchParameterException; 21 | use Webmozart\Assert\Assert; 22 | 23 | /** 24 | * Base class for bindings. 25 | * 26 | * @since 1.0 27 | * 28 | * @author Bernhard Schussek 29 | */ 30 | abstract class AbstractBinding implements Binding 31 | { 32 | /** 33 | * @var string 34 | */ 35 | private $typeName; 36 | 37 | /** 38 | * @var BindingType|null 39 | */ 40 | private $type; 41 | 42 | /** 43 | * @var array 44 | */ 45 | private $userParameterValues = array(); 46 | 47 | /** 48 | * @var array 49 | */ 50 | private $parameterValues = array(); 51 | 52 | /** 53 | * Creates a new binding. 54 | * 55 | * You can pass parameters that have been defined for the type. If you pass 56 | * unknown parameters, or if a required parameter is missing, an exception 57 | * is thrown. 58 | * 59 | * All parameters that you do not set here will receive the default values 60 | * set for the parameter. 61 | * 62 | * @param string $typeName The name of the type to bind against. 63 | * @param array $parameterValues The values of the parameters defined 64 | * for the type. 65 | * 66 | * @throws NoSuchParameterException If an invalid parameter was passed. 67 | * @throws MissingParameterException If a required parameter was not passed. 68 | */ 69 | public function __construct($typeName, array $parameterValues = array()) 70 | { 71 | Assert::stringNotEmpty($typeName, 'The type name must be a non-empty string. Got: %s'); 72 | 73 | ksort($parameterValues); 74 | 75 | $this->typeName = $typeName; 76 | $this->userParameterValues = $parameterValues; 77 | $this->parameterValues = $parameterValues; 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function initialize(BindingType $type) 84 | { 85 | if ($this->typeName !== $type->getName()) { 86 | throw new InvalidArgumentException(sprintf( 87 | 'The passed type "%s" does not match the configured type "%s".', 88 | $type->getName(), 89 | $this->typeName 90 | )); 91 | } 92 | 93 | if (!$type->acceptsBinding($this)) { 94 | throw BindingNotAcceptedException::forBindingClass($type->getName(), get_class($this)); 95 | } 96 | 97 | // Merge default parameter values of the type 98 | $this->assertParameterValuesValid($this->userParameterValues, $type); 99 | 100 | $this->type = $type; 101 | $this->parameterValues = array_replace($type->getParameterValues(), $this->userParameterValues); 102 | 103 | ksort($this->parameterValues); 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function isInitialized() 110 | { 111 | return null !== $this->type; 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function getTypeName() 118 | { 119 | return $this->typeName; 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function getType() 126 | { 127 | if (null === $this->type) { 128 | throw new NotInitializedException('The binding must be initialized before accessing the type.'); 129 | } 130 | 131 | return $this->type; 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function getParameterValues($includeDefault = true) 138 | { 139 | if ($includeDefault) { 140 | return $this->parameterValues; 141 | } 142 | 143 | return $this->userParameterValues; 144 | } 145 | 146 | /** 147 | * {@inheritdoc} 148 | */ 149 | public function hasParameterValues($includeDefault = true) 150 | { 151 | if ($includeDefault) { 152 | return count($this->parameterValues) > 0; 153 | } 154 | 155 | return count($this->userParameterValues) > 0; 156 | } 157 | 158 | /** 159 | * {@inheritdoc} 160 | */ 161 | public function getParameterValue($parameterName, $includeDefault = true) 162 | { 163 | $parameterValues = $includeDefault ? $this->parameterValues : $this->userParameterValues; 164 | 165 | if (!array_key_exists($parameterName, $parameterValues)) { 166 | throw NoSuchParameterException::forParameterName($parameterName, $this->typeName); 167 | } 168 | 169 | return $parameterValues[$parameterName]; 170 | } 171 | 172 | /** 173 | * {@inheritdoc} 174 | */ 175 | public function hasParameterValue($parameterName, $includeDefault = true) 176 | { 177 | if ($includeDefault) { 178 | return array_key_exists($parameterName, $this->parameterValues); 179 | } 180 | 181 | return array_key_exists($parameterName, $this->userParameterValues); 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function serialize() 188 | { 189 | $data = array(); 190 | 191 | $this->preSerialize($data); 192 | 193 | return serialize($data); 194 | } 195 | 196 | /** 197 | * {@inheritdoc} 198 | */ 199 | public function unserialize($serialized) 200 | { 201 | $data = unserialize($serialized); 202 | 203 | $this->postUnserialize($data); 204 | } 205 | 206 | /** 207 | * {@inheritdoc} 208 | */ 209 | public function equals(Binding $other) 210 | { 211 | if (get_class($this) !== get_class($other)) { 212 | return false; 213 | } 214 | 215 | /** @var AbstractBinding $other */ 216 | if ($this->typeName !== $other->typeName) { 217 | return false; 218 | } 219 | 220 | return $this->userParameterValues === $other->userParameterValues; 221 | } 222 | 223 | protected function preSerialize(array &$data) 224 | { 225 | $data[] = $this->typeName; 226 | $data[] = $this->userParameterValues; 227 | } 228 | 229 | protected function postUnserialize(array &$data) 230 | { 231 | $this->userParameterValues = array_pop($data); 232 | $this->parameterValues = $this->userParameterValues; 233 | $this->typeName = array_pop($data); 234 | } 235 | 236 | private function assertParameterValuesValid(array $parameterValues, BindingType $type) 237 | { 238 | foreach ($parameterValues as $name => $value) { 239 | if (!$type->hasParameter($name)) { 240 | throw NoSuchParameterException::forParameterName($name, $type->getName()); 241 | } 242 | } 243 | 244 | foreach ($type->getParameters() as $parameter) { 245 | if (!isset($parameterValues[$parameter->getName()])) { 246 | if ($parameter->isRequired()) { 247 | throw MissingParameterException::forParameterName($parameter->getName(), $type->getName()); 248 | } 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/Test/AbstractPersistentDiscoveryTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test; 13 | 14 | use Puli\Discovery\Api\Type\BindingParameter; 15 | use Puli\Discovery\Api\Type\BindingType; 16 | use Puli\Discovery\Test\Fixtures\Foo; 17 | use Puli\Discovery\Test\Fixtures\StringBinding; 18 | use Webmozart\Expression\Expr; 19 | 20 | /** 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | abstract class AbstractPersistentDiscoveryTest extends AbstractEditableDiscoveryTest 26 | { 27 | public function testAddBindingKeepsStoredBindings() 28 | { 29 | $discovery = $this->createDiscovery(); 30 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 31 | $discovery->addBinding($binding1 = new StringBinding('string1', Foo::clazz)); 32 | 33 | $discovery = $this->loadDiscoveryFromStorage($discovery); 34 | $discovery->addBinding($binding2 = new StringBinding('string2', Foo::clazz)); 35 | 36 | $this->assertEquals(array($binding1, $binding2), $discovery->getBindings()); 37 | } 38 | 39 | public function testAddBindingInitializesLoadedBindings() 40 | { 41 | $binding1 = new StringBinding('string1', Foo::clazz); 42 | $binding2 = new StringBinding('string2', Foo::clazz); 43 | 44 | $this->initializer->expects($this->once()) 45 | ->method('acceptsBinding') 46 | ->willReturn(true); 47 | 48 | $this->initializer->expects($this->exactly(2)) 49 | ->method('initializeBinding') 50 | ->withConsecutive( 51 | array($binding1), 52 | array($binding2) 53 | ); 54 | 55 | $discovery = $this->createDiscovery(); 56 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 57 | $discovery->addBinding($binding1); 58 | 59 | $discovery = $this->loadDiscoveryFromStorage($discovery, array($this->initializer)); 60 | $discovery->addBinding($binding2); 61 | } 62 | 63 | public function testRemoveBindingsDoesNotInitializeLoadedBindings() 64 | { 65 | $this->initializer->expects($this->never()) 66 | ->method('acceptsBinding'); 67 | 68 | $this->initializer->expects($this->never()) 69 | ->method('initializeBinding'); 70 | 71 | $discovery = $this->createDiscovery(); 72 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 73 | $discovery->addBinding(new StringBinding('string1', Foo::clazz)); 74 | $discovery->addBinding(new StringBinding('string2', Foo::clazz)); 75 | 76 | $discovery = $this->loadDiscoveryFromStorage($discovery, array($this->initializer)); 77 | $discovery->removeBindings(); 78 | } 79 | 80 | public function testRemoveBindingsWithTypeDoesNotInitializeLoadedBindings() 81 | { 82 | $this->initializer->expects($this->never()) 83 | ->method('acceptsBinding'); 84 | 85 | $this->initializer->expects($this->never()) 86 | ->method('initializeBinding'); 87 | 88 | $discovery = $this->createDiscovery(); 89 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 90 | $discovery->addBinding(new StringBinding('string1', Foo::clazz)); 91 | $discovery->addBinding(new StringBinding('string2', Foo::clazz)); 92 | 93 | $discovery = $this->loadDiscoveryFromStorage($discovery, array($this->initializer)); 94 | $discovery->removeBindings(Foo::clazz); 95 | } 96 | 97 | public function testRemoveBindingsWithTypeAndParameterWorksOnLoadedDiscovery() 98 | { 99 | $binding1 = new StringBinding('string1', Foo::clazz, array('param2' => 'bar')); 100 | $binding2 = new StringBinding('string2', Foo::clazz); 101 | $binding3 = new StringBinding('string3', Foo::clazz, array('param1' => 'bar')); 102 | 103 | $discovery = $this->createDiscovery(); 104 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING, array( 105 | new BindingParameter('param1', BindingParameter::OPTIONAL, 'foo'), 106 | new BindingParameter('param2'), 107 | ))); 108 | $discovery->addBinding($binding1); 109 | $discovery->addBinding($binding2); 110 | $discovery->addBinding($binding3); 111 | 112 | $discovery = $this->loadDiscoveryFromStorage($discovery); 113 | 114 | // Bindings need to be initialized for this to work 115 | $discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); 116 | 117 | $this->assertEquals(array($binding3), $discovery->findBindings(Foo::clazz)); 118 | $this->assertEquals(array($binding3), $discovery->getBindings()); 119 | } 120 | 121 | public function testRemoveBindingTypeDoesNotInitializeLoadedBindings() 122 | { 123 | $this->initializer->expects($this->never()) 124 | ->method('acceptsBinding'); 125 | 126 | $this->initializer->expects($this->never()) 127 | ->method('initializeBinding'); 128 | 129 | $discovery = $this->createDiscovery(); 130 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 131 | $discovery->addBinding(new StringBinding('string1', Foo::clazz)); 132 | $discovery->addBinding(new StringBinding('string2', Foo::clazz)); 133 | 134 | $discovery = $this->loadDiscoveryFromStorage($discovery, array($this->initializer)); 135 | $discovery->removeBindingType(Foo::clazz); 136 | } 137 | 138 | public function testFindBindingsInitializesLoadedBindings() 139 | { 140 | $binding1 = new StringBinding('string1', Foo::clazz); 141 | $binding2 = new StringBinding('string2', Foo::clazz); 142 | 143 | $this->initializer->expects($this->once()) 144 | ->method('acceptsBinding') 145 | ->willReturn(true); 146 | 147 | $this->initializer->expects($this->exactly(2)) 148 | ->method('initializeBinding') 149 | ->withConsecutive( 150 | array($binding1), 151 | array($binding2) 152 | ); 153 | 154 | $discovery = $this->createDiscovery(); 155 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 156 | $discovery->addBinding($binding1); 157 | $discovery->addBinding($binding2); 158 | 159 | $discovery = $this->loadDiscoveryFromStorage($discovery, array($this->initializer)); 160 | $discovery->findBindings(Foo::clazz); 161 | } 162 | 163 | public function testGetBindingsInitializesLoadedBindings() 164 | { 165 | $binding1 = new StringBinding('string1', Foo::clazz); 166 | $binding2 = new StringBinding('string2', Foo::clazz); 167 | 168 | $this->initializer->expects($this->once()) 169 | ->method('acceptsBinding') 170 | ->willReturn(true); 171 | 172 | $this->initializer->expects($this->exactly(2)) 173 | ->method('initializeBinding') 174 | ->withConsecutive( 175 | array($binding1), 176 | array($binding2) 177 | ); 178 | 179 | $discovery = $this->createDiscovery(); 180 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 181 | $discovery->addBinding($binding1); 182 | $discovery->addBinding($binding2); 183 | 184 | $discovery = $this->loadDiscoveryFromStorage($discovery, array($this->initializer)); 185 | $discovery->getBindings(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Api/Type/BindingType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Api\Type; 13 | 14 | use InvalidArgumentException; 15 | use Puli\Discovery\Api\Binding\Binding; 16 | use Serializable; 17 | use Webmozart\Assert\Assert; 18 | 19 | /** 20 | * A type that a binding can be bound to. 21 | * 22 | * @since 1.0 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | final class BindingType implements Serializable 27 | { 28 | /** 29 | * @var string 30 | */ 31 | private $name; 32 | 33 | /** 34 | * @var string 35 | */ 36 | private $acceptedBindingClass; 37 | 38 | /** 39 | * @var BindingParameter[] 40 | */ 41 | private $parameters = array(); 42 | 43 | /** 44 | * Creates a new type. 45 | * 46 | * @param string $name The name of the type. 47 | * @param string $bindingClass The class name of the accepted 48 | * bindings. 49 | * @param BindingParameter[] $parameters The parameters that can be set 50 | * for a binding. 51 | */ 52 | public function __construct($name, $bindingClass, array $parameters = array()) 53 | { 54 | Assert::stringNotEmpty($name, 'The type name must be a non-empty string. Got: %s'); 55 | Assert::allIsInstanceOf($parameters, 'Puli\Discovery\Api\Type\BindingParameter'); 56 | 57 | if (!class_exists($bindingClass) && !interface_exists($bindingClass)) { 58 | throw new InvalidArgumentException(sprintf( 59 | 'The binding class "%s" is neither a class nor an '. 60 | 'interface name. Is there a typo?', 61 | $bindingClass 62 | )); 63 | } 64 | 65 | $this->name = $name; 66 | $this->acceptedBindingClass = $bindingClass; 67 | 68 | foreach ($parameters as $parameter) { 69 | $this->parameters[$parameter->getName()] = $parameter; 70 | } 71 | 72 | // Sort to facilitate comparison 73 | ksort($this->parameters); 74 | } 75 | 76 | /** 77 | * Returns the type's name. 78 | * 79 | * @return string The name of the type. 80 | */ 81 | public function getName() 82 | { 83 | return $this->name; 84 | } 85 | 86 | /** 87 | * Returns the parameters. 88 | * 89 | * @return BindingParameter[] The type parameters. 90 | */ 91 | public function getParameters() 92 | { 93 | return $this->parameters; 94 | } 95 | 96 | /** 97 | * Returns whether the type has parameters. 98 | * 99 | * @return bool Returns `true` if the type has parameters and `false` 100 | * otherwise. 101 | */ 102 | public function hasParameters() 103 | { 104 | return count($this->parameters) > 0; 105 | } 106 | 107 | /** 108 | * Returns whether the type has any required parameters. 109 | * 110 | * @return bool Returns `true` if the type has at least one required 111 | * parameter. 112 | */ 113 | public function hasRequiredParameters() 114 | { 115 | foreach ($this->parameters as $parameter) { 116 | if ($parameter->isRequired()) { 117 | return true; 118 | } 119 | } 120 | 121 | return false; 122 | } 123 | 124 | /** 125 | * Returns whether the type has any optional parameters. 126 | * 127 | * @return bool Returns `true` if the type has at least one optional 128 | * parameter. 129 | */ 130 | public function hasOptionalParameters() 131 | { 132 | foreach ($this->parameters as $parameter) { 133 | if (!$parameter->isRequired()) { 134 | return true; 135 | } 136 | } 137 | 138 | return false; 139 | } 140 | 141 | /** 142 | * Returns a parameter by name. 143 | * 144 | * @param string $name The parameter name. 145 | * 146 | * @return BindingParameter The parameter. 147 | * 148 | * @throws NoSuchParameterException If the parameter was not found. 149 | */ 150 | public function getParameter($name) 151 | { 152 | if (!isset($this->parameters[$name])) { 153 | throw new NoSuchParameterException(sprintf( 154 | 'The parameter "%s" does not exist on type "%s".', 155 | $name, 156 | $this->name 157 | )); 158 | } 159 | 160 | return $this->parameters[$name]; 161 | } 162 | 163 | /** 164 | * Returns whether a parameter exists. 165 | * 166 | * @param string $name The parameter name. 167 | * 168 | * @return bool Returns `true` if a parameter with that name exists. 169 | */ 170 | public function hasParameter($name) 171 | { 172 | return isset($this->parameters[$name]); 173 | } 174 | 175 | /** 176 | * Returns the default values of the parameters. 177 | * 178 | * @return array The default values of the parameters. 179 | */ 180 | public function getParameterValues() 181 | { 182 | $values = array(); 183 | 184 | foreach ($this->parameters as $name => $parameter) { 185 | if (!$parameter->isRequired()) { 186 | $values[$name] = $parameter->getDefaultValue(); 187 | } 188 | } 189 | 190 | return $values; 191 | } 192 | 193 | /** 194 | * Returns whether the type has parameters with default values. 195 | * 196 | * @return bool Returns `true` if at least one parameter has a default value 197 | * and `false` otherwise. 198 | */ 199 | public function hasParameterValues() 200 | { 201 | foreach ($this->parameters as $name => $parameter) { 202 | if (!$parameter->isRequired()) { 203 | return true; 204 | } 205 | } 206 | 207 | return false; 208 | } 209 | 210 | /** 211 | * Returns the default value of a parameter. 212 | * 213 | * @param string $name The parameter name. 214 | * 215 | * @return mixed The default value. 216 | * 217 | * @throws NoSuchParameterException If the parameter was not found. 218 | */ 219 | public function getParameterValue($name) 220 | { 221 | return $this->getParameter($name)->getDefaultValue(); 222 | } 223 | 224 | /** 225 | * Returns whether a parameter has a default value set. 226 | * 227 | * @param string $name The parameter name. 228 | * 229 | * @return bool Returns `true` if the parameter has a default value set and 230 | * `false` otherwise. 231 | * 232 | * @throws NoSuchParameterException If the parameter was not found. 233 | */ 234 | public function hasParameterValue($name) 235 | { 236 | return !$this->getParameter($name)->isRequired(); 237 | } 238 | 239 | /** 240 | * Returns whether the type accepts a binding class. 241 | * 242 | * @param Binding|string $binding The binding or the fully-qualified name of 243 | * the binding class. 244 | * 245 | * @return bool Returns `true` if the binding can be bound to this type and 246 | * `false` otherwise. 247 | */ 248 | public function acceptsBinding($binding) 249 | { 250 | return $binding instanceof $this->acceptedBindingClass 251 | || $binding === $this->acceptedBindingClass 252 | || is_subclass_of($binding, $this->acceptedBindingClass); 253 | } 254 | 255 | /** 256 | * Returns the binding class name that can be bound to this type. 257 | * 258 | * @return string The accepted binding class name. 259 | */ 260 | public function getAcceptedBindingClass() 261 | { 262 | return $this->acceptedBindingClass; 263 | } 264 | 265 | /** 266 | * {@inheritdoc} 267 | */ 268 | public function serialize() 269 | { 270 | $data = array(); 271 | 272 | $this->preSerialize($data); 273 | 274 | return serialize($data); 275 | } 276 | 277 | /** 278 | * {@inheritdoc} 279 | */ 280 | public function unserialize($serialized) 281 | { 282 | $data = unserialize($serialized); 283 | 284 | $this->postUnserialize($data); 285 | } 286 | 287 | protected function preSerialize(array &$data) 288 | { 289 | $data[] = $this->name; 290 | $data[] = $this->acceptedBindingClass; 291 | 292 | foreach ($this->parameters as $parameter) { 293 | $data[] = $parameter->getName(); 294 | $data[] = $parameter->getFlags(); 295 | $data[] = $parameter->getDefaultValue(); 296 | } 297 | } 298 | 299 | protected function postUnserialize(array &$data) 300 | { 301 | while (count($data) > 2) { 302 | $defaultValue = array_pop($data); 303 | $flags = array_pop($data); 304 | $name = array_pop($data); 305 | 306 | $this->parameters[$name] = new BindingParameter($name, $flags, $defaultValue); 307 | } 308 | 309 | $this->acceptedBindingClass = array_pop($data); 310 | $this->name = array_pop($data); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/Test/AbstractDiscoveryTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test; 13 | 14 | use PHPUnit_Framework_MockObject_MockObject; 15 | use PHPUnit_Framework_TestCase; 16 | use Puli\Discovery\Api\Binding\Binding; 17 | use Puli\Discovery\Api\Binding\Initializer\BindingInitializer; 18 | use Puli\Discovery\Api\Discovery; 19 | use Puli\Discovery\Api\Type\BindingParameter; 20 | use Puli\Discovery\Api\Type\BindingType; 21 | use Puli\Discovery\Binding\ClassBinding; 22 | use Puli\Discovery\Test\Fixtures\Bar; 23 | use Puli\Discovery\Test\Fixtures\Foo; 24 | use Puli\Discovery\Test\Fixtures\StringBinding; 25 | use stdClass; 26 | use Webmozart\Expression\Expr; 27 | 28 | /** 29 | * @since 1.0 30 | * 31 | * @author Bernhard Schussek 32 | */ 33 | abstract class AbstractDiscoveryTest extends PHPUnit_Framework_TestCase 34 | { 35 | const STRING_BINDING = 'Puli\Discovery\Test\Fixtures\StringBinding'; 36 | 37 | const CLASS_BINDING = 'Puli\Discovery\Binding\ClassBinding'; 38 | 39 | /** 40 | * @var PHPUnit_Framework_MockObject_MockObject|BindingInitializer 41 | */ 42 | protected $initializer; 43 | 44 | /** 45 | * @param BindingType[] $types 46 | * @param Binding[] $bindings 47 | * 48 | * @return Discovery 49 | */ 50 | abstract protected function createLoadedDiscovery(array $types = array(), array $bindings = array(), array $initializers = array()); 51 | 52 | protected function setUp() 53 | { 54 | $this->initializer = $this->getMock('Puli\Discovery\Api\Binding\Initializer\BindingInitializer'); 55 | } 56 | 57 | public function testFindBindings() 58 | { 59 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING); 60 | $type2 = new BindingType(Bar::clazz, self::CLASS_BINDING); 61 | $binding1 = new StringBinding('string1', Foo::clazz); 62 | $binding2 = new StringBinding('string2', Foo::clazz); 63 | $binding3 = new ClassBinding(__CLASS__, Bar::clazz); 64 | 65 | $discovery = $this->createLoadedDiscovery(array($type1, $type2), array($binding1, $binding2, $binding3)); 66 | 67 | $this->assertEquals(array($binding1, $binding2), $discovery->findBindings(Foo::clazz)); 68 | $this->assertEquals(array($binding3), $discovery->findBindings(Bar::clazz)); 69 | } 70 | 71 | public function testFindBindingsWithExpression() 72 | { 73 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING, array( 74 | new BindingParameter('param1'), 75 | new BindingParameter('param2'), 76 | )); 77 | $type2 = new BindingType(Bar::clazz, self::CLASS_BINDING, array( 78 | new BindingParameter('param1'), 79 | new BindingParameter('param2'), 80 | )); 81 | $binding1 = new StringBinding('string1', Foo::clazz, array('param1' => 'value1', 'param2' => 'value2')); 82 | $binding2 = new StringBinding('string2', Foo::clazz, array('param1' => 'value1')); 83 | $binding3 = new ClassBinding(__CLASS__, Bar::clazz, array('param1' => 'value1', 'param2' => 'value2')); 84 | 85 | $discovery = $this->createLoadedDiscovery(array($type1, $type2), array($binding1, $binding2, $binding3)); 86 | 87 | $exprParam1 = Expr::method('getParameterValue', 'param1', Expr::same('value1')); 88 | $exprParam2 = Expr::method('getParameterValue', 'param1', Expr::same('value1')) 89 | ->andMethod('getParameterValue', 'param2', Expr::same('value2')); 90 | 91 | $this->assertEquals(array($binding1, $binding2), $discovery->findBindings(Foo::clazz, $exprParam1)); 92 | $this->assertEquals(array($binding1), $discovery->findBindings(Foo::clazz, $exprParam2)); 93 | $this->assertEquals(array($binding3), $discovery->findBindings(Bar::clazz, $exprParam2)); 94 | } 95 | 96 | public function testFindBindingsReturnsEmptyArrayIfUnknownType() 97 | { 98 | $discovery = $this->createLoadedDiscovery(); 99 | 100 | $this->assertEquals(array(), $discovery->findBindings(Foo::clazz)); 101 | } 102 | 103 | /** 104 | * @expectedException \InvalidArgumentException 105 | * @expectedExceptionMessage stdClass 106 | */ 107 | public function testFindBindingsFailsIfInvalidType() 108 | { 109 | $discovery = $this->createLoadedDiscovery(); 110 | $discovery->findBindings(new stdClass()); 111 | } 112 | 113 | public function testGetBindings() 114 | { 115 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING); 116 | $type2 = new BindingType(Bar::clazz, self::CLASS_BINDING); 117 | $binding1 = new StringBinding('string1', Foo::clazz); 118 | $binding2 = new StringBinding('string2', Foo::clazz); 119 | $binding3 = new ClassBinding(__CLASS__, Bar::clazz); 120 | 121 | $discovery = $this->createLoadedDiscovery(array($type1, $type2), array($binding1, $binding2, $binding3)); 122 | 123 | $this->assertEquals(array($binding1, $binding2, $binding3), $discovery->getBindings()); 124 | } 125 | 126 | public function testGetNoBindings() 127 | { 128 | $discovery = $this->createLoadedDiscovery(); 129 | 130 | $this->assertEquals(array(), $discovery->getBindings()); 131 | } 132 | 133 | public function testHasBindings() 134 | { 135 | $type = new BindingType(Foo::clazz, self::STRING_BINDING); 136 | $binding = new StringBinding('string1', Foo::clazz); 137 | 138 | $discovery = $this->createLoadedDiscovery(array($type), array($binding)); 139 | 140 | $this->assertTrue($discovery->hasBindings()); 141 | } 142 | 143 | public function testHasNoBindings() 144 | { 145 | $type = new BindingType(Foo::clazz, self::STRING_BINDING); 146 | 147 | $discovery = $this->createLoadedDiscovery(array($type)); 148 | 149 | $this->assertFalse($discovery->hasBindings()); 150 | } 151 | 152 | public function testHasBindingsWithType() 153 | { 154 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING); 155 | $type2 = new BindingType(Bar::clazz, self::STRING_BINDING); 156 | $binding = new StringBinding('string1', Foo::clazz); 157 | 158 | $discovery = $this->createLoadedDiscovery(array($type1, $type2), array($binding)); 159 | 160 | $this->assertTrue($discovery->hasBindings(Foo::clazz)); 161 | $this->assertFalse($discovery->hasBindings(Bar::clazz)); 162 | } 163 | 164 | public function testHasBindingsWithTypeReturnsFalseIfUnknownType() 165 | { 166 | $discovery = $this->createLoadedDiscovery(); 167 | $this->assertFalse($discovery->hasBindings(Foo::clazz)); 168 | } 169 | 170 | /** 171 | * @expectedException \InvalidArgumentException 172 | * @expectedExceptionMessage stdClass 173 | */ 174 | public function testHasBindingsWithTypeFailsIfInvalidType() 175 | { 176 | $discovery = $this->createLoadedDiscovery(); 177 | $discovery->hasBindings(new stdClass()); 178 | } 179 | 180 | public function testHasBindingsWithTypeAndExpression() 181 | { 182 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING, array( 183 | new BindingParameter('param'), 184 | )); 185 | $type2 = new BindingType(Bar::clazz, self::STRING_BINDING); 186 | $binding = new StringBinding('string1', Foo::clazz, array('param' => 'foo')); 187 | 188 | $discovery = $this->createLoadedDiscovery(array($type1, $type2), array($binding)); 189 | 190 | $this->assertTrue($discovery->hasBindings(Foo::clazz, Expr::method('getParameterValue', 'param', Expr::same('foo')))); 191 | $this->assertFalse($discovery->hasBindings(Foo::clazz, Expr::method('getParameterValue', 'param', Expr::same('bar')))); 192 | } 193 | 194 | public function testHasBindingsWithTypeAndExpressionReturnsFalseIfUnknownType() 195 | { 196 | $discovery = $this->createLoadedDiscovery(); 197 | $this->assertFalse($discovery->hasBindings(Foo::clazz, Expr::method('getParameterValue', 'param', Expr::same('foo')))); 198 | } 199 | 200 | /** 201 | * @expectedException \InvalidArgumentException 202 | * @expectedExceptionMessage stdClass 203 | */ 204 | public function testHasBindingsWithTypeAndParametersFailsIfInvalidType() 205 | { 206 | $discovery = $this->createLoadedDiscovery(); 207 | $discovery->hasBindings(new stdClass(), Expr::method('getParameterValue', 'param', Expr::same('foo'))); 208 | } 209 | 210 | public function testGetBindingType() 211 | { 212 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING); 213 | $type2 = new BindingType(Bar::clazz, self::STRING_BINDING); 214 | 215 | $discovery = $this->createLoadedDiscovery(array($type1, $type2)); 216 | 217 | $this->assertEquals($type1, $discovery->getBindingType(Foo::clazz)); 218 | $this->assertEquals($type2, $discovery->getBindingType(Bar::clazz)); 219 | } 220 | 221 | /** 222 | * @expectedException \Puli\Discovery\Api\Type\NoSuchTypeException 223 | * @expectedExceptionMessage Foo 224 | */ 225 | public function testGetBindingTypeFailsIfUnknownType() 226 | { 227 | $discovery = $this->createLoadedDiscovery(); 228 | 229 | $discovery->getBindingType(Foo::clazz); 230 | } 231 | 232 | /** 233 | * @expectedException \InvalidArgumentException 234 | * @expectedExceptionMessage stdClass 235 | */ 236 | public function testGetBindingTypeFailsIfInvalidType() 237 | { 238 | $discovery = $this->createLoadedDiscovery(); 239 | $discovery->getBindingType(new stdClass()); 240 | } 241 | 242 | public function testGetBindingTypes() 243 | { 244 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING); 245 | $type2 = new BindingType(Bar::clazz, self::STRING_BINDING); 246 | 247 | $discovery = $this->createLoadedDiscovery(array($type1, $type2)); 248 | 249 | $this->assertEquals(array($type1, $type2), $discovery->getBindingTypes()); 250 | } 251 | 252 | public function testHasBindingType() 253 | { 254 | $type = new BindingType(Foo::clazz, self::STRING_BINDING); 255 | 256 | $discovery = $this->createLoadedDiscovery(array($type)); 257 | 258 | $this->assertTrue($discovery->hasBindingType(Foo::clazz)); 259 | $this->assertFalse($discovery->hasBindingType(Bar::clazz)); 260 | } 261 | 262 | /** 263 | * @expectedException \InvalidArgumentException 264 | * @expectedExceptionMessage stdClass 265 | */ 266 | public function testHasBindingTypeFailsIfInvalidType() 267 | { 268 | $discovery = $this->createLoadedDiscovery(); 269 | $discovery->hasBindingType(new stdClass()); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/Test/AbstractBindingTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test; 13 | 14 | use PHPUnit_Framework_TestCase; 15 | use Puli\Discovery\Api\Type\BindingParameter; 16 | use Puli\Discovery\Api\Type\BindingType; 17 | use Puli\Discovery\Binding\AbstractBinding; 18 | use Puli\Discovery\Test\Fixtures\Bar; 19 | use Puli\Discovery\Test\Fixtures\Foo; 20 | use stdClass; 21 | 22 | /** 23 | * @since 1.0 24 | * 25 | * @author Bernhard Schussek 26 | */ 27 | abstract class AbstractBindingTest extends PHPUnit_Framework_TestCase 28 | { 29 | /** 30 | * @param string $typeName 31 | * @param array $parameterValues 32 | * 33 | * @return AbstractBinding 34 | */ 35 | abstract protected function createBinding($typeName, array $parameterValues = array()); 36 | 37 | public function testCreate() 38 | { 39 | $binding = $this->createBinding(Foo::clazz); 40 | 41 | $this->assertSame(Foo::clazz, $binding->getTypeName()); 42 | $this->assertSame(array(), $binding->getParameterValues()); 43 | $this->assertFalse($binding->hasParameterValue('param')); 44 | } 45 | 46 | /** 47 | * @expectedException \InvalidArgumentException 48 | * @expectedExceptionMessage stdClass 49 | */ 50 | public function testCreateFailsIfInvalidType() 51 | { 52 | $this->createBinding(new stdClass()); 53 | } 54 | 55 | public function testCreateWithParameters() 56 | { 57 | $binding = $this->createBinding(Foo::clazz, array( 58 | 'param1' => 'value', 59 | )); 60 | 61 | $this->assertSame(Foo::clazz, $binding->getTypeName()); 62 | $this->assertSame(array( 63 | 'param1' => 'value', 64 | ), $binding->getParameterValues()); 65 | $this->assertTrue($binding->hasParameterValue('param1')); 66 | $this->assertFalse($binding->hasParameterValue('foo')); 67 | $this->assertSame('value', $binding->getParameterValue('param1')); 68 | } 69 | 70 | public function testInitialize() 71 | { 72 | $binding = $this->createBinding(Foo::clazz); 73 | $type = new BindingType(Foo::clazz, get_class($binding)); 74 | 75 | $this->assertFalse($binding->isInitialized()); 76 | 77 | $binding->initialize($type); 78 | 79 | $this->assertSame($type, $binding->getType()); 80 | $this->assertTrue($binding->isInitialized()); 81 | } 82 | 83 | public function testInitializeWithParameters() 84 | { 85 | $binding = $this->createBinding(Foo::clazz, array( 86 | 'param1' => 'value', 87 | )); 88 | 89 | $type = new BindingType(Foo::clazz, get_class($binding), array( 90 | new BindingParameter('param1'), 91 | new BindingParameter('param2'), 92 | )); 93 | 94 | $this->assertSame(array('param1' => 'value'), $binding->getParameterValues()); 95 | $this->assertTrue($binding->hasParameterValue('param1')); 96 | $this->assertFalse($binding->hasParameterValue('param2')); 97 | $this->assertFalse($binding->hasParameterValue('foo')); 98 | $this->assertSame('value', $binding->getParameterValue('param1')); 99 | 100 | $binding->initialize($type); 101 | 102 | $this->assertSame(array('param1' => 'value', 'param2' => null), $binding->getParameterValues()); 103 | $this->assertTrue($binding->hasParameterValue('param1')); 104 | $this->assertTrue($binding->hasParameterValue('param2')); 105 | $this->assertFalse($binding->hasParameterValue('foo')); 106 | $this->assertSame('value', $binding->getParameterValue('param1')); 107 | $this->assertNull($binding->getParameterValue('param2')); 108 | 109 | // exclude default values 110 | $this->assertSame(array('param1' => 'value'), $binding->getParameterValues(false)); 111 | $this->assertTrue($binding->hasParameterValue('param1', false)); 112 | $this->assertFalse($binding->hasParameterValue('param2', false)); 113 | $this->assertFalse($binding->hasParameterValue('foo', false)); 114 | $this->assertSame('value', $binding->getParameterValue('param1', false)); 115 | } 116 | 117 | public function testInitializeWithParameterDefaults() 118 | { 119 | $binding = $this->createBinding(Foo::clazz, array( 120 | 'param2' => 'value', 121 | )); 122 | 123 | $type = new BindingType(Foo::clazz, get_class($binding), array( 124 | new BindingParameter('param1', BindingParameter::OPTIONAL, 'default'), 125 | new BindingParameter('param2'), 126 | )); 127 | 128 | $this->assertSame(array('param2' => 'value'), $binding->getParameterValues()); 129 | $this->assertFalse($binding->hasParameterValue('param1')); 130 | $this->assertTrue($binding->hasParameterValue('param2')); 131 | $this->assertSame('value', $binding->getParameterValue('param2')); 132 | 133 | $binding->initialize($type); 134 | 135 | $this->assertSame(array('param1' => 'default', 'param2' => 'value'), $binding->getParameterValues()); 136 | $this->assertTrue($binding->hasParameterValue('param1')); 137 | $this->assertTrue($binding->hasParameterValue('param2')); 138 | $this->assertSame('default', $binding->getParameterValue('param1')); 139 | $this->assertSame('value', $binding->getParameterValue('param2')); 140 | 141 | // exclude default values 142 | $this->assertSame(array('param2' => 'value'), $binding->getParameterValues(false)); 143 | $this->assertFalse($binding->hasParameterValue('param1', false)); 144 | $this->assertTrue($binding->hasParameterValue('param2', false)); 145 | $this->assertSame('value', $binding->getParameterValue('param2', false)); 146 | } 147 | 148 | public function testInitializeWithRequiredParameters() 149 | { 150 | $binding = $this->createBinding(Foo::clazz, array( 151 | 'param2' => 'value', 152 | )); 153 | 154 | $type = new BindingType(Foo::clazz, get_class($binding), array( 155 | new BindingParameter('param1', BindingParameter::OPTIONAL, 'default'), 156 | new BindingParameter('param2', BindingParameter::REQUIRED), 157 | )); 158 | 159 | $this->assertSame(array('param2' => 'value'), $binding->getParameterValues()); 160 | $this->assertFalse($binding->hasParameterValue('param1')); 161 | $this->assertTrue($binding->hasParameterValue('param2')); 162 | $this->assertSame('value', $binding->getParameterValue('param2')); 163 | 164 | $binding->initialize($type); 165 | 166 | $this->assertSame(array('param1' => 'default', 'param2' => 'value'), $binding->getParameterValues()); 167 | $this->assertTrue($binding->hasParameterValue('param1')); 168 | $this->assertTrue($binding->hasParameterValue('param2')); 169 | $this->assertSame('default', $binding->getParameterValue('param1')); 170 | $this->assertSame('value', $binding->getParameterValue('param2')); 171 | 172 | // exclude default values 173 | $this->assertSame(array('param2' => 'value'), $binding->getParameterValues(false)); 174 | $this->assertFalse($binding->hasParameterValue('param1', false)); 175 | $this->assertTrue($binding->hasParameterValue('param2', false)); 176 | $this->assertSame('value', $binding->getParameterValue('param2', false)); 177 | } 178 | 179 | /** 180 | * @expectedException \Puli\Discovery\Api\Type\MissingParameterException 181 | * @expectedExceptionMessage param 182 | */ 183 | public function testInitializeFailsIfMissingRequiredParameter() 184 | { 185 | $binding = $this->createBinding(Foo::clazz); 186 | 187 | $type = new BindingType(Foo::clazz, get_class($binding), array( 188 | new BindingParameter('param', BindingParameter::REQUIRED), 189 | )); 190 | 191 | $binding->initialize($type); 192 | } 193 | 194 | /** 195 | * @expectedException \Puli\Discovery\Api\Type\NoSuchParameterException 196 | * @expectedExceptionMessage foo 197 | */ 198 | public function testInitializeFailsIfUnknownParameter() 199 | { 200 | $binding = $this->createBinding(Foo::clazz, array( 201 | 'foo' => 'bar', 202 | )); 203 | 204 | $type = new BindingType(Foo::clazz, get_class($binding)); 205 | 206 | $binding->initialize($type); 207 | } 208 | 209 | /** 210 | * @expectedException \InvalidArgumentException 211 | * @expectedExceptionMessage Bar 212 | */ 213 | public function testInitializeFailsIfWrongType() 214 | { 215 | $binding = $this->createBinding(Bar::clazz); 216 | 217 | $type = new BindingType(Foo::clazz, get_class($binding)); 218 | 219 | $binding->initialize($type); 220 | } 221 | 222 | /** 223 | * @expectedException \Puli\Discovery\Api\Type\BindingNotAcceptedException 224 | */ 225 | public function testInitializeFailsIfBindingNotAccepted() 226 | { 227 | $binding = $this->createBinding(Foo::clazz); 228 | 229 | $type = new BindingType(Foo::clazz, __CLASS__); 230 | 231 | $binding->initialize($type); 232 | } 233 | 234 | /** 235 | * @expectedException \Puli\Discovery\Api\Type\NoSuchParameterException 236 | * @expectedExceptionMessage foo 237 | */ 238 | public function testGetParameterFailsIfNotFound() 239 | { 240 | $binding = $this->createBinding(Foo::clazz); 241 | 242 | $binding->getParameterValue('foo'); 243 | } 244 | 245 | /** 246 | * @expectedException \Puli\Discovery\Api\Binding\Initializer\NotInitializedException 247 | */ 248 | public function testGetTypeFailsIfNotInitialized() 249 | { 250 | $binding = $this->createBinding(Foo::clazz); 251 | 252 | $binding->getType(); 253 | } 254 | 255 | public function testSerialize() 256 | { 257 | $binding = $this->createBinding(Foo::clazz, array( 258 | 'param1' => 'value', 259 | )); 260 | 261 | $unserialized = unserialize(serialize($binding)); 262 | 263 | $this->assertEquals($binding, $unserialized); 264 | } 265 | 266 | public function testSerializeInitialized() 267 | { 268 | $binding = $this->createBinding(Foo::clazz, array( 269 | 'param1' => 'value', 270 | )); 271 | 272 | $type = new BindingType(Foo::clazz, get_class($binding), array( 273 | new BindingParameter('param1'), 274 | new BindingParameter('param2'), 275 | )); 276 | 277 | $binding->initialize($type); 278 | 279 | $unserialized = unserialize(serialize($binding)); 280 | $unserialized->initialize($type); 281 | 282 | $this->assertEquals($binding, $unserialized); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/KeyValueStoreDiscovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Binding\Initializer\BindingInitializer; 16 | use Puli\Discovery\Api\Type\BindingType; 17 | use Puli\Discovery\Api\Type\DuplicateTypeException; 18 | use Puli\Discovery\Api\Type\NoSuchTypeException; 19 | use Webmozart\Assert\Assert; 20 | use Webmozart\Expression\Expr; 21 | use Webmozart\Expression\Expression; 22 | use Webmozart\KeyValueStore\Api\KeyValueStore; 23 | 24 | /** 25 | * A discovery that stores the bindings in a key-value store. 26 | * 27 | * @since 1.0 28 | * 29 | * @author Bernhard Schussek 30 | */ 31 | class KeyValueStoreDiscovery extends AbstractEditableDiscovery 32 | { 33 | /** 34 | * @var KeyValueStore 35 | */ 36 | private $store; 37 | 38 | /** 39 | * Stores the integer key that will be used when adding the next binding type. 40 | * 41 | * Synchronized with the entry "::nextKey" in the store. 42 | * 43 | * @var int 44 | */ 45 | private $nextKey; 46 | 47 | /** 48 | * Stores an integer "key" for each binding type name. 49 | * 50 | * Contains each key only once. 51 | * 52 | * Synchronized with the entry "::keysByTypeName" in the store. 53 | * 54 | * @var int[] 55 | */ 56 | private $keysByTypeName; 57 | 58 | /** 59 | * Stores the binding type for each key. 60 | * 61 | * Synchronized with the entries "t:" in the store. 62 | * 63 | * @var BindingType[] 64 | */ 65 | private $typesByKey = array(); 66 | 67 | /** 68 | * Stores the bindings for each key. 69 | * 70 | * Synchronized with the entries "b:" in the store. 71 | * 72 | * @var Binding[][] 73 | */ 74 | private $bindingsByKey = array(); 75 | 76 | /** 77 | * Creates a new discovery. 78 | * 79 | * @param KeyValueStore $store The key-value store used to 80 | * store the bindings and the 81 | * binding types. 82 | * @param BindingInitializer[] $initializers The binding initializers to 83 | * apply to newly created or 84 | * unserialized bindings. 85 | */ 86 | public function __construct(KeyValueStore $store, array $initializers = array()) 87 | { 88 | parent::__construct($initializers); 89 | 90 | $this->store = $store; 91 | $this->keysByTypeName = $store->get('::keysByTypeName', array()); 92 | $this->nextKey = $store->get('::nextKey', 0); 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function addBindingType(BindingType $type) 99 | { 100 | if (isset($this->keysByTypeName[$type->getName()])) { 101 | throw DuplicateTypeException::forTypeName($type->getName()); 102 | } 103 | 104 | $key = $this->nextKey++; 105 | 106 | $this->keysByTypeName[$type->getName()] = $key; 107 | 108 | // Use integer keys to reduce storage space 109 | // (compared to fully-qualified class names) 110 | $this->typesByKey[$key] = $type; 111 | 112 | $this->store->set('::keysByTypeName', $this->keysByTypeName); 113 | $this->store->set('::nextKey', $this->nextKey); 114 | $this->store->set('t:'.$key, $type); 115 | } 116 | 117 | /** 118 | * {@inheritdoc} 119 | */ 120 | public function removeBindingType($typeName) 121 | { 122 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 123 | 124 | if (!isset($this->keysByTypeName[$typeName])) { 125 | return; 126 | } 127 | 128 | $key = $this->keysByTypeName[$typeName]; 129 | 130 | unset( 131 | $this->keysByTypeName[$typeName], 132 | $this->typesByKey[$key], 133 | $this->bindingsByKey[$key] 134 | ); 135 | 136 | $this->store->remove('t:'.$key); 137 | $this->store->remove('b:'.$key); 138 | $this->store->set('::keysByTypeName', $this->keysByTypeName); 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function removeBindingTypes() 145 | { 146 | $this->keysByTypeName = array(); 147 | $this->typesByKey = array(); 148 | $this->bindingsByKey = array(); 149 | $this->nextKey = 0; 150 | 151 | $this->store->clear(); 152 | } 153 | 154 | /** 155 | * {@inheritdoc} 156 | */ 157 | public function hasBindingType($typeName) 158 | { 159 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 160 | 161 | return isset($this->keysByTypeName[$typeName]); 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | public function getBindingType($typeName) 168 | { 169 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 170 | 171 | if (!isset($this->keysByTypeName[$typeName])) { 172 | throw NoSuchTypeException::forTypeName($typeName); 173 | } 174 | 175 | $key = $this->keysByTypeName[$typeName]; 176 | 177 | if (!isset($this->typesByKey[$key])) { 178 | $this->typesByKey[$key] = $this->store->get('t:'.$key); 179 | } 180 | 181 | return $this->typesByKey[$key]; 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function hasBindingTypes() 188 | { 189 | return count($this->keysByTypeName) > 0; 190 | } 191 | 192 | /** 193 | * {@inheritdoc} 194 | */ 195 | public function getBindingTypes() 196 | { 197 | $keysToFetch = array(); 198 | 199 | foreach ($this->keysByTypeName as $key) { 200 | if (!isset($this->typesByKey[$key])) { 201 | $keysToFetch[] = 't:'.$key; 202 | } 203 | } 204 | 205 | $types = $this->store->getMultiple($keysToFetch); 206 | 207 | foreach ($types as $prefixedKey => $type) { 208 | $this->typesByKey[substr($prefixedKey, 2)] = $type; 209 | } 210 | 211 | ksort($this->typesByKey); 212 | 213 | return $this->typesByKey; 214 | } 215 | 216 | /** 217 | * {@inheritdoc} 218 | */ 219 | public function addBinding(Binding $binding) 220 | { 221 | $typeName = $binding->getTypeName(); 222 | 223 | if (!isset($this->keysByTypeName[$typeName])) { 224 | throw NoSuchTypeException::forTypeName($typeName); 225 | } 226 | 227 | $key = $this->keysByTypeName[$typeName]; 228 | 229 | if (!isset($this->bindingsByKey[$key])) { 230 | $this->loadBindingsForKey($key); 231 | } 232 | 233 | $this->initializeBinding($binding); 234 | 235 | $this->bindingsByKey[$key][] = $binding; 236 | 237 | $this->store->set('b:'.$key, $this->bindingsByKey[$key]); 238 | } 239 | 240 | /** 241 | * {@inheritdoc} 242 | */ 243 | public function findBindings($typeName, Expression $expr = null) 244 | { 245 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 246 | 247 | if (!isset($this->keysByTypeName[$typeName])) { 248 | return array(); 249 | } 250 | 251 | $key = $this->keysByTypeName[$typeName]; 252 | 253 | if (!isset($this->bindingsByKey[$key])) { 254 | $this->loadBindingsForKey($key); 255 | } 256 | 257 | $bindings = $this->bindingsByKey[$key]; 258 | 259 | if (null !== $expr) { 260 | $bindings = Expr::filter($bindings, $expr); 261 | } 262 | 263 | return $bindings; 264 | } 265 | 266 | /** 267 | * {@inheritdoc} 268 | */ 269 | public function getBindings() 270 | { 271 | $this->loadAllBindings(); 272 | 273 | $bindings = array(); 274 | 275 | foreach ($this->bindingsByKey as $bindingsForKey) { 276 | $bindings = array_merge($bindings, $bindingsForKey); 277 | } 278 | 279 | return $bindings; 280 | } 281 | 282 | /** 283 | * {@inheritdoc} 284 | */ 285 | protected function removeAllBindings() 286 | { 287 | $this->bindingsByKey = array(); 288 | 289 | // Iterate $keysByTypeName which does not contain duplicate keys 290 | foreach ($this->keysByTypeName as $key) { 291 | $this->store->remove('b:'.$key); 292 | } 293 | } 294 | 295 | /** 296 | * {@inheritdoc} 297 | */ 298 | protected function removeBindingsThatMatch(Expression $expr) 299 | { 300 | $this->loadAllBindings(); 301 | 302 | foreach ($this->bindingsByKey as $key => $bindingsForKey) { 303 | foreach ($bindingsForKey as $i => $binding) { 304 | if ($expr->evaluate($binding)) { 305 | unset($this->bindingsByKey[$key][$i]); 306 | } 307 | } 308 | 309 | // Reindex array 310 | $this->reindexBindingsForKey($key); 311 | 312 | $this->syncBindingsForKey($key); 313 | } 314 | } 315 | 316 | /** 317 | * {@inheritdoc} 318 | */ 319 | protected function removeBindingsWithTypeName($typeName) 320 | { 321 | if (!isset($this->keysByTypeName[$typeName])) { 322 | return; 323 | } 324 | 325 | $key = $this->keysByTypeName[$typeName]; 326 | 327 | if (!isset($this->bindingsByKey[$key])) { 328 | // no initialize, since we're removing this anyway 329 | $this->loadBindingsForKey($key, false); 330 | } 331 | 332 | unset($this->bindingsByKey[$key]); 333 | 334 | $this->store->remove('b:'.$key); 335 | } 336 | 337 | /** 338 | * {@inheritdoc} 339 | */ 340 | protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr) 341 | { 342 | if (!isset($this->keysByTypeName[$typeName])) { 343 | return; 344 | } 345 | 346 | $key = $this->keysByTypeName[$typeName]; 347 | 348 | if (!isset($this->bindingsByKey[$key])) { 349 | $this->loadBindingsForKey($key); 350 | } 351 | 352 | foreach ($this->bindingsByKey[$key] as $i => $binding) { 353 | if ($expr->evaluate($binding)) { 354 | unset($this->bindingsByKey[$key][$i]); 355 | } 356 | } 357 | 358 | $this->reindexBindingsForKey($key); 359 | $this->syncBindingsForKey($key); 360 | } 361 | 362 | /** 363 | * {@inheritdoc} 364 | */ 365 | protected function hasAnyBinding() 366 | { 367 | // First check loaded keys 368 | if (count($this->bindingsByKey) > 0) { 369 | return true; 370 | } 371 | 372 | // Next check unloaded keys 373 | foreach ($this->keysByTypeName as $key) { 374 | if ($this->store->exists('b:'.$key)) { 375 | return true; 376 | } 377 | } 378 | 379 | return false; 380 | } 381 | 382 | /** 383 | * {@inheritdoc} 384 | */ 385 | protected function hasBindingsThatMatch(Expression $expr) 386 | { 387 | $this->loadAllBindings(); 388 | 389 | foreach ($this->bindingsByKey as $bindingsForKey) { 390 | foreach ($bindingsForKey as $binding) { 391 | if ($expr->evaluate($binding)) { 392 | return true; 393 | } 394 | } 395 | } 396 | 397 | return false; 398 | } 399 | 400 | /** 401 | * {@inheritdoc} 402 | */ 403 | protected function hasBindingsWithTypeName($typeName) 404 | { 405 | if (!isset($this->keysByTypeName[$typeName])) { 406 | return false; 407 | } 408 | 409 | $key = $this->keysByTypeName[$typeName]; 410 | 411 | return isset($this->bindingsByKey[$key]) || $this->store->exists('b:'.$key); 412 | } 413 | 414 | protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr) 415 | { 416 | if (!$this->hasBindingsWithTypeName($typeName)) { 417 | return false; 418 | } 419 | 420 | $key = $this->keysByTypeName[$typeName]; 421 | 422 | if (!isset($this->bindingsByKey[$key])) { 423 | $this->loadBindingsForKey($key); 424 | } 425 | 426 | foreach ($this->bindingsByKey[$key] as $binding) { 427 | if ($expr->evaluate($binding)) { 428 | return true; 429 | } 430 | } 431 | 432 | return false; 433 | } 434 | 435 | private function loadAllBindings() 436 | { 437 | $keysToFetch = array(); 438 | 439 | foreach ($this->keysByTypeName as $key) { 440 | if (!isset($this->bindingsByKey[$key])) { 441 | $keysToFetch[] = 'b:'.$key; 442 | } 443 | } 444 | 445 | $fetchedBindings = $this->store->getMultiple($keysToFetch); 446 | 447 | foreach ($fetchedBindings as $key => $bindingsForKey) { 448 | $this->bindingsByKey[$key] = $bindingsForKey ?: array(); 449 | $this->initializeBindings($this->bindingsByKey[$key]); 450 | } 451 | } 452 | 453 | private function loadBindingsForKey($key, $initialize = true) 454 | { 455 | $this->bindingsByKey[$key] = $this->store->get('b:'.$key, array()); 456 | 457 | if ($initialize) { 458 | $this->initializeBindings($this->bindingsByKey[$key]); 459 | } 460 | } 461 | 462 | private function reindexBindingsForKey($key) 463 | { 464 | $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]); 465 | } 466 | 467 | private function syncBindingsForKey($key) 468 | { 469 | if (count($this->bindingsByKey[$key]) > 0) { 470 | $this->store->set('b:'.$key, $this->bindingsByKey[$key]); 471 | } else { 472 | unset($this->bindingsByKey[$key]); 473 | $this->store->remove('b:'.$key); 474 | } 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /src/JsonDiscovery.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Binding\Initializer\BindingInitializer; 16 | use Puli\Discovery\Api\Type\BindingType; 17 | use Puli\Discovery\Api\Type\DuplicateTypeException; 18 | use Puli\Discovery\Api\Type\NoSuchTypeException; 19 | use Webmozart\Assert\Assert; 20 | use Webmozart\Expression\Expr; 21 | use Webmozart\Expression\Expression; 22 | use Webmozart\Json\JsonDecoder; 23 | use Webmozart\Json\JsonEncoder; 24 | 25 | /** 26 | * A discovery backed by a JSON file. 27 | * 28 | * @since 1.0 29 | * 30 | * @author Bernhard Schussek 31 | */ 32 | class JsonDiscovery extends AbstractEditableDiscovery 33 | { 34 | /** 35 | * @var string 36 | */ 37 | private $path; 38 | 39 | /** 40 | * @var array 41 | */ 42 | private $json; 43 | 44 | /** 45 | * @var JsonEncoder 46 | */ 47 | private $encoder; 48 | 49 | /** 50 | * Stores the binding type for each key. 51 | * 52 | * Synchronized with the entries "t:" in the store. 53 | * 54 | * @var BindingType[] 55 | */ 56 | private $typesByKey = array(); 57 | 58 | /** 59 | * Stores the bindings for each key. 60 | * 61 | * Synchronized with the entries "b:" in the store. 62 | * 63 | * @var Binding[][] 64 | */ 65 | private $bindingsByKey = array(); 66 | 67 | /** 68 | * Creates a new discovery. 69 | * 70 | * @param string $path The path to the JSON file. 71 | * @param BindingInitializer[] $initializers The binding initializers to 72 | * apply to newly created or 73 | * unserialized bindings. 74 | */ 75 | public function __construct($path, array $initializers = array()) 76 | { 77 | Assert::stringNotEmpty($path, 'The path to the JSON file must be a non-empty string. Got: %s'); 78 | 79 | parent::__construct($initializers); 80 | 81 | $this->path = $path; 82 | $this->encoder = new JsonEncoder(); 83 | $this->encoder->setPrettyPrinting(true); 84 | $this->encoder->setEscapeSlash(false); 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function addBindingType(BindingType $type) 91 | { 92 | if (null === $this->json) { 93 | $this->load(); 94 | } 95 | 96 | if (isset($this->json['keysByTypeName'][$type->getName()])) { 97 | throw DuplicateTypeException::forTypeName($type->getName()); 98 | } 99 | 100 | $key = $this->json['nextKey']++; 101 | 102 | $this->json['keysByTypeName'][$type->getName()] = $key; 103 | 104 | $this->typesByKey[$key] = $type; 105 | 106 | // Use integer keys to reduce storage space 107 | // (compared to fully-qualified class names) 108 | $this->json['typesByKey'][$key] = serialize($type); 109 | 110 | $this->flush(); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function removeBindingType($typeName) 117 | { 118 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 119 | 120 | if (null === $this->json) { 121 | $this->load(); 122 | } 123 | 124 | if (!isset($this->json['keysByTypeName'][$typeName])) { 125 | return; 126 | } 127 | 128 | $key = $this->json['keysByTypeName'][$typeName]; 129 | 130 | if (!isset($this->bindingsByKey[$key])) { 131 | // no initialize, since we're removing this anyway 132 | $this->loadBindingsForKey($key, false); 133 | } 134 | 135 | unset($this->typesByKey[$key]); 136 | unset($this->bindingsByKey[$key]); 137 | 138 | unset($this->json['keysByTypeName'][$typeName]); 139 | unset($this->json['typesByKey'][$key]); 140 | unset($this->json['bindingsByKey'][$key]); 141 | 142 | $this->flush(); 143 | } 144 | 145 | /** 146 | * {@inheritdoc} 147 | */ 148 | public function removeBindingTypes() 149 | { 150 | if (null === $this->json) { 151 | $this->load(); 152 | } 153 | 154 | $this->typesByKey = array(); 155 | $this->bindingsByKey = array(); 156 | 157 | $this->json['keysByTypeName'] = array(); 158 | $this->json['typesByKey'] = array(); 159 | $this->json['bindingsByKey'] = array(); 160 | $this->json['nextKey'] = 0; 161 | 162 | $this->flush(); 163 | } 164 | 165 | /** 166 | * {@inheritdoc} 167 | */ 168 | public function hasBindingType($typeName) 169 | { 170 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 171 | 172 | if (null === $this->json) { 173 | $this->load(); 174 | } 175 | 176 | return isset($this->json['keysByTypeName'][$typeName]); 177 | } 178 | 179 | /** 180 | * {@inheritdoc} 181 | */ 182 | public function getBindingType($typeName) 183 | { 184 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 185 | 186 | if (null === $this->json) { 187 | $this->load(); 188 | } 189 | 190 | if (!isset($this->json['keysByTypeName'][$typeName])) { 191 | throw NoSuchTypeException::forTypeName($typeName); 192 | } 193 | 194 | $key = $this->json['keysByTypeName'][$typeName]; 195 | 196 | if (!isset($this->typesByKey[$key])) { 197 | $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]); 198 | } 199 | 200 | return $this->typesByKey[$key]; 201 | } 202 | 203 | /** 204 | * {@inheritdoc} 205 | */ 206 | public function hasBindingTypes() 207 | { 208 | if (null === $this->json) { 209 | $this->load(); 210 | } 211 | 212 | return count($this->json['keysByTypeName']) > 0; 213 | } 214 | 215 | /** 216 | * {@inheritdoc} 217 | */ 218 | public function getBindingTypes() 219 | { 220 | if (null === $this->json) { 221 | $this->load(); 222 | } 223 | 224 | foreach ($this->json['keysByTypeName'] as $key) { 225 | if (!isset($this->typesByKey[$key])) { 226 | $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]); 227 | } 228 | } 229 | 230 | ksort($this->typesByKey); 231 | 232 | return $this->typesByKey; 233 | } 234 | 235 | /** 236 | * {@inheritdoc} 237 | */ 238 | public function addBinding(Binding $binding) 239 | { 240 | if (null === $this->json) { 241 | $this->load(); 242 | } 243 | 244 | $typeName = $binding->getTypeName(); 245 | 246 | if (!isset($this->json['keysByTypeName'][$typeName])) { 247 | throw NoSuchTypeException::forTypeName($typeName); 248 | } 249 | 250 | $key = $this->json['keysByTypeName'][$typeName]; 251 | 252 | if (!isset($this->bindingsByKey[$key])) { 253 | $this->loadBindingsForKey($key); 254 | } 255 | 256 | $this->initializeBinding($binding); 257 | 258 | $this->bindingsByKey[$key][] = $binding; 259 | 260 | $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]); 261 | 262 | $this->flush(); 263 | } 264 | 265 | /** 266 | * {@inheritdoc} 267 | */ 268 | public function findBindings($typeName, Expression $expr = null) 269 | { 270 | Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s'); 271 | 272 | if (null === $this->json) { 273 | $this->load(); 274 | } 275 | 276 | if (!isset($this->json['keysByTypeName'][$typeName])) { 277 | return array(); 278 | } 279 | 280 | $key = $this->json['keysByTypeName'][$typeName]; 281 | 282 | if (!isset($this->bindingsByKey[$key])) { 283 | $this->loadBindingsForKey($key); 284 | } 285 | 286 | $bindings = $this->bindingsByKey[$key]; 287 | 288 | if (null !== $expr) { 289 | $bindings = Expr::filter($bindings, $expr); 290 | } 291 | 292 | return $bindings; 293 | } 294 | 295 | /** 296 | * {@inheritdoc} 297 | */ 298 | public function getBindings() 299 | { 300 | if (null === $this->json) { 301 | $this->load(); 302 | } 303 | 304 | $this->loadAllBindings(); 305 | 306 | $bindings = array(); 307 | 308 | foreach ($this->bindingsByKey as $bindingsForKey) { 309 | $bindings = array_merge($bindings, $bindingsForKey); 310 | } 311 | 312 | return $bindings; 313 | } 314 | 315 | /** 316 | * {@inheritdoc} 317 | */ 318 | protected function removeAllBindings() 319 | { 320 | if (null === $this->json) { 321 | $this->load(); 322 | } 323 | 324 | $this->bindingsByKey = array(); 325 | 326 | $this->json['bindingsByKey'] = array(); 327 | 328 | $this->flush(); 329 | } 330 | 331 | /** 332 | * {@inheritdoc} 333 | */ 334 | protected function removeBindingsThatMatch(Expression $expr) 335 | { 336 | if (null === $this->json) { 337 | $this->load(); 338 | } 339 | 340 | $this->loadAllBindings(); 341 | 342 | foreach ($this->bindingsByKey as $key => $bindingsForKey) { 343 | foreach ($bindingsForKey as $i => $binding) { 344 | if ($expr->evaluate($binding)) { 345 | unset($this->bindingsByKey[$key][$i]); 346 | } 347 | } 348 | 349 | $this->reindexBindingsForKey($key); 350 | $this->syncBindingsForKey($key); 351 | } 352 | 353 | $this->flush(); 354 | } 355 | 356 | /** 357 | * {@inheritdoc} 358 | */ 359 | protected function removeBindingsWithTypeName($typeName) 360 | { 361 | if (null === $this->json) { 362 | $this->load(); 363 | } 364 | 365 | if (!isset($this->json['keysByTypeName'][$typeName])) { 366 | return; 367 | } 368 | 369 | $key = $this->json['keysByTypeName'][$typeName]; 370 | 371 | unset( 372 | $this->bindingsByKey[$key], 373 | $this->json['bindingsByKey'][$key] 374 | ); 375 | 376 | $this->flush(); 377 | } 378 | 379 | /** 380 | * {@inheritdoc} 381 | */ 382 | protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr) 383 | { 384 | if (null === $this->json) { 385 | $this->load(); 386 | } 387 | 388 | if (!isset($this->json['keysByTypeName'][$typeName])) { 389 | return; 390 | } 391 | 392 | $key = $this->json['keysByTypeName'][$typeName]; 393 | 394 | if (!isset($this->bindingsByKey[$key])) { 395 | $this->loadBindingsForKey($key); 396 | } 397 | 398 | foreach ($this->bindingsByKey[$key] as $i => $binding) { 399 | if ($expr->evaluate($binding)) { 400 | unset($this->bindingsByKey[$key][$i]); 401 | } 402 | } 403 | 404 | $this->reindexBindingsForKey($key); 405 | $this->syncBindingsForKey($key); 406 | 407 | $this->flush(); 408 | } 409 | 410 | /** 411 | * {@inheritdoc} 412 | */ 413 | protected function hasAnyBinding() 414 | { 415 | if (null === $this->json) { 416 | $this->load(); 417 | } 418 | 419 | return count($this->json['bindingsByKey']) > 0; 420 | } 421 | 422 | /** 423 | * {@inheritdoc} 424 | */ 425 | protected function hasBindingsThatMatch(Expression $expr) 426 | { 427 | if (null === $this->json) { 428 | $this->load(); 429 | } 430 | 431 | $this->loadAllBindings(); 432 | 433 | foreach ($this->bindingsByKey as $bindingsForKey) { 434 | foreach ($bindingsForKey as $binding) { 435 | if ($expr->evaluate($binding)) { 436 | return true; 437 | } 438 | } 439 | } 440 | 441 | return false; 442 | } 443 | 444 | /** 445 | * {@inheritdoc} 446 | */ 447 | protected function hasBindingsWithTypeName($typeName) 448 | { 449 | if (null === $this->json) { 450 | $this->load(); 451 | } 452 | 453 | if (!isset($this->json['keysByTypeName'][$typeName])) { 454 | return false; 455 | } 456 | 457 | $key = $this->json['keysByTypeName'][$typeName]; 458 | 459 | return isset($this->json['bindingsByKey'][$key]); 460 | } 461 | 462 | protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr) 463 | { 464 | if (null === $this->json) { 465 | $this->load(); 466 | } 467 | 468 | if (!$this->hasBindingsWithTypeName($typeName)) { 469 | return false; 470 | } 471 | 472 | $key = $this->json['keysByTypeName'][$typeName]; 473 | 474 | if (!isset($this->bindingsByKey[$key])) { 475 | $this->loadBindingsForKey($key); 476 | } 477 | 478 | foreach ($this->bindingsByKey[$key] as $binding) { 479 | if ($expr->evaluate($binding)) { 480 | return true; 481 | } 482 | } 483 | 484 | return false; 485 | } 486 | 487 | private function loadAllBindings() 488 | { 489 | foreach ($this->json['keysByTypeName'] as $key) { 490 | if (!isset($this->bindingsByKey[$key])) { 491 | $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key]) 492 | ? unserialize($this->json['bindingsByKey'][$key]) 493 | : array(); 494 | $this->initializeBindings($this->bindingsByKey[$key]); 495 | } 496 | } 497 | } 498 | 499 | private function loadBindingsForKey($key, $initialize = true) 500 | { 501 | $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key]) 502 | ? unserialize($this->json['bindingsByKey'][$key]) 503 | : array(); 504 | 505 | if ($initialize) { 506 | $this->initializeBindings($this->bindingsByKey[$key]); 507 | } 508 | } 509 | 510 | private function reindexBindingsForKey($key) 511 | { 512 | $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]); 513 | 514 | $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]); 515 | } 516 | 517 | private function syncBindingsForKey($key) 518 | { 519 | if (count($this->bindingsByKey[$key]) > 0) { 520 | $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]); 521 | } else { 522 | unset($this->bindingsByKey[$key], $this->json['bindingsByKey'][$key]); 523 | } 524 | } 525 | 526 | /** 527 | * Loads the JSON file. 528 | */ 529 | private function load() 530 | { 531 | $decoder = new JsonDecoder(); 532 | $decoder->setObjectDecoding(JsonDecoder::ASSOC_ARRAY); 533 | 534 | $this->json = file_exists($this->path) 535 | ? $decoder->decodeFile($this->path) 536 | : array(); 537 | 538 | if (!isset($this->json['keysByTypeName'])) { 539 | $this->json['keysByTypeName'] = array(); 540 | $this->json['typesByKey'] = array(); 541 | $this->json['bindingsByKey'] = array(); 542 | $this->json['nextKey'] = 0; 543 | } 544 | } 545 | 546 | /** 547 | * Writes the JSON file. 548 | */ 549 | private function flush() 550 | { 551 | $this->encoder->encodeFile($this->json, $this->path); 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/Test/AbstractEditableDiscoveryTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Puli\Discovery\Test; 13 | 14 | use Puli\Discovery\Api\Binding\Binding; 15 | use Puli\Discovery\Api\Binding\Initializer\BindingInitializer; 16 | use Puli\Discovery\Api\Discovery; 17 | use Puli\Discovery\Api\EditableDiscovery; 18 | use Puli\Discovery\Api\Type\BindingParameter; 19 | use Puli\Discovery\Api\Type\BindingType; 20 | use Puli\Discovery\Test\Fixtures\Bar; 21 | use Puli\Discovery\Test\Fixtures\Foo; 22 | use Puli\Discovery\Test\Fixtures\StringBinding; 23 | use stdClass; 24 | use Webmozart\Expression\Expr; 25 | 26 | /** 27 | * @since 1.0 28 | * 29 | * @author Bernhard Schussek 30 | */ 31 | abstract class AbstractEditableDiscoveryTest extends AbstractDiscoveryTest 32 | { 33 | /** 34 | * Creates a discovery that can be written in the test. 35 | * 36 | * @param BindingInitializer[] $initializers 37 | * 38 | * @return EditableDiscovery 39 | */ 40 | abstract protected function createDiscovery(array $initializers = array()); 41 | 42 | /** 43 | * Creates a discovery that can be read in the test. 44 | * 45 | * This method is needed to test whether the discovery actually synchronized 46 | * all in-memory changes to the backing data store: 47 | * 48 | * * If the method returns the passed $discovery, the in-memory data 49 | * structures are tested. 50 | * * If the method returns a new discovery with the same backing data store, 51 | * that data store is tested. 52 | * 53 | * @param EditableDiscovery $discovery 54 | * @param BindingInitializer[] $initializers 55 | * 56 | * @return EditableDiscovery 57 | */ 58 | abstract protected function loadDiscoveryFromStorage(EditableDiscovery $discovery, array $initializers = array()); 59 | 60 | /** 61 | * @param BindingType[] $types 62 | * @param Binding[] $bindings 63 | * @param BindingInitializer[] $initializers 64 | * 65 | * @return Discovery 66 | */ 67 | protected function createLoadedDiscovery(array $types = array(), array $bindings = array(), array $initializers = array()) 68 | { 69 | $discovery = $this->createDiscovery($initializers); 70 | 71 | foreach ($types as $type) { 72 | $discovery->addBindingType($type); 73 | } 74 | 75 | foreach ($bindings as $binding) { 76 | $discovery->addBinding($binding); 77 | } 78 | 79 | return $this->loadDiscoveryFromStorage($discovery); 80 | } 81 | 82 | public function testAddBinding() 83 | { 84 | $binding = new StringBinding('string', Foo::clazz); 85 | 86 | $discovery = $this->createDiscovery(); 87 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 88 | $discovery->addBinding($binding); 89 | 90 | $discovery = $this->loadDiscoveryFromStorage($discovery); 91 | 92 | $this->assertCount(1, $discovery->findBindings(Foo::clazz)); 93 | $this->assertCount(1, $discovery->getBindings()); 94 | } 95 | 96 | /** 97 | * @expectedException \Puli\Discovery\Api\Type\NoSuchTypeException 98 | * @expectedExceptionMessage Foo 99 | */ 100 | public function testAddBindingFailsIfTypeNotFound() 101 | { 102 | $discovery = $this->createDiscovery(); 103 | $discovery->addBinding(new StringBinding('string', Foo::clazz)); 104 | } 105 | 106 | /** 107 | * @expectedException \Puli\Discovery\Api\Type\BindingNotAcceptedException 108 | * @expectedExceptionMessage Foo 109 | */ 110 | public function testAddBindingFailsIfTypeDoesNotAcceptBinding() 111 | { 112 | $discovery = $this->createDiscovery(); 113 | $discovery->addBindingType(new BindingType(Foo::clazz, self::CLASS_BINDING)); 114 | $discovery->addBinding(new StringBinding('string', Foo::clazz)); 115 | } 116 | 117 | public function testAddBindingAcceptsDuplicates() 118 | { 119 | // The idea behind accepting duplicates is that depending on the use 120 | // case, multiple modules may define the same binding but the order 121 | // in which the modules is loaded is important. Hence if we load 122 | // [m1, m2, m3] and m1 and m3 contain the same binding, we cannot simply 123 | // discard one of the bindings, since the order might be important for 124 | // the end user. 125 | 126 | $binding = new StringBinding('string', Foo::clazz); 127 | 128 | $discovery = $this->createDiscovery(); 129 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 130 | $discovery->addBinding($binding); 131 | $discovery->addBinding($binding); 132 | 133 | $discovery = $this->loadDiscoveryFromStorage($discovery); 134 | 135 | $this->assertCount(2, $discovery->findBindings(Foo::clazz)); 136 | $this->assertCount(2, $discovery->getBindings()); 137 | } 138 | 139 | public function testRemoveBindings() 140 | { 141 | $binding1 = new StringBinding('string1', Foo::clazz); 142 | $binding2 = new StringBinding('string2', Foo::clazz); 143 | 144 | $discovery = $this->createDiscovery(); 145 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 146 | $discovery->addBinding($binding1); 147 | $discovery->addBinding($binding2); 148 | $discovery->removeBindings(); 149 | 150 | $discovery = $this->loadDiscoveryFromStorage($discovery); 151 | 152 | $this->assertCount(0, $discovery->findBindings(Foo::clazz)); 153 | $this->assertCount(0, $discovery->getBindings()); 154 | } 155 | 156 | public function testRemoveBindingsDoesNothingIfNoneFound() 157 | { 158 | $discovery = $this->createDiscovery(); 159 | $discovery->removeBindings(); 160 | 161 | $discovery = $this->loadDiscoveryFromStorage($discovery); 162 | 163 | $this->assertCount(0, $discovery->getBindings()); 164 | } 165 | 166 | public function testRemoveBindingsWithType() 167 | { 168 | $binding1 = new StringBinding('string1', Foo::clazz); 169 | $binding2 = new StringBinding('string2', Foo::clazz); 170 | $binding3 = new StringBinding('string3', Bar::clazz); 171 | 172 | $discovery = $this->createDiscovery(); 173 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 174 | $discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); 175 | $discovery->addBinding($binding1); 176 | $discovery->addBinding($binding2); 177 | $discovery->addBinding($binding3); 178 | $discovery->removeBindings(Foo::clazz); 179 | 180 | $discovery = $this->loadDiscoveryFromStorage($discovery); 181 | 182 | $this->assertEquals(array(), $discovery->findBindings(Foo::clazz)); 183 | $this->assertEquals(array($binding3), $discovery->findBindings(Bar::clazz)); 184 | $this->assertEquals(array($binding3), $discovery->getBindings()); 185 | } 186 | 187 | public function testRemoveBindingsWithTypeDoesNothingIfNoneFound() 188 | { 189 | $discovery = $this->createDiscovery(); 190 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 191 | $discovery->removeBindings(Foo::clazz); 192 | 193 | $discovery = $this->loadDiscoveryFromStorage($discovery); 194 | 195 | $this->assertCount(0, $discovery->getBindings()); 196 | } 197 | 198 | public function testRemoveBindingsWithTypeDoesNothingIfTypeNotFound() 199 | { 200 | $discovery = $this->createDiscovery(); 201 | $discovery->removeBindings(Foo::clazz); 202 | 203 | $discovery = $this->loadDiscoveryFromStorage($discovery); 204 | 205 | $this->assertCount(0, $discovery->getBindings()); 206 | } 207 | 208 | /** 209 | * @expectedException \InvalidArgumentException 210 | * @expectedExceptionMessage stdClass 211 | */ 212 | public function testRemoveBindingsWithTypeFailsIfInvalidType() 213 | { 214 | $discovery = $this->createDiscovery(); 215 | $discovery->removeBindings(new stdClass()); 216 | } 217 | 218 | public function testRemoveBindingsWithExpression() 219 | { 220 | $binding1 = new StringBinding('string1', Foo::clazz, array('param1' => 'foo', 'param2' => 'bar')); 221 | $binding2 = new StringBinding('string2', Foo::clazz, array('param1' => 'foo')); 222 | $binding3 = new StringBinding('string3', Foo::clazz, array('param1' => 'bar')); 223 | 224 | $discovery = $this->createDiscovery(); 225 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING, array( 226 | new BindingParameter('param1'), 227 | new BindingParameter('param2'), 228 | ))); 229 | $discovery->addBinding($binding1); 230 | $discovery->addBinding($binding2); 231 | $discovery->addBinding($binding3); 232 | $discovery->removeBindings(null, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); 233 | 234 | $discovery = $this->loadDiscoveryFromStorage($discovery); 235 | 236 | $this->assertEquals(array($binding3), $discovery->findBindings(Foo::clazz)); 237 | $this->assertEquals(array($binding3), $discovery->getBindings()); 238 | } 239 | 240 | public function testRemoveBindingsWithTypeAndExpression() 241 | { 242 | $binding1 = new StringBinding('string1', Foo::clazz, array('param1' => 'foo', 'param2' => 'bar')); 243 | $binding2 = new StringBinding('string2', Foo::clazz, array('param1' => 'foo')); 244 | $binding3 = new StringBinding('string3', Foo::clazz, array('param1' => 'bar')); 245 | 246 | $discovery = $this->createDiscovery(); 247 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING, array( 248 | new BindingParameter('param1'), 249 | new BindingParameter('param2'), 250 | ))); 251 | $discovery->addBinding($binding1); 252 | $discovery->addBinding($binding2); 253 | $discovery->addBinding($binding3); 254 | $discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); 255 | 256 | $discovery = $this->loadDiscoveryFromStorage($discovery); 257 | 258 | $this->assertEquals(array($binding3), $discovery->findBindings(Foo::clazz)); 259 | $this->assertEquals(array($binding3), $discovery->getBindings()); 260 | } 261 | 262 | public function testRemoveBindingsWithTypeAndParametersDoesNothingIfNoneFound() 263 | { 264 | $discovery = $this->createDiscovery(); 265 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 266 | $discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); 267 | 268 | $discovery = $this->loadDiscoveryFromStorage($discovery); 269 | 270 | $this->assertCount(0, $discovery->getBindings()); 271 | } 272 | 273 | public function testRemoveBindingsWithTypeAndParametersDoesNothingIfTypeNotFound() 274 | { 275 | $discovery = $this->createDiscovery(); 276 | $discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); 277 | 278 | $discovery = $this->loadDiscoveryFromStorage($discovery); 279 | 280 | $this->assertCount(0, $discovery->getBindings()); 281 | } 282 | 283 | public function testAddBindingType() 284 | { 285 | $type = new BindingType(Foo::clazz, self::STRING_BINDING); 286 | 287 | $discovery = $this->createDiscovery(); 288 | $discovery->addBindingType($type); 289 | 290 | $discovery = $this->loadDiscoveryFromStorage($discovery); 291 | 292 | $this->assertEquals($type, $discovery->getBindingType(Foo::clazz)); 293 | } 294 | 295 | public function testAddBindingTypeAfterReadingStorage() 296 | { 297 | $type1 = new BindingType(Foo::clazz, self::STRING_BINDING); 298 | $type2 = new BindingType(Bar::clazz, self::STRING_BINDING); 299 | 300 | $discovery = $this->createDiscovery(); 301 | $discovery->addBindingType($type1); 302 | 303 | // Make sure that the previous call to addBindingType() stored all 304 | // necessary information in order to add further types (e.g. nextId) 305 | $discovery = $this->loadDiscoveryFromStorage($discovery); 306 | $discovery->addBindingType($type2); 307 | 308 | $this->assertEquals($type1, $discovery->getBindingType(Foo::clazz)); 309 | $this->assertEquals($type2, $discovery->getBindingType(Bar::clazz)); 310 | } 311 | 312 | /** 313 | * @expectedException \Puli\Discovery\Api\Type\DuplicateTypeException 314 | * @expectedExceptionMessage Foo 315 | */ 316 | public function testAddBindingTypeFailsIfAlreadyDefined() 317 | { 318 | $discovery = $this->createDiscovery(); 319 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 320 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 321 | } 322 | 323 | public function testRemoveBindingType() 324 | { 325 | $discovery = $this->createDiscovery(); 326 | $discovery->addBindingType($type1 = new BindingType(Foo::clazz, self::STRING_BINDING)); 327 | $discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); 328 | $discovery->removeBindingType(Bar::clazz); 329 | 330 | $discovery = $this->loadDiscoveryFromStorage($discovery); 331 | 332 | $this->assertEquals(array($type1), $discovery->getBindingTypes()); 333 | $this->assertTrue($discovery->hasBindingType(Foo::clazz)); 334 | $this->assertFalse($discovery->hasBindingType(Bar::clazz)); 335 | } 336 | 337 | public function testRemoveBindingTypeIgnoresUnknownTypes() 338 | { 339 | $discovery = $this->createDiscovery(); 340 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 341 | $discovery->removeBindingType(Bar::clazz); 342 | 343 | $discovery = $this->loadDiscoveryFromStorage($discovery); 344 | 345 | $this->assertTrue($discovery->hasBindingType(Foo::clazz)); 346 | $this->assertFalse($discovery->hasBindingType(Bar::clazz)); 347 | } 348 | 349 | /** 350 | * @expectedException \InvalidArgumentException 351 | * @expectedExceptionMessage stdClass 352 | */ 353 | public function testRemoveBindingTypeFailsIfInvalidType() 354 | { 355 | $discovery = $this->createDiscovery(); 356 | $discovery->removeBindingType(new stdClass()); 357 | } 358 | 359 | public function testRemoveBindingTypeRemovesCorrespondingBindings() 360 | { 361 | $discovery = $this->createDiscovery(); 362 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 363 | $discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); 364 | $discovery->addBinding($binding1 = new StringBinding('string1', Foo::clazz)); 365 | $discovery->addBinding($binding2 = new StringBinding('string2', Foo::clazz)); 366 | $discovery->addBinding($binding3 = new StringBinding('string3', Bar::clazz)); 367 | 368 | $discovery->removeBindingType(Foo::clazz); 369 | 370 | $discovery = $this->loadDiscoveryFromStorage($discovery); 371 | 372 | $this->assertEquals(array($binding3), $discovery->getBindings()); 373 | } 374 | 375 | public function testRemoveBindingTypes() 376 | { 377 | $discovery = $this->createDiscovery(); 378 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 379 | $discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); 380 | $discovery->removeBindingTypes(); 381 | 382 | $discovery = $this->loadDiscoveryFromStorage($discovery); 383 | 384 | $this->assertEquals(array(), $discovery->getBindingTypes()); 385 | $this->assertFalse($discovery->hasBindingType(Foo::clazz)); 386 | $this->assertFalse($discovery->hasBindingType(Bar::clazz)); 387 | } 388 | 389 | public function testRemoveBindingTypesRemovesBindings() 390 | { 391 | $discovery = $this->createDiscovery(); 392 | $discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); 393 | $discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); 394 | $discovery->addBinding($binding1 = new StringBinding('string1', Foo::clazz)); 395 | $discovery->addBinding($binding2 = new StringBinding('string2', Bar::clazz)); 396 | $discovery->removeBindingTypes(); 397 | 398 | $discovery = $this->loadDiscoveryFromStorage($discovery); 399 | 400 | $this->assertCount(0, $discovery->getBindings()); 401 | } 402 | } 403 | --------------------------------------------------------------------------------