├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── tests ├── bootstrap.php ├── TestAsset │ └── Adapter │ │ ├── Options │ │ ├── WrongAdapterOptions.php │ │ ├── FakeAdapterOptions.php │ │ ├── AdapterWithOptionsOptions.php │ │ ├── FakeOptionsMap.php │ │ └── WrongOptionsMap.php │ │ ├── ConvertNothing.php │ │ ├── AdapterWithOptions.php │ │ ├── WrongAdapter.php │ │ └── FakeAdapter.php ├── Adapter │ ├── AbstractOptionsEnabledAdapterTest.php │ └── Options │ │ └── OptionsMapTest.php └── ConversionTest.php ├── library ├── Exception │ ├── ExceptionInterface.php │ ├── DomainException.php │ ├── RuntimeException.php │ └── InvalidArgumentException.php ├── ConversionAlgorithmInterface.php ├── Adapter │ ├── AbstractOptionsEnabledAdapter.php │ └── Options │ │ └── OptionsMap.php └── Conversion.php ├── LICENSE ├── phpunit.xml.dist ├── composer.json ├── README.md └── CONTRIBUTING.md /.coveralls.yml: -------------------------------------------------------------------------------- 1 | src_dir: library -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .~lock.* 3 | *.ipr 4 | *.iws 5 | .idea 6 | .buildpath 7 | .project 8 | .settings 9 | build/ 10 | vendor/ 11 | composer.lock 12 | composer.phar 13 | phpunit.xml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.4 6 | - 8.0 7 | 8 | branches: 9 | only: 10 | - master 11 | - develop 12 | 13 | install: 14 | - composer self-update 15 | - composer install --dev --prefer-source 16 | 17 | before_script: 18 | - mkdir -p build/coverage 19 | 20 | script: 21 | - vendor/bin/phpunit 22 | 23 | after_script: 24 | - php vendor/bin/coveralls 25 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /library/Exception/DomainException.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class DomainException extends \DomainException implements ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /library/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class RuntimeException extends \RuntimeException implements ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /library/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 17 | { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/ConvertNothing.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/AdapterWithOptions.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | 9 | library 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /library/ConversionAlgorithmInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface ConversionAlgorithmInterface 17 | { 18 | /** 19 | * Convert $value with the defined settings 20 | * 21 | * @param string $value Data to decompress 22 | * @return string The converted data 23 | */ 24 | public function convert($value); 25 | 26 | /** 27 | * Return the conversion adapter name 28 | * 29 | * @return string 30 | */ 31 | public function getName(); 32 | } 33 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/Options/FakeAdapterOptions.php: -------------------------------------------------------------------------------- 1 | fake; 29 | } 30 | 31 | /** 32 | * @param string $fake 33 | */ 34 | public function setFake($fake) 35 | { 36 | $this->fake = $fake; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leodido/conversio", 3 | "description": "A library that provides a simple infrastructure to create your own converters and to perform any conversion you want", 4 | "minimum-stability": "stable", 5 | "license": "ISC", 6 | "authors": [ 7 | { 8 | "name": "Leo Di Donato", 9 | "email": "leodidonato@gmail.com", 10 | "homepage": "http://github.com/leodido" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4", 15 | "laminas/laminas-filter": "2.*" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "~4.2" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Conversio\\": "library/", 23 | "ConversioTest\\": "tests/" 24 | } 25 | }, 26 | "keywords": [ 27 | "convert", 28 | "conversion", 29 | "converter", 30 | "library", 31 | "conversions", 32 | "converters" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/Options/AdapterWithOptionsOptions.php: -------------------------------------------------------------------------------- 1 | opt2; 28 | } 29 | 30 | /** 31 | * @param mixed $opt2 32 | */ 33 | public function setOpt2($opt2) 34 | { 35 | $this->opt2 = $opt2; 36 | } 37 | 38 | /** 39 | * @return mixed 40 | */ 41 | public function getOpt1() 42 | { 43 | return $this->opt1; 44 | } 45 | 46 | /** 47 | * @param mixed $opt1 48 | */ 49 | public function setOpt1($opt1) 50 | { 51 | $this->opt1 = $opt1; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/WrongAdapter.php: -------------------------------------------------------------------------------- 1 | options = $options; 47 | return $this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/FakeAdapter.php: -------------------------------------------------------------------------------- 1 | options = $options; 47 | return $this; 48 | } 49 | 50 | /** 51 | * @return FakeAdapterOptions 52 | */ 53 | public function getOptions() 54 | { 55 | return $this->options; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/Options/FakeOptionsMap.php: -------------------------------------------------------------------------------- 1 | [1, 2, 3], 20 | 'string' => ['1', '2', '3'], 21 | ]; 22 | 23 | /** 24 | * @param $value 25 | * @return $this 26 | */ 27 | public function setNumber($value) 28 | { 29 | $this->setOption('number', $value); 30 | return $this; 31 | } 32 | 33 | /** 34 | * @return int 35 | */ 36 | public function getNumber() 37 | { 38 | return $this->getOption('number'); 39 | } 40 | 41 | /** 42 | * @param $value 43 | * @return $this 44 | */ 45 | public function setString($value) 46 | { 47 | $this->setOption('string', $value); 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getString() 55 | { 56 | return $this->getOption('string'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/TestAsset/Adapter/Options/WrongOptionsMap.php: -------------------------------------------------------------------------------- 1 | [1, 2, 3], 20 | 'string' => 'this value is not a list', 21 | ]; 22 | 23 | /** 24 | * @param $value 25 | * @return $this 26 | */ 27 | public function setNumber($value) 28 | { 29 | $this->setOption('number', $value); 30 | return $this; 31 | } 32 | 33 | /** 34 | * @return int 35 | */ 36 | public function getNumber() 37 | { 38 | return $this->getOption('number'); 39 | } 40 | 41 | /** 42 | * @param $value 43 | * @return $this 44 | */ 45 | public function setNotSet($value) 46 | { 47 | $this->setOption('not_set', $value); 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getNotSet() 55 | { 56 | return $this->getOption('not_set'); 57 | } 58 | 59 | /** 60 | * @param $value 61 | * @return $this 62 | */ 63 | public function setString($value) 64 | { 65 | $this->setOption('string', $value); 66 | return $this; 67 | } 68 | 69 | /** 70 | * @return string 71 | */ 72 | public function getString() 73 | { 74 | return $this->getOption('string'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /library/Adapter/AbstractOptionsEnabledAdapter.php: -------------------------------------------------------------------------------- 1 | options = $options; 45 | return $this; 46 | } 47 | 48 | /** 49 | * Retrieve the adapter options instance 50 | * 51 | * @return AbstractOptions 52 | */ 53 | public function getOptions() 54 | { 55 | if (!$this->options) { 56 | throw new Exception\RuntimeException(sprintf( 57 | 'No options instance set for the adapter "%s"', 58 | get_class($this) 59 | )); 60 | } 61 | return $this->options; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Conversio 2 | ========= 3 | 4 | [![Latest Stable Version](http://img.shields.io/packagist/v/leodido/conversio.svg?style=flat-square)](https://packagist.org/packages/leodido/conversio) [![Build Status](https://img.shields.io/travis/leodido/conversio.svg?style=flat-square)](https://travis-ci.org/leodido/conversio) [![Coverage](http://img.shields.io/coveralls/leodido/conversio.svg?style=flat-square)](https://coveralls.io/r/leodido/conversio) 5 | 6 | Conversio is a PHP library that provides a simple infrastructure to create your own converters and to perform any conversion. 7 | 8 | Explaination 9 | ------------ 10 | 11 | The entry point for **conversion** is the class `Conversion`, that acts as a **filter** (i.e., `Zend\Filter\AbstractFilter`). 12 | 13 | To implement a conversion you have to create an **adapter** (that will be passed to `Conversion` class) that describes its process. 14 | 15 | The adapters must implement the `ConversionAlgorithmInterface` interface. 16 | 17 | Furthermore, adapters can have **options** too, in the form of a `Zend\Stdlib\AbstractOptions` subclass. Conversio library requires only that the options class of each adapter is called with the name of the adapter followed by the suffix "Options" (e.g., `LanguageCodeOptions` is the option class of `LanguageCode` adapter class). 18 | 19 | In this case your adapter can extend `AbstractOptionsEnabledAdapter` abstract class to take advantage of its options related methods. 20 | 21 | The `OptionsMap` class is an utility class aimed to create a option class starting from a configuration hash that describes the options (by name) and their admitted values. 22 | 23 | Installation 24 | ------------ 25 | 26 | Add `leodido/conversio` to your `composer.json`. 27 | 28 | ```json 29 | { 30 | "require": { 31 | "leodido/conversio": "v0.3.0" 32 | } 33 | } 34 | ``` 35 | 36 | Usage 37 | ----- 38 | 39 | **WIP** 40 | 41 | Converters 42 | ---------- 43 | 44 | Here will be listed the converters created using Conversio library. 45 | 46 | - [LangCode](https://github.com/leodido/langcode-conv) 47 | 48 | --- 49 | 50 | [![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/conversio)](https://github.com/igrigorik/ga-beacon) 51 | -------------------------------------------------------------------------------- /tests/Adapter/AbstractOptionsEnabledAdapterTest.php: -------------------------------------------------------------------------------- 1 | adapter = new AdapterWithOptions(); 31 | } 32 | 33 | /** 34 | * @expectedException DomainException 35 | */ 36 | public function testSetNotValidOptionsShouldThrowDomainException() 37 | { 38 | /** @var $abstractOptsMock AbstractOptions */ 39 | $abstractOptsMock = $this->getMockForAbstractClass('Zend\Stdlib\AbstractOptions'); 40 | $this->adapter->setOptions($abstractOptsMock); 41 | } 42 | 43 | /** 44 | * @expectedException RuntimeException 45 | */ 46 | public function testGetNotSetOptionsShouldThrowRuntimeException() 47 | { 48 | $this->adapter->getOptions(); 49 | } 50 | 51 | public function testOptions() 52 | { 53 | $opts = new AdapterWithOptionsOptions(['opt1' => 1, 'opt2' => 2]); 54 | $this->assertInstanceOf( 55 | 'ConversioTest\TestAsset\Adapter\AdapterWithOptions', 56 | $this->adapter->setOptions($opts) 57 | ); 58 | $options = $this->adapter->getOptions(); 59 | $this->assertInstanceOf('ConversioTest\TestAsset\Adapter\Options\AdapterWithOptionsOptions', $options); 60 | $this->assertAttributeEquals(1, 'opt1', $options); 61 | $this->assertAttributeEquals(2, 'opt2', $options); 62 | $options = $options->toArray(); 63 | $this->assertArrayHasKey('opt1', $options); 64 | $this->assertArrayHasKey('opt2', $options); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Adapter/Options/OptionsMapTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('Conversio\Adapter\Options\OptionsMap', $optsMap); 28 | $this->assertInstanceOf('Zend\Stdlib\AbstractOptions', $optsMap); 29 | } 30 | 31 | /** 32 | * @expectedException DomainException 33 | */ 34 | public function testConstructWithoutValidConfigurationShouldThrowDomainException() 35 | { 36 | new OptionsMap(); 37 | } 38 | 39 | /** 40 | * @expectedException InvalidArgumentException 41 | */ 42 | public function testSetInvalidOptionsValueShouldThrowInvalidArgumentException() 43 | { 44 | $optsMap = new FakeOptionsMap; 45 | $optsMap->setNumber('string'); 46 | } 47 | 48 | public function testSettersAndGetters() 49 | { 50 | $optsMap = new FakeOptionsMap(['number' => 1, 'string' => '1']); 51 | $this->assertInstanceOf('ConversioTest\TestAsset\Adapter\Options\FakeOptionsMap', $optsMap); 52 | $this->assertEquals(1, $optsMap->getNumber()); 53 | $this->assertEquals('1', $optsMap->getString()); 54 | } 55 | 56 | public function testToArray() 57 | { 58 | $optsMap = new FakeOptionsMap; 59 | $this->assertEmpty($optsMap->toArray()); 60 | 61 | $optsMap->setNumber(3); 62 | $this->assertArrayHasKey('number', $optsMap->toArray()); 63 | $this->assertArrayNotHasKey('string', $optsMap->toArray()); 64 | $optsMap->setString('3'); 65 | $this->assertArrayHasKey('string', $optsMap->toArray()); 66 | } 67 | 68 | /** 69 | * @expectedException DomainException 70 | */ 71 | public function testSetNonExistentOptionShouldThrowDomainException() 72 | { 73 | $optsMap = new WrongOptionsMap; 74 | $optsMap->setNotSet('try!'); 75 | } 76 | 77 | /** 78 | * @expectedException RuntimeException 79 | */ 80 | public function testGetNonExistentOptionShouldThrowRuntimeException() 81 | { 82 | $optsMap = new WrongOptionsMap; 83 | $optsMap->getNotSet(); 84 | } 85 | 86 | /** 87 | * @expectedException DomainException 88 | */ 89 | public function testSetNonListOptionsShouldThrowDomainException() 90 | { 91 | $optsMap = new WrongOptionsMap; 92 | $optsMap->setString('value'); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions to **Conversio** library are always welcomed and encouraged. 4 | 5 | You make our lives easier by sending us your contributions through github pull requests. 6 | 7 | * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 8 | 9 | * Any contribution must provide tests for additional introduced conditions 10 | 11 | ## Team members 12 | 13 | The core team members are: 14 | 15 | | Name | Nickname | 16 | |:---------------:|:------------------------------------:| 17 | | Leo Di Donato | [leodido](http://github.com/leodido) | 18 | 19 | ## Got a question or problem? 20 | 21 | If you have questions about how to use Conversio library please write at . 22 | 23 | Other communication channels will be activated soon. In the mean time you can also contact us writing a [new issue](https://github.com/leodido/conversio/issues/new). 24 | 25 | Due to time constraints, we are not always able to respond as quickly as we would like. Please do not take delays personal and feel free to remind us. 26 | 27 | ## New features 28 | 29 | You can request a new feature by submitting an issue to our github repository. If you would like to implement a new feature then consider what kind of change it is: 30 | 31 | * **Major changes** 32 | 33 | This kind of contribution should be discussed first with us in issues. This way we can better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project. 34 | 35 | * **Small changes** 36 | 37 | Can be crafted and submitted to the github repository as a pull request. 38 | 39 | ## Bug triage 40 | 41 | Bug triaging is managed via github [issues](https://github.com/leodido/conversio/issues). 42 | 43 | You can help report bugs by filing them [here](https://github.com/leodido/conversio/issues). 44 | 45 | Before submitting new bugs please verify that similar ones do not exists yet. This will help us to reduce the duplicates and the references between issues. 46 | 47 | Is desiderable that you provide reproducible behaviours attaching (failing) tests. 48 | 49 | ## Testing 50 | 51 | The PHPUnit version to be used is the one installed as a dev-dependency via [composer](https://getcomposer.org/): 52 | 53 | ```bash 54 | $ ./vendor/bin/phpunit 55 | ``` 56 | 57 | ## Contributing process 58 | 59 | What branch to issue the pull request against? 60 | 61 | For **new features**, or fixes that introduce **new elements to the public API** (such as new public methods or properties), issue the pull request against the `develop` branch. 62 | 63 | For **hotfixes** against the stable release, issue the pull request against the `master` branch. 64 | 65 | 1. **Fork** the sphinxsearch [repository](https://github.com/leodido/conversio/fork) 66 | 67 | 2. **Checkout** the forked repository 68 | 69 | 3. Retrieve **dependencies** using [composer](https://getcomposer.org/) 70 | 71 | 4. Create your **local branch**, **commit** your code and **push** your local branch to your github fork 72 | 73 | 5. Send us a **pull request** as descripted for your changes to be included 74 | 75 | Please remember that **any contribution must provide tests** for additional introduced conditions. Accepted coverage for new contributions is 75%. Any contribution not satisfying this requirement won't be merged. 76 | 77 | Don't get discouraged! -------------------------------------------------------------------------------- /library/Adapter/Options/OptionsMap.php: -------------------------------------------------------------------------------- 1 | config, false)) { 41 | throw new Exception\DomainException(sprintf( 42 | '"%s" expects that options map configuration property is an hash table', 43 | __METHOD__ 44 | )); 45 | } 46 | parent::__construct($options); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function toArray() 53 | { 54 | return $this->options; 55 | } 56 | 57 | /** 58 | * Option setter with validation 59 | * If option can have the specified value then it is set, otherwise this method throws exception 60 | * 61 | * Tip: call it into your setter methods. 62 | * 63 | * @param $key 64 | * @param $value 65 | * @return $this 66 | * @throws Exception\DomainException 67 | * @throws Exception\InvalidArgumentException 68 | */ 69 | protected function setOption($key, $value) 70 | { 71 | if (!isset($this->config[$key])) { 72 | throw new Exception\DomainException( 73 | sprintf( 74 | 'Option "%s" does not exist; available options are (%s)', 75 | $key, 76 | implode( 77 | ', ', 78 | array_map( 79 | function ($opt) { 80 | return '"' . $opt . '"'; 81 | }, 82 | array_keys($this->config) 83 | ) 84 | ) 85 | ) 86 | ); 87 | } 88 | if (!ArrayUtils::isList($this->config[$key], false)) { 89 | throw new Exception\DomainException(sprintf( 90 | 'Option "%s" does not have a list of allowed values', 91 | $key 92 | )); 93 | } 94 | if (!ArrayUtils::inArray($value, $this->config[$key], true)) { 95 | throw new Exception\InvalidArgumentException(sprintf( 96 | 'Option "%s" can not be set to value "%s"; allowed values are (%s)', 97 | $key, 98 | $value, 99 | implode( 100 | ', ', 101 | array_map( 102 | function ($val) { 103 | return '"' . $val . '"'; 104 | }, 105 | $this->config[$key] 106 | ) 107 | ) 108 | )); 109 | } 110 | $this->options[$key] = $value; 111 | return $this; 112 | } 113 | 114 | /** 115 | * Option getter with check 116 | * 117 | * Tip: call it into your getter methods. 118 | * 119 | * @param $key 120 | * @return mixed 121 | * @throws Exception\RuntimeException 122 | */ 123 | protected function getOption($key) 124 | { 125 | if (!isset($this->options[$key])) { 126 | throw new Exception\RuntimeException(sprintf( 127 | 'Option "%s" not found', 128 | $key 129 | )); 130 | } 131 | 132 | return $this->options[$key]; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /library/Conversion.php: -------------------------------------------------------------------------------- 1 | setAdapter($params); 45 | break; 46 | case is_array($params): 47 | $this->setOptions($params); 48 | break; 49 | default: 50 | break; 51 | } 52 | } 53 | 54 | /** 55 | * Sets conversion adapter 56 | * 57 | * @param string|ConversionAlgorithmInterface $adapter Adapter to use 58 | * @return $this 59 | * @throws Exception\InvalidArgumentException 60 | * @throws Exception\RuntimeException 61 | */ 62 | public function setAdapter($adapter) 63 | { 64 | if (!is_string($adapter) && !$adapter instanceof ConversionAlgorithmInterface) { 65 | throw new Exception\InvalidArgumentException(sprintf( 66 | '"%s" expects a string or an instance of ConversionAlgorithmInterface; received "%s"', 67 | __METHOD__, 68 | is_object($adapter) ? get_class($adapter) : gettype($adapter) 69 | )); 70 | } 71 | if (is_string($adapter)) { 72 | if (!class_exists($adapter)) { 73 | throw new Exception\RuntimeException(sprintf( 74 | '"%s" unable to load adapter; class "%s" not found', 75 | __METHOD__, 76 | $adapter 77 | )); 78 | } 79 | $adapter = new $adapter(); 80 | if (!$adapter instanceof ConversionAlgorithmInterface) { 81 | throw new Exception\InvalidArgumentException(sprintf( 82 | '"%s" expects a string representing an instance of ConversionAlgorithmInterface; received "%s"', 83 | __METHOD__, 84 | is_object($adapter) ? get_class($adapter) : gettype($adapter) 85 | )); 86 | } 87 | } 88 | $this->adapter = $adapter; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Returns the current adapter 95 | * 96 | * @return ConversionAlgorithmInterface 97 | * @throws Exception\RuntimeException 98 | */ 99 | public function getAdapter() 100 | { 101 | if (!$this->adapter) { 102 | throw new Exception\RuntimeException(sprintf( 103 | '"%s" unable to load adapter; adapter not found', 104 | __METHOD__ 105 | )); 106 | } 107 | if (method_exists($this->adapter, 'setOptions')) { 108 | $this->adapter->setOptions($this->getAdapterOptions()); 109 | } 110 | 111 | return $this->adapter; 112 | } 113 | 114 | /** 115 | * Retrieve adapter name 116 | * 117 | * @return string 118 | */ 119 | public function getAdapterName() 120 | { 121 | return $this->getAdapter()->getName(); 122 | } 123 | 124 | /** 125 | * Set adapter options 126 | * 127 | * @param array|AbstractOptions $options 128 | * @return $this 129 | */ 130 | public function setAdapterOptions($options) 131 | { 132 | if (!is_array($options) && !$options instanceof AbstractOptions) { 133 | throw new Exception\InvalidArgumentException(sprintf( 134 | '"%s" expects an array or a valid instance of "%s"; received "%s"', 135 | __METHOD__, 136 | 'Zend\Stdlib\AbstractOptions', 137 | is_object($options) ? get_class($options) : gettype($options) 138 | )); 139 | } 140 | $this->adapterOptions = $options; 141 | $this->options = $options; 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * @return AbstractOptions 148 | */ 149 | public function getAdapterOptions() 150 | { 151 | if (is_array($this->adapterOptions)) { 152 | $optClass = $this->getAbstractOptions(); 153 | $this->adapterOptions = $optClass->setFromArray($this->adapterOptions); 154 | return $this->adapterOptions; 155 | } 156 | $wantedOptionsClass = self::getOptionsFullQualifiedClassName($this->adapter); 157 | if (get_class($this->adapterOptions) !== $wantedOptionsClass) { 158 | throw new Exception\DomainException(sprintf( 159 | '"%s" expects that options set are an array or a valid "%s" instance; received "%s"', 160 | __METHOD__, 161 | $wantedOptionsClass, 162 | get_class($this->adapterOptions) 163 | )); 164 | } 165 | $this->options = $this->adapterOptions->toArray(); 166 | return $this->adapterOptions; 167 | } 168 | 169 | /** 170 | * Instantiate and retrieve the options of the current adapter 171 | * 172 | * It also checks that an appropriate options class exists. 173 | * 174 | * @return AbstractOptions 175 | */ 176 | protected function getAbstractOptions() 177 | { 178 | $optClass = self::getOptionsFullQualifiedClassName($this->adapter); 179 | // Does the option class exist? 180 | if (!class_exists($optClass)) { 181 | throw new Exception\DomainException( 182 | sprintf( 183 | '"%s" expects that an options class ("%s") for the current adapter exists', 184 | __METHOD__, 185 | $optClass 186 | ) 187 | ); 188 | } 189 | $opts = new $optClass(); 190 | if (!$opts instanceof AbstractOptions) { 191 | throw new Exception\DomainException(sprintf( 192 | '"%s" expects the options class to resolve to a valid "%s" instance; received "%s"', 193 | __METHOD__, 194 | 'Zend\Stdlib\AbstractOptions', 195 | $optClass 196 | )); 197 | } 198 | return $opts; 199 | } 200 | 201 | /** 202 | * Set filter options 203 | * 204 | * @param array|Traversable $options 205 | * @throws Exception\InvalidArgumentException if options is not an array or Traversable 206 | * @return $this 207 | */ 208 | public function setOptions($options) 209 | { 210 | if (!is_array($options) && !$options instanceof Traversable) { 211 | throw new Exception\InvalidArgumentException(sprintf( 212 | '"%s" expects an array or Traversable; received "%s"', 213 | __METHOD__, 214 | is_object($options) ? get_class($options) : gettype($options) 215 | )); 216 | } 217 | foreach ($options as $key => $value) { 218 | if ($key === 'options') { 219 | $key = 'adapterOptions'; 220 | } 221 | $method = 'set' . ucfirst($key); 222 | if (method_exists($this, $method)) { 223 | $this->$method($value); 224 | } 225 | } 226 | return $this; 227 | } 228 | 229 | /** 230 | * Get individual or all options from underlying adapter options object 231 | * 232 | * @param string|null $option 233 | * @return mixed|array 234 | */ 235 | public function getOptions($option = null) 236 | { 237 | $this->getAdapterOptions(); 238 | if ($option !== null) { 239 | if (!isset($this->options[$option])) { 240 | throw new Exception\RuntimeException(sprintf( 241 | 'Options "%s" not found', 242 | $option 243 | )); 244 | } 245 | return $this->options[$option]; 246 | } 247 | return $this->options; 248 | } 249 | 250 | /** 251 | * {@inheritdoc} 252 | */ 253 | public function filter($value) 254 | { 255 | if (!is_string($value)) { 256 | return $value; 257 | } 258 | return $this->getAdapter()->convert($value); 259 | } 260 | 261 | /** 262 | * Recreate the full qualified class name of options class for the supplied adapter 263 | * 264 | * @param ConversionAlgorithmInterface|null $adapter 265 | * @return string 266 | * @throws Exception\InvalidArgumentException 267 | */ 268 | public static function getOptionsFullQualifiedClassName($adapter) 269 | { 270 | if (!$adapter) { 271 | throw new Exception\InvalidArgumentException(sprintf( 272 | '"%s" unable to load adapter; adapter not found', 273 | __METHOD__ 274 | )); 275 | } 276 | if (!$adapter instanceof ConversionAlgorithmInterface) { 277 | throw new Exception\InvalidArgumentException(sprintf( 278 | '"%s" expects an instance of ConversionAlgorithmInterface; received "%s"', 279 | __METHOD__, 280 | is_object($adapter) ? get_class($adapter) : gettype($adapter) 281 | )); 282 | } 283 | 284 | $adapterClass = get_class($adapter); 285 | $namespace = substr($adapterClass, 0, strrpos($adapterClass, '\\')); 286 | return $namespace . '\\Options\\' . $adapter->getName() . 'Options'; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /tests/ConversionTest.php: -------------------------------------------------------------------------------- 1 | mock = $this->getMockBuilder(self::CLASSNAME) 30 | ->disableOriginalConstructor() 31 | ->getMock(); 32 | } 33 | 34 | public function testConstructor() 35 | { 36 | // array input 37 | $input = []; 38 | $this->mock->expects($this->at(0)) 39 | ->method('setOptions') 40 | ->with($this->equalTo($input)); 41 | 42 | $class = new \ReflectionClass(self::CLASSNAME); 43 | $ctor = $class->getConstructor(); 44 | 45 | $ctor->invoke($this->mock, $input); 46 | 47 | // traversable input 48 | $input = new \ArrayIterator([]); 49 | $this->mock->expects($this->at(0)) 50 | ->method('setOptions') 51 | ->with($this->equalTo($input->getArrayCopy())); 52 | 53 | $ctor->invoke($this->mock, $input); 54 | 55 | // string input 56 | $input = 'adapter'; 57 | $this->mock->expects($this->at(0)) 58 | ->method('setAdapter') 59 | ->with($this->equalTo($input)); 60 | 61 | $ctor->invoke($this->mock, $input); 62 | 63 | // adapter input 64 | $input = $this->getMockForAbstractClass('Conversio\ConversionAlgorithmInterface'); 65 | $this->mock->expects($this->at(0)) 66 | ->method('setAdapter') 67 | ->with($this->equalTo($input)); 68 | 69 | $ctor->invoke($this->mock, $input); 70 | 71 | // null input 72 | $input = null; 73 | $this->mock->expects($this->never()) 74 | ->method('setAdapter') 75 | ->with($this->equalTo($input)); 76 | $this->mock->expects($this->never()) 77 | ->method('setOptions') 78 | ->with($this->equalTo($input)); 79 | 80 | $ctor->invoke($this->mock, $input); 81 | } 82 | 83 | public function testGetOptionsFQCNWithNotAConversionAlgorithmInterfaceShouldThrowInvalidArgumentException() 84 | { 85 | $this->setExpectedException('Conversio\Exception\InvalidArgumentException'); 86 | Conversion::getOptionsFullQualifiedClassName(new \stdClass()); 87 | } 88 | 89 | public function testGetAdapterNotSetShouldThrowRuntimeException() 90 | { 91 | $filter = new Conversion(); 92 | $this->setExpectedException('Conversio\Exception\RuntimeException'); 93 | $filter->getAdapter(); 94 | } 95 | 96 | public function testSetInvalidTypeAdapterShouldThrowInvalidArgumentException() 97 | { 98 | $filter = new Conversion(); 99 | $this->setExpectedException('Conversio\Exception\InvalidArgumentException'); 100 | $filter->setAdapter(new \stdClass()); 101 | } 102 | 103 | public function testSetNonExistentAdapterShouldThrowRuntimeException() 104 | { 105 | $filter = new Conversion(); 106 | $this->setExpectedException('Conversio\Exception\RuntimeException'); 107 | $filter->setAdapter('Conversio\Phantom\NonExistentAdapter'); 108 | 109 | $this->setExpectedException('Conversio\Exception\RuntimeException'); 110 | $filter->getAdapter(); 111 | } 112 | 113 | public function testSetInvalidAdapterClassShouldThrowInvalidArgumentException() 114 | { 115 | $filter = new Conversion(); 116 | $this->setExpectedException('Conversio\Exception\InvalidArgumentException'); 117 | $filter->setAdapter('\ArrayIterator'); 118 | 119 | $this->setExpectedException('Conversio\Exception\RuntimeException'); 120 | $filter->getAdapter(); 121 | } 122 | 123 | public function testSetAdapter() 124 | { 125 | $adapterClassName = 'ConversioTest\TestAsset\Adapter\ConvertNothing'; 126 | $filter = new Conversion(); 127 | 128 | // string param 129 | $filter->setAdapter($adapterClassName); 130 | $this->assertInstanceOf($adapterClassName, $filter->getAdapter()); 131 | $this->assertEquals($filter->getAdapter()->getName(), $filter->getAdapterName()); 132 | 133 | // instance param 134 | /** @var $adapterInstance \ConversioTest\TestAsset\Adapter\ConvertNothing */ 135 | $adapterInstance = new $adapterClassName(); 136 | $filter->setAdapter($adapterInstance); 137 | $this->assertInstanceOf($adapterClassName, $filter->getAdapter()); 138 | $this->assertEquals($adapterInstance->getName(), $filter->getAdapterName()); 139 | } 140 | 141 | public function testSetInvalidTypeOptionsShouldThrowInvalidArgumentException() 142 | { 143 | $filter = new Conversion(); 144 | $this->setExpectedException('Conversio\Exception\InvalidArgumentException'); 145 | $filter->setOptions('invalidoptions'); 146 | } 147 | 148 | public function testSetOptions() 149 | { 150 | $onlyOpts = [ 151 | 'options' => ['prop1' => 1, 'prop2' => 2] 152 | ]; 153 | 154 | $this->mock->expects($this->at(0)) 155 | ->method('setAdapterOptions') 156 | ->with($this->equalTo($onlyOpts['options'])); 157 | $class = new \ReflectionClass(self::CLASSNAME); 158 | $setOptsMethod = $class->getMethod('setOptions'); 159 | 160 | $setOptsMethod->invoke($this->mock, $onlyOpts); 161 | 162 | $opts = [ 163 | 'adapter' => 'ConversioTest\TestAsset\Adapter\ConvertNothing', 164 | 'options' => ['prop1' => 1, 'prop2' => 2], 165 | ]; 166 | 167 | $this->mock->expects($this->at(0)) 168 | ->method('setAdapter') 169 | ->with($this->equalTo($opts['adapter'])); 170 | $this->mock->expects($this->at(1)) 171 | ->method('setAdapterOptions') 172 | ->with($this->equalTo($opts['options'])); 173 | $class = new \ReflectionClass(self::CLASSNAME); 174 | $setOptsMethod = $class->getMethod('setOptions'); 175 | 176 | $setOptsMethod->invoke($this->mock, $opts); 177 | 178 | $opts = [ 179 | 'options' => ['prop1' => 1, 'prop2' => 2], 180 | 'adapter' => 'ConversioTest\TestAsset\Adapter\ConvertNothing', 181 | ]; 182 | 183 | $this->mock->expects($this->at(0)) 184 | ->method('setAdapterOptions') 185 | ->with($this->equalTo($opts['options'])); 186 | $this->mock->expects($this->at(1)) 187 | ->method('setAdapter') 188 | ->with($this->equalTo($opts['adapter'])); 189 | $class = new \ReflectionClass(self::CLASSNAME); 190 | $setOptsMethod = $class->getMethod('setOptions'); 191 | 192 | $setOptsMethod->invoke($this->mock, $opts); 193 | } 194 | 195 | public function testGetAdapterOptionsWhenAdapterHasNotBeenSpecifiedShouldThrowRuntimeException() 196 | { 197 | $onlyOpts = [ 198 | 'options' => ['opt1' => 'O1', 'opt2' => 'O2'], 199 | ]; 200 | $filter = new Conversion($onlyOpts); 201 | $this->setExpectedException('Conversio\Exception\InvalidArgumentException'); 202 | $filter->getAdapterOptions(); 203 | } 204 | 205 | public function testGetAdapterOptionsWhenAdapterAbstractOptionsClassDoesNotExistShouldThrowDomainException() 206 | { 207 | $onlyOpts = [ 208 | 'options' => ['opt1' => 'O1', 'opt2' => 'O2'], 209 | 'adapter' => 'ConversioTest\TestAsset\Adapter\ConvertNothing', 210 | ]; 211 | $filter = new Conversion($onlyOpts); 212 | $this->setExpectedException( 213 | 'Conversio\Exception\DomainException', 214 | sprintf( 215 | '%s::getAbstractOptions" expects that an options class ("%s") for the current adapter exists', 216 | self::CLASSNAME, 217 | 'ConversioTest\TestAsset\Adapter\Options\ConvertNothingOptions' 218 | ) 219 | ); 220 | $filter->getAdapterOptions(); 221 | } 222 | 223 | public function testGetAdapterOptionsWhenAdapterOptionsAreNotAbstractOptionsShouldThrowDomainException() 224 | { 225 | $onlyOpts = [ 226 | 'options' => ['opt1' => 'O1', 'opt2' => 'O2'], 227 | 'adapter' => 'ConversioTest\TestAsset\Adapter\WrongAdapter', 228 | ]; 229 | $filter = new Conversion($onlyOpts); 230 | $this->setExpectedException( 231 | 'Conversio\Exception\DomainException', 232 | sprintf( 233 | '%s::getAbstractOptions" expects the options class to resolve to a valid "%s" instance; received "%s"', 234 | self::CLASSNAME, 235 | 'Zend\Stdlib\AbstractOptions', 236 | 'ConversioTest\TestAsset\Adapter\Options\WrongAdapterOptions' 237 | ) 238 | ); 239 | $filter->getAdapterOptions(); 240 | } 241 | 242 | public function testGetAdapterOptionsWhenNotMachingAdapterOptionsWasSetShouldThrowDomainException() 243 | { 244 | /** @var $mockOptions \Zend\Stdlib\AbstractOptions */ 245 | $mockOptions = $this->getMockForAbstractClass('\Zend\Stdlib\AbstractOptions'); 246 | $filter = new Conversion(); 247 | $filter->setAdapter(new FakeAdapter()); 248 | $filter->setAdapterOptions($mockOptions); 249 | $this->setExpectedException('Conversio\Exception\DomainException'); 250 | $filter->getAdapterOptions(); 251 | } 252 | 253 | public function testGetAdapterOptionsAndOptions() 254 | { 255 | $fakeAdapterOpts = new FakeAdapterOptions(); 256 | $filter = new Conversion(); 257 | $filter->setAdapter(new FakeAdapter()); 258 | $filter->setAdapterOptions($fakeAdapterOpts); 259 | $this->assertInstanceOf(get_class($fakeAdapterOpts), $filter->getAdapterOptions()); 260 | $this->assertEquals($fakeAdapterOpts, $filter->getAdapterOptions()); 261 | $this->assertEquals($fakeAdapterOpts->toArray(), $filter->getOptions()); 262 | 263 | $params = [ 264 | 'adapterOptions' => ['fake' => 'fake'], 265 | 'adapter' => 'ConversioTest\TestAsset\Adapter\FakeAdapter', 266 | ]; 267 | $filter = new Conversion($params); 268 | $this->assertInstanceOf(get_class($fakeAdapterOpts), $filter->getAdapterOptions()); 269 | $this->assertEquals(new FakeAdapterOptions($params['adapterOptions']), $filter->getAdapterOptions()); 270 | $this->assertEquals($params['adapterOptions'], $filter->getOptions()); 271 | 272 | 273 | $filter = new Conversion(); 274 | $filter->setAdapter(new FakeAdapter()); 275 | $this->assertEmpty($filter->getOptions()); 276 | $this->assertEquals($fakeAdapterOpts, $filter->getAdapterOptions()); 277 | $this->assertEquals($fakeAdapterOpts->toArray(), $filter->getOptions()); 278 | 279 | $this->assertEquals($fakeAdapterOpts->getFake(), $filter->getOptions('fake')); 280 | 281 | $this->setExpectedException('Conversio\Exception\RuntimeException'); 282 | $filter->getOptions('not_exists'); 283 | } 284 | 285 | public function testSetAdapterOptionsWithInvalidTypeInputShouldThrowInvalidArgumentException() 286 | { 287 | $filter = new Conversion(); 288 | $this->setExpectedException( 289 | 'Conversio\Exception\InvalidArgumentException', 290 | sprintf( 291 | '"%s::setAdapterOptions" expects an array or a valid instance of "%s"; received "%s"', 292 | self::CLASSNAME, 293 | 'Zend\Stdlib\AbstractOptions', 294 | 'string' 295 | ) 296 | ); 297 | $filter->setAdapterOptions('invalid'); 298 | } 299 | 300 | public function testFilter() 301 | { 302 | // Filtering a non string return the non strig input 303 | $notAString = []; 304 | $filter = new Conversion(); 305 | $this->assertEquals($notAString, $filter->filter($notAString)); 306 | 307 | // Filtering 308 | $input = 'string'; 309 | 310 | $adapterMock = $this->getMock('Conversio\ConversionAlgorithmInterface'); 311 | 312 | $this->mock->expects($this->at(0)) 313 | ->method('getAdapter') 314 | ->willReturn($adapterMock); 315 | 316 | $adapterMock->expects($this->at(0)) 317 | ->method('convert') 318 | ->with($this->equalTo($input)); 319 | 320 | $class = new \ReflectionClass(self::CLASSNAME); 321 | $filterMethod = $class->getMethod('filter'); 322 | $filterMethod->invoke($this->mock, $input); 323 | } 324 | 325 | 326 | public function testGetAdapterThatHaveOptions() 327 | { 328 | $adapterOpts = new FakeAdapterOptions(['fake' => 'ABC']); 329 | $adapter = new FakeAdapter(); 330 | 331 | $filter = new Conversion(); 332 | $filter->setAdapter($adapter); 333 | $filter->setAdapterOptions($adapterOpts); 334 | 335 | $this->assertSame($adapter, $filter->getAdapter()); 336 | $this->assertSame($adapterOpts, $adapter->getOptions()); 337 | } 338 | } 339 | --------------------------------------------------------------------------------