├── .codeclimate.yml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpmd.xml ├── phpunit.xml ├── src ├── ArrayHelpers │ ├── ByteArray.php │ ├── ByteArrayDispenser.php │ ├── DotArray.php │ └── SearchableArray.php ├── ClassLoader.php ├── Enumerable.php ├── EnumerableInterface.php ├── Interfaces │ ├── ArrayAccessTrait.php │ └── IteratorTrait.php ├── Magic.php ├── SPL │ ├── CountableTrait.php │ └── SeekableIteratorTrait.php └── TraitException.php └── tests ├── ArrayTrait ├── ArrayTraitTest.php ├── ByteArrayTest.php ├── TestArrayClass.php └── gw2api-floors-2-1.json ├── ClassLoader ├── ClassLoaderTraitTest.php ├── tAbstract.php ├── tClass.php ├── tInterface.php ├── tInterface2.php └── tTrait.php ├── Enumerable ├── EnumerableContainer.php └── EnumerableTest.php ├── Interfaces ├── ArrayAccessTest.php └── TestArrayAccessContainer.php └── Magic ├── MagicContainer.php └── MagicTest.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | phpmd: 5 | enabled: true 6 | config: 7 | file_extensions: "php" 8 | rulesets: "phpmd.xml" 9 | markdownlint: 10 | enabled: true 11 | ratings: 12 | paths: 13 | - "**.php" 14 | exclude_paths: 15 | - examples/**/* 16 | - tests/**/* 17 | - "**/vendor/**/*" 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - examples/* 4 | - tests/* 5 | - vendor/* 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | include: 5 | - php: 7.2 6 | - php: 7.3 7 | - php: 7.4snapshot 8 | - php: nightly 9 | allow_failures: 10 | - php: 7.4snapshot 11 | - php: nightly 12 | 13 | before_script: travis_retry composer install --no-interaction --prefer-source 14 | 15 | script: vendor/bin/phpunit --configuration phpunit.xml --coverage-clover clover.xml 16 | 17 | after_script: bash <(curl -s https://codecov.io/bash) 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Smiley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chillerlan/php-traits 2 | 3 | A collection of (more or less) useful traits for PHP7.2+ 4 | 5 | [![version][packagist-badge]][packagist] 6 | [![license][license-badge]][license] 7 | [![Travis][travis-badge]][travis] 8 | [![Coverage][coverage-badge]][coverage] 9 | [![Scrunitizer][scrutinizer-badge]][scrutinizer] 10 | [![Packagist downloads][downloads-badge]][downloads] 11 | [![PayPal donate][donate-badge]][donate] 12 | 13 | [packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-traits.svg?style=flat-square 14 | [packagist]: https://packagist.org/packages/chillerlan/php-traits 15 | [license-badge]: https://img.shields.io/github/license/chillerlan/php-traits.svg?style=flat-square 16 | [license]: https://github.com/chillerlan/php-traits/blob/master/LICENSE 17 | [travis-badge]: https://img.shields.io/travis/chillerlan/php-traits.svg?style=flat-square 18 | [travis]: https://travis-ci.org/chillerlan/php-traits 19 | [coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-traits.svg?style=flat-square 20 | [coverage]: https://codecov.io/github/chillerlan/php-traits 21 | [scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-traits.svg?style=flat-square 22 | [scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-traits 23 | [downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-traits.svg?style=flat-square 24 | [downloads]: https://packagist.org/packages/chillerlan/php-traits/stats 25 | [donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square 26 | [donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WLYUNAT9ZTJZ4 27 | 28 | ## Features 29 | - `ClassLoader` - invokes objects of a given class and interface/type with an arbitrary count of constructor arguments 30 | - `Magic` - turns methods into magic properties 31 | - `Enumerable` - provides some of [prototype's enumerable methods](http://api.prototypejs.org/language/Enumerable/), implements `EnumerableInterface` 32 | - `ArrayHelpers` 33 | - `ByteArray` - useful for byte/bit-flipping purposes, extends [`SplFixedArray`](http://php.net/manual/class.splfixedarray.php) 34 | - `ByteArrayDispenser` - creates `ByteArray` from several data types (hex, base64, binary, json etc.) 35 | - `DotArray` - adds dot key notation functionality 36 | - `SearchableArray` - deep search arrays using [`RecursiveIteratorIterator`](http://php.net/manual/class.recursiveiteratoriterator.php) 37 | - `Interfaces` 38 | - `ArrayAccessTrait` - implements [`ArrayAccess`](http://php.net/manual/class.arrayaccess.php) 39 | - `IteratorTrait` - implements [`Iterator`](http://php.net/manual/class.iterator.php) 40 | - `SPL` 41 | - `CountableTrait` - implements [`Countable`](http://php.net/manual/class.countable.php) 42 | - `SeekableIteratorTrait` - implements [`SeekableIterator`](http://php.net/manual/class.seekableiterator.php) 43 | 44 | ## Documentation 45 | 46 | ### Installation 47 | **requires [composer](https://getcomposer.org)** 48 | 49 | *composer.json* (note: replace `dev-master` with a version boundary) 50 | ```json 51 | { 52 | "require": { 53 | "php": "^7.2", 54 | "chillerlan/php-traits": "dev-master" 55 | } 56 | } 57 | ``` 58 | 59 | #### Manual installation 60 | Download the desired version of the package from [master](https://github.com/chillerlan/php-traits/archive/master.zip) or 61 | [release](https://github.com/chillerlan/php-traits/releases) and extract the contents to your project folder. After that: 62 | - run `composer install` to install the required dependencies and generate `/vendor/autoload.php`. 63 | - if you use a custom autoloader, point the namespace `chillerlan\Traits` to the folder `src` of the package 64 | 65 | Profit! 66 | 67 | ### Usage 68 | 69 | #### `ClassLoader` 70 | Simple usage: 71 | ```php 72 | class MyClass{ 73 | use ClassLoader; 74 | 75 | protected function doStuff(string $class){ 76 | $obj = $this->loadClass(__NAMESPACE__.'\\Whatever\\'.$class); 77 | 78 | // do stuff 79 | } 80 | } 81 | ``` 82 | 83 | Let's assume we have several classes that implement the same interface, but their constructors have different parameter counts, like so: 84 | ```php 85 | class SomeClass implements MyInterface{ 86 | public funtion __construct($param_foo){} 87 | } 88 | 89 | class OtherClass implements MyInterface{ 90 | public funtion __construct($param_foo, $param_bar){} 91 | } 92 | ``` 93 | 94 | Initialize an object based on a selction 95 | 96 | ```php 97 | class MyClass{ 98 | use ClassLoader; 99 | 100 | protected $classes = [ 101 | 'foo' => SomeClass::class, 102 | 'bar' => OtherClass::class 103 | ]; 104 | 105 | protected funtion initInterface(string $whatever, $foo, $bar = null):MyInterface{ 106 | 107 | foreach($this->classes as $what => $class){ 108 | if($whatever === $what){ 109 | return $this->loadClass($class, MyInterface::class, $foo, $bar); 110 | } 111 | } 112 | 113 | } 114 | } 115 | ``` 116 | 117 | 118 | #### `Magic` 119 | `Magic` allows to access internal methods like as properties. 120 | ```php 121 | class MyMagicContainer{ 122 | use Magic; 123 | 124 | protected $foo; 125 | 126 | protected function magic_get_foo(){ 127 | // do whatever... 128 | 129 | return 'foo: '.$this->foo; 130 | } 131 | 132 | protected function magic_set_foo($value){ 133 | // do stuff with $value 134 | // ... 135 | 136 | $this->foo = $value.'bar'; 137 | } 138 | } 139 | ``` 140 | 141 | ```php 142 | $magic = new MyMagicContainer; 143 | 144 | $magic->foo = 'foo'; 145 | 146 | var_dump($magic->foo); // -> foo: foobar 147 | 148 | ``` 149 | 150 | #### `Enumerable` 151 | ```php 152 | class MyEnumerableContainer implements EnumerableInterface{ 153 | use Enumerable; 154 | 155 | public function __construct(array $data){ 156 | $this->array = $data; 157 | } 158 | } 159 | ``` 160 | 161 | ```php 162 | $enum = new MyEnumerableContainer($data); 163 | 164 | $enum 165 | ->__each(function($value, $index){ 166 | // do stuff 167 | 168 | $this->array[$index] = $stuff; 169 | }) 170 | ->__reverse() 171 | ->__to_array() 172 | ; 173 | 174 | $arr = $enum->__map(function($value, $index){ 175 | // do stuff 176 | 177 | return $stuff; 178 | }); 179 | 180 | $enum; 181 | 182 | ``` 183 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chillerlan/php-traits", 3 | "description": "Some useful traits for PHP 7.2+", 4 | "homepage": "https://github.com/chillerlan/php-traits", 5 | "license": "MIT", 6 | "type": "library", 7 | "minimum-stability": "stable", 8 | "keywords": [ 9 | "trait", "php7", "helper", "dotenv", "container" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Smiley", 14 | "email": "smiley@chillerlan.net", 15 | "homepage": "https://github.com/codemasher" 16 | } 17 | ], 18 | "support": { 19 | "issues": "https://github.com/chillerlan/php-traits/issues", 20 | "source": "https://github.com/chillerlan/php-traits" 21 | }, 22 | "require": { 23 | "php": "^7.2", 24 | "ext-json": "*" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^8.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "chillerlan\\Traits\\": "src/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "chillerlan\\TraitTest\\": "tests/" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | codemasher/php-traits PMD ruleset 8 | */examples/* 9 | */tests/* 10 | 11 | 12 | 13 | 14 | 1 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | 19 | ./tests/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/ArrayHelpers/ByteArray.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\ArrayHelpers; 14 | 15 | use ReflectionClass, SplFixedArray; 16 | 17 | /** 18 | * @extends \SplFixedArray 19 | */ 20 | class ByteArray extends SplFixedArray{ 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function toString():string{ 26 | return $this->map('chr'); 27 | } 28 | 29 | /** 30 | * @return string 31 | */ 32 | public function toHex():string{ 33 | return $this->map(function($v){ 34 | return \str_pad(\dechex($v), '2', '0', \STR_PAD_LEFT); 35 | }); 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function toJSON():string{ 42 | return \json_encode($this->toArray()); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function toBase64():string{ 49 | return \base64_encode($this->toString()); 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function toBin():string{ 56 | return $this->map(function($v){ 57 | return \str_pad(\decbin($v), '8', '0', \STR_PAD_LEFT); 58 | }); 59 | } 60 | 61 | /** 62 | * @param callable $m 63 | * 64 | * @return string 65 | */ 66 | public function map(callable $m):string{ 67 | return \implode('', \array_map($m, $this->toArray())); 68 | } 69 | 70 | /** 71 | * @param \SplFixedArray $src 72 | * @param int $length 73 | * @param int|null $offset 74 | * @param int|null $srcOffset 75 | * 76 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 77 | */ 78 | public function copyFrom(SplFixedArray $src, int $length = null, int $offset = null, int $srcOffset = null):ByteArray{ 79 | $length = $length ?? $src->count(); 80 | $offset = $offset ?? $length; 81 | $srcOffset = $srcOffset ?? 0; 82 | 83 | $diff = $offset + $length; 84 | 85 | if($diff > $this->count()){ 86 | $this->setSize($diff); 87 | } 88 | 89 | for($i = 0; $i < $length; $i++){ 90 | $this[$i + $offset] = $src[$i + $srcOffset]; 91 | } 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param int $offset 98 | * @param int|null $length 99 | * 100 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 101 | */ 102 | public function slice(int $offset, int $length = null):ByteArray{ 103 | 104 | // keep an extended class 105 | /** @var \chillerlan\Traits\ArrayHelpers\ByteArray $slice */ 106 | $slice = (new ReflectionClass($this))->newInstanceArgs([$length ?? ($this->count() - $offset)]); 107 | 108 | foreach($slice as $i => $_){ 109 | $slice[$i] = $this[$offset + $i]; 110 | } 111 | 112 | return $slice; 113 | } 114 | 115 | /** 116 | * @param \SplFixedArray $array 117 | * 118 | * @return bool 119 | */ 120 | public function equal(SplFixedArray $array):bool{ 121 | 122 | if($this->count() !== $array->count()){ 123 | return false; 124 | } 125 | 126 | $diff = 0; 127 | 128 | foreach($this as $k => $v){ 129 | $diff |= $v ^ $array[$k]; 130 | } 131 | 132 | $diff = ($diff - 1) >> 31; 133 | 134 | return ($diff & 1) === 1; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/ArrayHelpers/ByteArrayDispenser.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\ArrayHelpers; 14 | 15 | use chillerlan\Traits\TraitException; 16 | use Exception, Traversable; 17 | 18 | /** 19 | * 20 | */ 21 | class ByteArrayDispenser{ 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $byteArrayClass = ByteArray::class; 27 | 28 | /** 29 | * @param int $int 30 | * 31 | * @return bool 32 | */ 33 | public function isAllowedInt(int $int):bool{ 34 | return $int >= 0 && $int <= PHP_INT_MAX; 35 | } 36 | 37 | /** 38 | * @param int $size 39 | * 40 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 41 | * @throws \chillerlan\Traits\TraitException 42 | */ 43 | public function fromIntSize(int $size):ByteArray{ 44 | 45 | if(!$this->isAllowedInt($size)){ 46 | throw new TraitException('invalid size'); 47 | } 48 | 49 | return new $this->byteArrayClass($size); 50 | } 51 | 52 | /** 53 | * @param array $array 54 | * @param bool $save_indexes 55 | * 56 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 57 | * @throws \chillerlan\Traits\TraitException 58 | */ 59 | public function fromArray(array $array, bool $save_indexes = null):ByteArray{ 60 | 61 | try{ 62 | $out = $this->fromIntSize(\count($array)); 63 | 64 | $array = ($save_indexes ?? true) ? $array : \array_values($array); 65 | 66 | foreach($array as $k => $v){ 67 | $out[$k] = $v; 68 | } 69 | 70 | return $out; 71 | } 72 | // this can be anything 73 | // @codeCoverageIgnoreStart 74 | catch(Exception $e){ 75 | throw new TraitException($e->getMessage()); 76 | } 77 | // @codeCoverageIgnoreEnd 78 | 79 | } 80 | 81 | /** 82 | * @param int $len 83 | * 84 | * @param mixed $fill 85 | * 86 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 87 | * @throws \chillerlan\Traits\TraitException 88 | */ 89 | public function fromArrayFill(int $len, $fill = null):ByteArray{ 90 | 91 | if(!$this->isAllowedInt($len)){ 92 | throw new TraitException('invalid length'); 93 | } 94 | 95 | return $this->fromArray(\array_fill(0, $len, $fill)); 96 | } 97 | 98 | /** 99 | * @param string $str 100 | * 101 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 102 | */ 103 | public function fromString(string $str):ByteArray{ 104 | return $this->fromArray(\unpack('C*', $str), false); 105 | } 106 | 107 | /** 108 | * checks if the given string is a hex string: ab12cd34 (case insensitive, whitespace allowed) 109 | * 110 | * @param string $hex 111 | * 112 | * @return bool 113 | */ 114 | public function isAllowedHex(string $hex):bool{ 115 | return \preg_match('/^[\s\r\n\t \da-f]+$/i', $hex) && \strlen($hex) % 2 === 0; 116 | } 117 | 118 | /** 119 | * @param string $hex 120 | * 121 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray|mixed 122 | * @throws \chillerlan\Traits\TraitException 123 | */ 124 | public function fromHex(string $hex):ByteArray{ 125 | $hex = \preg_replace('/[\s\r\n\t ]/', '', $hex); 126 | 127 | if(!$this->isAllowedHex($hex)){ 128 | throw new TraitException('invalid hex string'); 129 | } 130 | 131 | return $this->fromString(\pack('H*', $hex)); 132 | } 133 | 134 | /** 135 | * checks if the given (trimmed) JSON string is a an array that contains numbers: [1, 2, 3] 136 | * 137 | * @param string $json 138 | * 139 | * @return bool 140 | */ 141 | public function isAllowedJSON(string $json):bool{ 142 | return \preg_match('/^\\[[\s\d,]+\\]$/', $json) > 0; 143 | } 144 | 145 | /** 146 | * @param string $json 147 | * 148 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray|mixed 149 | * @throws \chillerlan\Traits\TraitException 150 | */ 151 | public function fromJSON(string $json):ByteArray{ 152 | $json = \trim($json); 153 | 154 | if(!$this->isAllowedJSON($json)){ 155 | throw new TraitException('invalid JSON array'); 156 | } 157 | 158 | return $this->fromArray(\json_decode(\trim($json))); 159 | } 160 | 161 | /** 162 | * checks if the given (trimmed) string is base64 encoded binary 163 | * 164 | * @param string $base64 165 | * 166 | * @return bool 167 | */ 168 | public function isAllowedBase64(string $base64):bool{ 169 | return \preg_match('#^[a-z\d/]*={0,2}$#i', $base64) > 0; 170 | } 171 | 172 | /** 173 | * @param string $base64 174 | * 175 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray|mixed 176 | * @throws \chillerlan\Traits\TraitException 177 | */ 178 | public function fromBase64(string $base64):ByteArray{ 179 | $base64 = \trim($base64); 180 | 181 | if(!$this->isAllowedBase64($base64)){ 182 | throw new TraitException('invalid base64 string'); 183 | } 184 | 185 | return $this->fromString(\base64_decode($base64)); 186 | } 187 | 188 | /** 189 | * checks if the given (trimmed) string is a binary string: [01] in multiples of 8 190 | * 191 | * @param string $bin 192 | * 193 | * @return bool 194 | */ 195 | public function isAllowedBin(string $bin):bool{ 196 | return \preg_match('/^[01]+$/', $bin) > 0 && \strlen($bin) % 8 === 0; 197 | } 198 | 199 | /** 200 | * @param string $bin 201 | * 202 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 203 | * @throws \chillerlan\Traits\TraitException 204 | */ 205 | public function fromBin(string $bin):ByteArray{ 206 | $bin = \trim($bin); 207 | 208 | if(!$this->isAllowedBin($bin)){ 209 | throw new TraitException('invalid binary string'); 210 | } 211 | 212 | return $this->fromArray(\array_map('bindec', \str_split($bin, 8))); 213 | } 214 | 215 | /** 216 | * @param string|array|\SplFixedArray $data 217 | * 218 | * @return \chillerlan\Traits\ArrayHelpers\ByteArray 219 | * @throws \chillerlan\Traits\TraitException 220 | */ 221 | public function guessFrom($data):ByteArray{ 222 | 223 | if($data instanceof Traversable){ 224 | return $this->fromArray(\iterator_to_array($data)); 225 | } 226 | 227 | if(\is_array($data)){ 228 | return $this->fromArray($data); 229 | } 230 | 231 | if(\is_string($data)){ 232 | 233 | foreach(['Bin', 'Hex', 'JSON', 'Base64'] as $type){ 234 | 235 | if(\call_user_func_array([$this, 'isAllowed'.$type], [$data]) === true){ 236 | return \call_user_func_array([$this, 'from'.$type], [$data]); 237 | } 238 | 239 | } 240 | 241 | return $this->fromString($data); 242 | } 243 | 244 | throw new TraitException('invalid input'); 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /src/ArrayHelpers/DotArray.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\ArrayHelpers; 14 | 15 | /** 16 | * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Support/Arr.php 17 | */ 18 | trait DotArray{ 19 | 20 | /** 21 | * @var array 22 | */ 23 | protected $array = []; 24 | 25 | /** 26 | * Checks if $key isset in $array using dot notation and returns it on success. 27 | * 28 | * @param string $dotKey the key to search 29 | * @param mixed $default [optional] a default value in case the key isn't being found 30 | * 31 | * @return mixed returns $array[$key], $default otherwise. 32 | */ 33 | public function get(string $dotKey, $default = null){ 34 | 35 | if(isset($this->array[$dotKey])){ 36 | return $this->array[$dotKey]; 37 | } 38 | 39 | $array = &$this->array; 40 | 41 | foreach(\explode('.', $dotKey) as $segment){ 42 | 43 | if(!\is_array($array) || !\array_key_exists($segment, $array)){ 44 | return $default; 45 | } 46 | 47 | $array = &$array[$segment]; 48 | } 49 | 50 | return $array; 51 | } 52 | 53 | /** 54 | * Checks if $key exists in $array using dot notation and returns it on success 55 | * 56 | * @param string $dotKey the key to search 57 | * 58 | * @return bool 59 | */ 60 | public function in(string $dotKey):bool{ 61 | 62 | if(empty($this->array)){ 63 | return false; 64 | } 65 | 66 | if(\array_key_exists($dotKey, $this->array)){ 67 | return true; 68 | } 69 | 70 | $array = &$this->array; 71 | 72 | foreach(\explode('.', $dotKey) as $segment){ 73 | 74 | if(!\is_array($array) || !\array_key_exists($segment, $array)){ 75 | return false; 76 | } 77 | 78 | $array = &$array[$segment]; 79 | } 80 | 81 | return true; 82 | } 83 | 84 | /** 85 | * Sets $key in $array using dot notation 86 | * 87 | * If no key is given to the method, the entire array will be replaced. 88 | * 89 | * @param string $dotKey 90 | * @param mixed $value 91 | * 92 | * @return \chillerlan\Traits\ArrayHelpers\DotArray 93 | */ 94 | public function set(string $dotKey, $value){ 95 | 96 | if(empty($dotKey)){ 97 | $this->array = $value; 98 | 99 | return $this; 100 | } 101 | 102 | $array = &$this->array; 103 | $keys = \explode('.', $dotKey); 104 | 105 | while(\count($keys) > 1){ 106 | $dotKey = \array_shift($keys); 107 | 108 | // If the key doesn't exist at this depth, we will just create an empty array 109 | // to hold the next value, allowing us to create the arrays to hold final 110 | // values at the correct depth. Then we'll keep digging into the array. 111 | if(!isset($array[$dotKey]) || !\is_array($array[$dotKey])){ 112 | $array[$dotKey] = []; 113 | } 114 | 115 | $array = &$array[$dotKey]; 116 | } 117 | 118 | $array[\array_shift($keys)] = $value; 119 | 120 | return $this; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/ArrayHelpers/SearchableArray.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\ArrayHelpers; 14 | 15 | use ArrayIterator, ArrayObject, RecursiveArrayIterator, RecursiveIteratorIterator, Traversable; 16 | 17 | trait SearchableArray{ 18 | use DotArray; 19 | 20 | /** 21 | * @var \IteratorIterator|\RecursiveIteratorIterator 22 | */ 23 | protected $iterator; 24 | 25 | /** 26 | * SearchableArray constructor. 27 | * 28 | * @param array|object|\Traversable|\ArrayIterator|\ArrayObject|null $array 29 | */ 30 | public function __construct($array = null){ 31 | 32 | if(($array instanceof ArrayObject) || ($array instanceof ArrayIterator)){ 33 | $this->array = $array->getArrayCopy(); // @codeCoverageIgnore 34 | } 35 | elseif($array instanceof Traversable){ 36 | $this->array = \iterator_to_array($array); // @codeCoverageIgnore 37 | } 38 | // yields unexpected results with DotArray 39 | elseif(\gettype($array) === 'object'){ 40 | $this->array = \get_object_vars($array); // @codeCoverageIgnore 41 | } 42 | elseif(\is_array($array)){ 43 | $this->array = $array; 44 | } 45 | 46 | $this->iterator = new RecursiveIteratorIterator( 47 | new RecursiveArrayIterator($this->array), 48 | RecursiveIteratorIterator::SELF_FIRST 49 | ); 50 | } 51 | 52 | /** 53 | * @param string $dotKey 54 | * 55 | * @return mixed 56 | */ 57 | public function searchByKey(string $dotKey){ 58 | 59 | foreach($this->iterator as $v){ 60 | 61 | if($this->getPath() === $dotKey){ 62 | return $v; 63 | } 64 | 65 | } 66 | 67 | return null; 68 | } 69 | 70 | /** 71 | * @param mixed $value 72 | * 73 | * @return array 74 | */ 75 | public function searchByValue($value):array{ 76 | 77 | $matches = []; 78 | 79 | foreach($this->iterator as $v){ 80 | 81 | if($v === $value){ 82 | $matches[$this->getPath()] = $value; 83 | } 84 | 85 | } 86 | 87 | return $matches; 88 | } 89 | 90 | /** 91 | * @param string $dotKey 92 | * 93 | * @return bool 94 | */ 95 | public function isset(string $dotKey):bool{ 96 | 97 | foreach($this->iterator as $v){ 98 | 99 | if($this->getPath() === $dotKey){ 100 | return true; 101 | } 102 | 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * @return string 110 | */ 111 | private function getPath():string{ 112 | return \implode('.', \array_map(function(int $depth):string { 113 | return $this->iterator->getSubIterator($depth)->key(); 114 | }, \range(0, $this->iterator->getDepth()))); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits; 14 | 15 | use Exception, ReflectionClass; 16 | 17 | trait ClassLoader{ 18 | 19 | /** 20 | * Instances an object of $class/$type with an arbitrary number of $params 21 | * 22 | * @param string $class class FQCN 23 | * @param string $type class/parent/interface FQCN 24 | * 25 | * @param mixed $params [optional] the following arguments will be passed to the $class constructor 26 | * 27 | * @return mixed of type $type 28 | * @throws \Exception 29 | */ 30 | public function loadClass(string $class, string $type = null, ...$params){ 31 | $type = $type ?? $class; 32 | 33 | try{ 34 | $reflectionClass = new ReflectionClass($class); 35 | $reflectionType = new ReflectionClass($type); 36 | } 37 | catch(Exception $e){ 38 | throw new TraitException('ClassLoader: '.$e->getMessage()); 39 | } 40 | 41 | 42 | if($reflectionType->isTrait()){ 43 | throw new TraitException($class.' cannot be an instance of trait '.$type); 44 | } 45 | 46 | if($reflectionClass->isAbstract()){ 47 | throw new TraitException('cannot instance abstract class '.$class); 48 | } 49 | 50 | if($reflectionClass->isTrait()){ 51 | throw new TraitException('cannot instance trait '.$class); 52 | } 53 | 54 | if($class !== $type){ 55 | 56 | if($reflectionType->isInterface() && !$reflectionClass->implementsInterface($type)){ 57 | throw new TraitException($class.' does not implement '.$type); 58 | } 59 | elseif(!$reflectionClass->isSubclassOf($type)) { 60 | throw new TraitException($class.' does not inherit '.$type); 61 | } 62 | 63 | } 64 | 65 | try{ 66 | $object = $reflectionClass->newInstanceArgs($params); 67 | 68 | if(!$object instanceof $type){ 69 | throw new TraitException('how did u even get here?'); // @codeCoverageIgnore 70 | } 71 | 72 | return $object; 73 | } 74 | // @codeCoverageIgnoreStart 75 | // here be dragons 76 | catch(Exception $e){ 77 | throw new TraitException('ClassLoader: '.$e->getMessage()); 78 | } 79 | // @codeCoverageIgnoreEnd 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/Enumerable.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits; 14 | 15 | /** 16 | * @link http://api.prototypejs.org/language/Enumerable/ 17 | */ 18 | trait Enumerable{ 19 | 20 | /** 21 | * @var array 22 | */ 23 | protected $array = []; 24 | 25 | /** 26 | * @var int 27 | */ 28 | protected $offset = 0; 29 | 30 | /** 31 | * @link http://api.prototypejs.org/language/Enumerable/prototype/toArray/ 32 | * 33 | * @return array 34 | * 35 | * @codeCoverageIgnore 36 | */ 37 | public function __toArray():array { 38 | return $this->array; 39 | } 40 | 41 | /** 42 | * @link http://api.prototypejs.org/language/Enumerable/prototype/each/ 43 | * 44 | * @param callable $callback 45 | * 46 | * @return $this 47 | */ 48 | public function __each($callback){ 49 | $this->__map($callback); 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * @link http://api.prototypejs.org/language/Enumerable/prototype/collect/ 56 | * @link http://api.prototypejs.org/language/Enumerable/prototype/map/ 57 | * 58 | * @param callable $callback 59 | * 60 | * @return array 61 | * @throws \chillerlan\Traits\TraitException 62 | */ 63 | public function __map($callback):array { 64 | 65 | if(!\is_callable($callback)){ 66 | throw new TraitException('invalid callback'); 67 | } 68 | 69 | $return = []; 70 | 71 | foreach($this->array as $index => $element){ 72 | $return[$index] = \call_user_func_array($callback, [$element, $index]); 73 | } 74 | 75 | return $return; 76 | } 77 | 78 | /** 79 | * @link http://api.prototypejs.org/language/Array/prototype/reverse/ 80 | * 81 | * @return \chillerlan\Traits\EnumerableInterface 82 | */ 83 | public function __reverse():EnumerableInterface{ 84 | $this->array = \array_reverse($this->array); 85 | $this->offset = 0; 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * @return mixed 92 | */ 93 | public function __first(){ 94 | return $this->array[0] ?? null; 95 | } 96 | 97 | /** 98 | * @return mixed 99 | */ 100 | public function __last(){ 101 | return $this->array[\count($this->array) - 1] ?? null; 102 | } 103 | 104 | /** 105 | * @return \chillerlan\Traits\EnumerableInterface 106 | */ 107 | public function __clear():EnumerableInterface{ 108 | $this->array = []; 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * @link http://api.prototypejs.org/language/Array/prototype/inspect/ 115 | * 116 | * @return string 117 | */ 118 | public function __inspect():string { 119 | return \print_r($this->array, true); 120 | } 121 | 122 | /** 123 | * @link http://api.prototypejs.org/language/Enumerable/prototype/findAll/ 124 | * 125 | * @param callable $callback 126 | * 127 | * @return array 128 | * @throws \chillerlan\Traits\TraitException 129 | */ 130 | public function __findAll($callback):array{ 131 | 132 | if(!\is_callable($callback)){ 133 | throw new TraitException('invalid callback'); 134 | } 135 | 136 | $return = []; 137 | 138 | foreach($this->array as $index => $element){ 139 | 140 | if(\call_user_func_array($callback, [$element, $index]) === true){ 141 | $return[] = $element; 142 | } 143 | 144 | } 145 | 146 | return $return; 147 | } 148 | 149 | /** 150 | * @link http://api.prototypejs.org/language/Enumerable/prototype/reject/ 151 | * 152 | * @param callable $callback 153 | * 154 | * @return array 155 | * @throws \chillerlan\Traits\TraitException 156 | */ 157 | public function __reject($callback):array{ 158 | 159 | if(!\is_callable($callback)){ 160 | throw new TraitException('invalid callback'); 161 | } 162 | 163 | $return = []; 164 | 165 | foreach($this->array as $index => $element){ 166 | 167 | if(\call_user_func_array($callback, [$element, $index]) !== true){ 168 | $return[] = $element; 169 | } 170 | 171 | } 172 | 173 | return $return; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/EnumerableInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2018 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits; 14 | 15 | interface EnumerableInterface{ 16 | 17 | /** 18 | * @return array 19 | */ 20 | public function __toArray():array; 21 | 22 | /** 23 | * @param callable $callback 24 | * 25 | * @return mixed 26 | */ 27 | public function __each($callback); 28 | 29 | /** 30 | * @param callable $callback 31 | * 32 | * @return array 33 | */ 34 | public function __map($callback):array; 35 | 36 | /** 37 | * @return \chillerlan\Traits\EnumerableInterface 38 | */ 39 | public function __reverse():EnumerableInterface; 40 | 41 | /** 42 | * @return mixed 43 | */ 44 | public function __first(); 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function __last(); 50 | 51 | /** 52 | * @return \chillerlan\Traits\EnumerableInterface 53 | */ 54 | public function __clear():EnumerableInterface; 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function __inspect():string; 60 | 61 | /** 62 | * @param callable $callback 63 | * 64 | * @return array 65 | */ 66 | public function __findAll($callback):array; 67 | 68 | /** 69 | * @param callable $callback 70 | * 71 | * @return array 72 | */ 73 | public function __reject($callback):array; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/Interfaces/ArrayAccessTrait.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\Interfaces; 14 | 15 | /** 16 | * @implements ArrayAccess 17 | * 18 | * @link http://php.net/manual/class.arrayaccess.php 19 | */ 20 | trait ArrayAccessTrait{ 21 | 22 | /** 23 | * @var array 24 | */ 25 | protected $array = []; 26 | 27 | /** 28 | * @var int 29 | */ 30 | protected $offset = 0; 31 | 32 | /** 33 | * @link http://php.net/manual/arrayaccess.offsetexists.php 34 | * @inheritdoc 35 | */ 36 | public function offsetExists($offset):bool{ 37 | return \array_key_exists($offset, $this->array); 38 | } 39 | 40 | /** 41 | * @link http://php.net/manual/arrayaccess.offsetget.php 42 | * @inheritdoc 43 | */ 44 | public function offsetGet($offset){ 45 | return $this->array[$offset] ?? null; 46 | } 47 | 48 | /** 49 | * @link http://php.net/manual/arrayaccess.offsetset.php 50 | * @inheritdoc 51 | */ 52 | public function offsetSet($offset, $value):void{ 53 | 54 | $offset !== null 55 | ? $this->array[$offset] = $value 56 | : $this->array[] = $value; 57 | } 58 | 59 | /** 60 | * @link http://php.net/manual/arrayaccess.offsetunset.php 61 | * @inheritdoc 62 | */ 63 | public function offsetUnset($offset):void{ 64 | unset($this->array[$offset]); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Interfaces/IteratorTrait.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\Interfaces; 14 | 15 | /** 16 | * @extends \Traversable 17 | * @implements \Iterator 18 | * 19 | * @link http://php.net/manual/class.traversable.php 20 | */ 21 | trait IteratorTrait{ 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected $array = []; 27 | 28 | /** 29 | * @var int 30 | */ 31 | protected $offset = 0; 32 | 33 | /** 34 | * @link http://php.net/manual/iterator.current.php 35 | * @inheritdoc 36 | */ 37 | public function current(){ 38 | return $this->array[$this->offset] ?? null; 39 | } 40 | 41 | /** 42 | * @link http://php.net/manual/iterator.next.php 43 | * @inheritdoc 44 | */ 45 | public function next():void{ 46 | $this->offset++; 47 | } 48 | 49 | /** 50 | * @link http://php.net/manual/iterator.key.php 51 | * @inheritdoc 52 | */ 53 | public function key(){ 54 | return $this->offset; 55 | } 56 | 57 | /** 58 | * @link http://php.net/manual/iterator.valid.php 59 | * @inheritdoc 60 | */ 61 | public function valid():bool{ 62 | return \array_key_exists($this->offset, $this->array); 63 | } 64 | 65 | /** 66 | * @link http://php.net/manual/iterator.rewind.php 67 | * @inheritdoc 68 | */ 69 | public function rewind():void{ 70 | $this->offset = 0; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/Magic.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits; 14 | 15 | /** 16 | * A Container that turns methods into magic properties 17 | */ 18 | trait Magic{ 19 | 20 | /** 21 | * @param string $name 22 | * 23 | * @return mixed|null 24 | */ 25 | public function __get(string $name){ 26 | return $this->get($name); 27 | } 28 | 29 | /** 30 | * @param string $name 31 | * @param mixed $value 32 | * 33 | * @return void 34 | */ 35 | public function __set(string $name, $value):void{ 36 | $this->set($name, $value); 37 | } 38 | 39 | /** 40 | * @param string $name 41 | * 42 | * @return mixed|null 43 | */ 44 | private function get(string $name){ 45 | $method = 'magic_get_'.$name; 46 | 47 | return \method_exists($this, $method) ? $this->$method() : null; 48 | } 49 | 50 | /** 51 | * @param string $name 52 | * @param $value 53 | * 54 | * @return void 55 | */ 56 | private function set(string $name, $value):void{ 57 | $method = 'magic_set_'.$name; 58 | 59 | if(\method_exists($this, $method)){ 60 | $this->$method($value); 61 | } 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/SPL/CountableTrait.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\SPL; 14 | 15 | /** 16 | * @implements \Countable 17 | * 18 | * @link http://php.net/manual/class.countable.php 19 | */ 20 | trait CountableTrait{ 21 | 22 | /** 23 | * @var array 24 | */ 25 | protected $array = []; 26 | 27 | /** 28 | * @link http://php.net/manual/countable.count.php 29 | * @inheritdoc 30 | */ 31 | public function count():int{ 32 | return \count($this->array); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/SPL/SeekableIteratorTrait.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits\SPL; 14 | 15 | use chillerlan\Traits\Interfaces\IteratorTrait; 16 | use OutOfBoundsException; 17 | 18 | /** 19 | * @extends \Iterator 20 | * @implements \SeekableIterator 21 | * 22 | * @link http://php.net/manual/class.seekableiterator.php 23 | */ 24 | trait SeekableIteratorTrait{ 25 | use IteratorTrait; 26 | 27 | /** 28 | * @link http://php.net/manual/seekableiterator.seek.php 29 | * @inheritdoc 30 | */ 31 | public function seek($pos):void{ 32 | $this->rewind(); 33 | 34 | for( ; $this->offset < $pos; ){ 35 | 36 | if(!\next($this->array)) { 37 | throw new OutOfBoundsException('invalid seek position: '.$pos); 38 | } 39 | 40 | $this->offset++; 41 | } 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/TraitException.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\Traits; 14 | 15 | class TraitException extends \Exception{} 16 | -------------------------------------------------------------------------------- /tests/ArrayTrait/ArrayTraitTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2017 Smiley 9 | * @license MIT 10 | */ 11 | 12 | namespace chillerlan\TraitTest\ArrayTrait; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | 16 | class SearchableArrayTest extends TestCase{ 17 | 18 | public function testSearchableArray(){ 19 | // https://api.guildwars2.com/v2/continents/2/floors/1 20 | $array = new TestArrayClass(json_decode(file_get_contents(__DIR__.'/gw2api-floors-2-1.json'), true)); 21 | 22 | $k = 'regions.7.maps.38.points_of_interest.990.name'; 23 | $v = 'Stonemist Keep'; 24 | 25 | // coverage - first level 26 | $this->assertSame('World vs. World', $array->get('regions')[7]['name']); 27 | $this->assertTrue($array->in('regions')); 28 | 29 | $this->assertSame($v, $array->get($k)); 30 | $this->assertSame($v, $array->searchByKey($k)); // RecursiveIterator 31 | $this->assertSame([$k => $v], $array->searchByValue($v)); // RecursiveIterator 32 | 33 | $this->assertNull($array->get($k.'.foo')); 34 | $this->assertNull($array->searchByKey($k.'.foo')); // RecursiveIterator 35 | 36 | $this->assertTrue($array->in($k)); 37 | $this->assertTrue($array->isset($k)); // RecursiveIterator 38 | 39 | $this->assertFalse($array->in($k.'.foo')); 40 | $this->assertFalse($array->isset($k.'.foo')); // RecursiveIterator 41 | 42 | $array->set($k, 'foo'); 43 | $this->assertSame('foo', $array->get($k)); 44 | 45 | } 46 | 47 | public function testSet(){ 48 | $array = new TestArrayClass; 49 | 50 | $this->assertFalse($array->set('', null)->in('nothing')); 51 | 52 | $array->set('foo.bar', 'whatever'); 53 | 54 | $this->assertSame('whatever', $array->get('foo.bar')); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tests/ArrayTrait/ByteArrayTest.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest; 14 | 15 | use chillerlan\Traits\ArrayHelpers\ByteArrayDispenser; 16 | use chillerlan\Traits\TraitException; 17 | use PHPUnit\Framework\TestCase; 18 | 19 | class ByteArrayTest extends TestCase{ 20 | 21 | /** 22 | * @var \chillerlan\Traits\ArrayHelpers\ByteArrayDispenser 23 | */ 24 | protected $arrayDispenser; 25 | 26 | protected function setUp():void{ 27 | $this->arrayDispenser = new ByteArrayDispenser; 28 | } 29 | 30 | public function testConvert(){ 31 | $hex_input = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'; 32 | 33 | $expected_bin = '0000000000000001000000100000001100000100000001010000011000000111000010000000100100001010000010110000110000001101000011100000111100010000000100010001001000010011000101000001010100010110000101110001100000011001000110100001101100011100000111010001111000011111'; 34 | $expected_b64 = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='; 35 | $expected_json = '[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]'; 36 | $expected_arr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]; 37 | $expected_str = hex2bin($hex_input); 38 | 39 | $this->assertSame($expected_bin, $this->arrayDispenser->fromHex($hex_input)->toBin()); 40 | $this->assertSame($expected_b64, $this->arrayDispenser->fromHex($hex_input)->toBase64()); 41 | $this->assertSame($expected_json, $this->arrayDispenser->fromHex($hex_input)->toJSON()); 42 | $this->assertSame($expected_arr, $this->arrayDispenser->fromHex($hex_input)->toArray()); 43 | $this->assertSame($expected_str, $this->arrayDispenser->fromHex($hex_input)->toString()); 44 | 45 | $this->assertSame($hex_input, $this->arrayDispenser->fromBin($expected_bin)->toHex()); 46 | $this->assertSame($hex_input, $this->arrayDispenser->fromBase64($expected_b64)->toHex()); 47 | $this->assertSame($hex_input, $this->arrayDispenser->fromJSON($expected_json)->toHex()); 48 | $this->assertSame($hex_input, $this->arrayDispenser->fromArray($expected_arr)->toHex()); 49 | $this->assertSame($hex_input, $this->arrayDispenser->fromString($expected_str)->toHex()); 50 | } 51 | 52 | public function testFromfromIntSize(){ 53 | $this->assertSame([null,null,null,null], $this->arrayDispenser->fromIntSize(4)->toArray()); 54 | } 55 | 56 | public function testFromArrayFill(){ 57 | $this->assertSame([42,42,42], $this->arrayDispenser->fromArrayFill(3, 42)->toArray()); 58 | $this->assertSame([[],[],[]], $this->arrayDispenser->fromArrayFill(3, [])->toArray()); 59 | } 60 | 61 | public function guessFromData(){ 62 | return [ 63 | 'hex' => ['000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'], 64 | 'bin' => ['0000000000000001000000100000001100000100000001010000011000000111000010000000100100001010000010110000110000001101000011100000111100010000000100010001001000010011000101000001010100010110000101110001100000011001000110100001101100011100000111010001111000011111'], 65 | 'base64' => ['AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='], 66 | 'json' => ['[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]'], 67 | 'array' => [[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]], 68 | 'string' => [hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f')], 69 | 'ByteArray' => [(new ByteArrayDispenser())->fromHex('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f')], 70 | ]; 71 | } 72 | 73 | /** 74 | * @dataProvider guessFromData 75 | */ 76 | public function testGuessFrom($from){ 77 | $this->assertSame('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', $this->arrayDispenser->guessFrom($from)->toHex()); 78 | } 79 | 80 | public function testGuessFromInvalidData(){ 81 | $this->expectException(TraitException::class); 82 | $this->expectExceptionMessage('invalid input'); 83 | 84 | $this->arrayDispenser->guessFrom(new \stdClass); 85 | } 86 | 87 | public function testInvalidIntException(){ 88 | $this->expectException(TraitException::class); 89 | $this->expectExceptionMessage('invalid size'); 90 | 91 | $this->arrayDispenser->fromIntSize(-1); 92 | } 93 | 94 | public function testInvalidRangeException(){ 95 | $this->expectException(TraitException::class); 96 | $this->expectExceptionMessage('invalid length'); 97 | 98 | $this->arrayDispenser->fromArrayFill(-1, 1); 99 | } 100 | 101 | public function testInvalidHexException(){ 102 | $this->expectException(TraitException::class); 103 | $this->expectExceptionMessage('invalid hex string'); 104 | 105 | $this->arrayDispenser->fromHex('foo'); 106 | } 107 | 108 | public function testInvalidJSONException(){ 109 | $this->expectException(TraitException::class); 110 | $this->expectExceptionMessage('invalid JSON array'); 111 | 112 | $this->arrayDispenser->fromJSON('{}'); 113 | } 114 | 115 | public function testInvalidBase64Exception(){ 116 | $this->expectException(TraitException::class); 117 | $this->expectExceptionMessage('invalid base64 string'); 118 | 119 | $this->arrayDispenser->fromBase64('\\'); 120 | } 121 | 122 | public function testInvalidBinException(){ 123 | $this->expectException(TraitException::class); 124 | $this->expectExceptionMessage('invalid binary string'); 125 | 126 | $this->arrayDispenser->fromBin('2'); 127 | } 128 | 129 | public function testCopyFrom(){ 130 | $h1 = '000102030405060708090a0b0c0d0e0f'; 131 | $h2 = '101112131415161718191a1b1c1d1e1f'; 132 | 133 | $b2 = $this->arrayDispenser->fromHex($h2); 134 | 135 | // note the "referenced" behaviour of \SplFixedArray 136 | $this->assertSame($h1.$h2, $this->arrayDispenser->fromHex($h1)->copyFrom($b2)->toHex()); 137 | $this->assertSame($h2, $this->arrayDispenser->fromHex($h1)->copyFrom($b2, null, 0)->toHex()); 138 | $this->assertSame($h2, $this->arrayDispenser->fromHex($h1)->copyFrom($b2, 16, 0)->toHex()); 139 | $this->assertSame('101112131415161708090a0b0c0d0e0f', $this->arrayDispenser->fromHex($h1)->copyFrom($b2, 8, 0)->toHex()); 140 | } 141 | 142 | public function testSlice(){ 143 | $h1 = '000102030405060708090a0b0c0d0e0f'; 144 | $h2 = '101112131415161718191a1b1c1d1e1f'; 145 | 146 | $b = $this->arrayDispenser->fromHex($h1.$h2); 147 | 148 | $this->assertSame($h1, $b->slice(0, 16)->toHex()); 149 | $this->assertSame($h2, $b->slice(16, 16)->toHex()); 150 | } 151 | 152 | public function testEqual(){ 153 | $h1 = '000102030405060708090a0b0c0d0e0f'; 154 | 155 | $b = $this->arrayDispenser->fromHex($h1); 156 | 157 | $this->assertTrue($b->equal($this->arrayDispenser->fromHex($h1))); 158 | $this->assertTrue($b->equal($this->arrayDispenser->fromArray([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]))); 159 | 160 | $this->assertFalse($b->equal($this->arrayDispenser->fromHex('000102030405060708090a0b0c0d0e0e'))); 161 | $this->assertFalse($b->equal($this->arrayDispenser->fromHex('000102030405060708090a0b0c0d0e'))); 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /tests/ArrayTrait/TestArrayClass.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2017 Smiley 9 | * @license MIT 10 | */ 11 | 12 | namespace chillerlan\TraitTest\ArrayTrait; 13 | 14 | use chillerlan\Traits\ArrayHelpers\SearchableArray; 15 | 16 | /** 17 | * Class TestArrayClass 18 | */ 19 | class TestArrayClass{ 20 | use SearchableArray; 21 | } 22 | -------------------------------------------------------------------------------- /tests/ArrayTrait/gw2api-floors-2-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture_dims": [ 3 | 16384, 4 | 16384 5 | ], 6 | "clamped_view": [ 7 | [ 8 | 0, 9 | 4094 10 | ], 11 | [ 12 | 16382, 13 | 16382 14 | ] 15 | ], 16 | "regions": { 17 | "7": { 18 | "name": "World vs. World", 19 | "label_coord": [ 20 | 19840, 21 | 13568 22 | ], 23 | "continent_rect": [ 24 | [ 25 | 17664, 26 | 8192 27 | ], 28 | [ 29 | 23808, 30 | 21376 31 | ] 32 | ], 33 | "maps": { 34 | "38": { 35 | "name": "Eternal Battlegrounds", 36 | "min_level": 80, 37 | "max_level": 80, 38 | "default_floor": 3, 39 | "label_coord": [ 40 | 10700, 41 | 14800 42 | ], 43 | "map_rect": [ 44 | [ 45 | -36864, 46 | -36864 47 | ], 48 | [ 49 | 36864, 50 | 36864 51 | ] 52 | ], 53 | "continent_rect": [ 54 | [ 55 | 8958, 56 | 12798 57 | ], 58 | [ 59 | 12030, 60 | 15870 61 | ] 62 | ], 63 | "points_of_interest": { 64 | "990": { 65 | "name": "Stonemist Keep", 66 | "type": "landmark", 67 | "floor": 3, 68 | "coord": [ 69 | 10606.1, 70 | 14549.6 71 | ], 72 | "id": 990, 73 | "chat_link": "[&BN4DAAA=]" 74 | }, 75 | "2293": { 76 | "name": "Valley Emergency Waypoint", 77 | "type": "waypoint", 78 | "floor": 3, 79 | "coord": [ 80 | 11587.2, 81 | 15145.7 82 | ], 83 | "id": 2293, 84 | "chat_link": "[&BPUIAAA=]" 85 | } 86 | }, 87 | "tasks": {}, 88 | "skill_challenges": [ 89 | { 90 | "coord": [ 91 | 11127.8, 92 | 14246.9 93 | ], 94 | "id": "0-225" 95 | }, 96 | { 97 | "coord": [ 98 | 10007.2, 99 | 14220.6 100 | ], 101 | "id": "0-224" 102 | }, 103 | { 104 | "coord": [ 105 | 10604.1, 106 | 14525.8 107 | ], 108 | "id": "0-226" 109 | }, 110 | { 111 | "coord": [ 112 | 10657.2, 113 | 15627.2 114 | ], 115 | "id": "0-223" 116 | } 117 | ], 118 | "sectors": { 119 | "833": { 120 | "name": "Stonemist Castle", 121 | "level": 0, 122 | "coord": [ 123 | 10643.9, 124 | 14563.8 125 | ], 126 | "bounds": [ 127 | [ 128 | 10233.8, 129 | 14851.7 130 | ], 131 | [ 132 | 10120.4, 133 | 14676.4 134 | ], 135 | [ 136 | 10181.3, 137 | 14473.4 138 | ], 139 | [ 140 | 10222.5, 141 | 14191.7 142 | ], 143 | [ 144 | 10475.5, 145 | 14146.4 146 | ], 147 | [ 148 | 10803.8, 149 | 14153.2 150 | ], 151 | [ 152 | 11012.4, 153 | 14218.5 154 | ], 155 | [ 156 | 11057.8, 157 | 14343.8 158 | ], 159 | [ 160 | 11010.2, 161 | 14500.5 162 | ], 163 | [ 164 | 11055.5, 165 | 14788.7 166 | ], 167 | [ 168 | 10950.6, 169 | 14944.6 170 | ], 171 | [ 172 | 10768.4, 173 | 15051.6 174 | ], 175 | [ 176 | 10479.1, 177 | 14988.8 178 | ], 179 | [ 180 | 10233.8, 181 | 14851.7 182 | ] 183 | ], 184 | "id": 833, 185 | "chat_link": "[&BEEDAAA=]" 186 | }, 187 | "1031": { 188 | "name": "Zraith's Beacon", 189 | "level": 0, 190 | "coord": [ 191 | 11511.2, 192 | 13437.4 193 | ], 194 | "bounds": [ 195 | [ 196 | 11534, 197 | 14108.1 198 | ], 199 | [ 200 | 11391.5, 201 | 13918.9 202 | ], 203 | [ 204 | 11353.6, 205 | 13694.5 206 | ], 207 | [ 208 | 11228.3, 209 | 13489.9 210 | ], 211 | [ 212 | 10930.5, 213 | 13126 214 | ], 215 | [ 216 | 11221.5, 217 | 12956.2 218 | ], 219 | [ 220 | 11333.5, 221 | 12877.2 222 | ], 223 | [ 224 | 11443.8, 225 | 12849.3 226 | ], 227 | [ 228 | 11631, 229 | 12948.8 230 | ], 231 | [ 232 | 11718, 233 | 13290.5 234 | ], 235 | [ 236 | 11991.1, 237 | 13477.3 238 | ], 239 | [ 240 | 12079.8, 241 | 13842.5 242 | ], 243 | [ 244 | 11788.6, 245 | 14107.5 246 | ], 247 | [ 248 | 11534, 249 | 14108.1 250 | ] 251 | ], 252 | "id": 1031, 253 | "chat_link": "[&BAcEAAA=]" 254 | } 255 | }, 256 | "adventures": [], 257 | "id": 38, 258 | "mastery_points": [] 259 | } 260 | }, 261 | "id": 7 262 | } 263 | }, 264 | "id": 1 265 | } -------------------------------------------------------------------------------- /tests/ClassLoader/ClassLoaderTraitTest.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\ClassLoader; 14 | 15 | use chillerlan\Traits\ClassLoader; 16 | use chillerlan\Traits\TraitException; 17 | use PHPUnit\Framework\TestCase; 18 | 19 | class ClassLoaderTraitTest extends TestCase{ 20 | use ClassLoader; 21 | 22 | public function testLoadClass1(){ 23 | $this->assertInstanceOf(tClass::class, $this->loadClass(tClass::class)); 24 | $this->assertInstanceOf(tClass::class, $this->loadClass(tClass::class, tClass::class)); 25 | $this->assertInstanceOf(tInterface::class, $this->loadClass(tClass::class, tInterface::class)); 26 | $this->assertInstanceOf(tAbstract::class, $this->loadClass(tClass::class, tAbstract::class)); 27 | } 28 | 29 | public function testLoadClass2(){ 30 | $obj = $this->loadClass(tClass::class, tInterface::class); 31 | $this->assertSame('foo', $obj->test('foo')); 32 | $this->assertSame('bar', $obj->testTrait('bar')); 33 | } 34 | 35 | public function testLoadClass3(){ 36 | $obj = $this->loadClass(tClass::class, tInterface::class, 'whatever'); 37 | 38 | $this->assertSame('whatever', $obj->bar()); 39 | } 40 | 41 | public function testLoadClassExceptionA1(){ 42 | $this->expectException(TraitException::class); 43 | $this->expectExceptionMessage('\whatever\foo does not exist'); 44 | 45 | $this->loadClass('\\whatever\\foo', tInterface::class); 46 | } 47 | 48 | public function testLoadClassExceptionA2(){ 49 | $this->expectException(TraitException::class); 50 | $this->expectExceptionMessage('\whatever\bar does not exist'); 51 | 52 | $this->loadClass(tClass::class, '\\whatever\\bar'); 53 | } 54 | 55 | public function testLoadClassExceptionB(){ 56 | $this->expectException(TraitException::class); 57 | $this->expectExceptionMessage('cannot be an instance of trait'); 58 | 59 | $this->loadClass(tClass::class, tTrait::class); 60 | } 61 | 62 | public function testLoadClassExceptionC(){ 63 | $this->expectException(TraitException::class); 64 | $this->expectExceptionMessage('cannot instance abstract class'); 65 | 66 | $this->loadClass(tAbstract::class, tInterface::class); 67 | } 68 | 69 | public function testLoadClassExceptionD(){ 70 | $this->expectException(TraitException::class); 71 | $this->expectExceptionMessage('cannot instance trait'); 72 | 73 | $this->loadClass(tTrait::class, tInterface::class); 74 | } 75 | 76 | public function testLoadClassExceptionE1(){ 77 | $this->expectException(TraitException::class); 78 | $this->expectExceptionMessage('does not implement'); 79 | 80 | $this->loadClass(tClass::class, tInterface2::class); 81 | } 82 | 83 | public function testLoadClassExceptionE2(){ 84 | $this->expectException(TraitException::class); 85 | $this->expectExceptionMessage('does not inherit'); 86 | 87 | $this->loadClass(tClass::class, \stdClass::class); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /tests/ClassLoader/tAbstract.php: -------------------------------------------------------------------------------- 1 | foo = $foo; 12 | } 13 | 14 | public function bar(){ 15 | return $this->foo; 16 | } 17 | 18 | public function test(string $test):string{ 19 | return $test; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/ClassLoader/tClass.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\Enumerable; 14 | 15 | use chillerlan\Traits\Enumerable; 16 | use chillerlan\Traits\EnumerableInterface; 17 | 18 | class EnumerableContainer implements EnumerableInterface{ 19 | use Enumerable; 20 | 21 | public function __construct(array $data){ 22 | $this->array = $data; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/Enumerable/EnumerableTest.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\Enumerable; 14 | 15 | use chillerlan\Traits\TraitException; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class EnumerableTest extends TestCase{ 19 | 20 | protected $array = [1, 'two', 3, 'four', 5]; 21 | 22 | /** 23 | * @var \chillerlan\TraitTest\Enumerable\EnumerableContainer 24 | */ 25 | protected $enumerable; 26 | 27 | protected function setUp():void{ 28 | $this->enumerable = new EnumerableContainer($this->array); 29 | } 30 | 31 | public function testMap(){ 32 | 33 | $arr = $this->enumerable 34 | ->__map(function($v, $k){ 35 | return $v; 36 | }) 37 | ; 38 | 39 | $this->assertSame($arr, $this->enumerable->__toArray()); 40 | } 41 | 42 | public function testReverse(){ 43 | $arr = $this->enumerable 44 | ->__reverse() 45 | ->__toArray() 46 | ; 47 | 48 | $this->assertSame(array_reverse($this->array), $arr); 49 | } 50 | 51 | public function testEach(){ 52 | 53 | $this->enumerable 54 | ->__each(function($v, $k){ 55 | $this->assertSame($this->array[$k], $v); 56 | }) 57 | ; 58 | 59 | } 60 | 61 | public function testFirst(){ 62 | $this->assertSame(1, $this->enumerable->__first()); 63 | } 64 | 65 | public function testLast(){ 66 | $this->assertSame(5, $this->enumerable->__last()); 67 | } 68 | 69 | public function testClear(){ 70 | 71 | $arr = $this->enumerable 72 | ->__clear() 73 | ->__toArray() 74 | ; 75 | 76 | $this->assertSame([], $arr); 77 | } 78 | 79 | public function testInspect(){ 80 | 81 | $expected = 'Array 82 | ( 83 | [0] => 1 84 | [1] => two 85 | [2] => 3 86 | [3] => four 87 | [4] => 5 88 | ) 89 | '; 90 | 91 | $expected = str_replace("\r", '', $expected); // will we ever settle on ONE line ending that is \n??? 92 | 93 | $this->assertSame($expected, $this->enumerable->__inspect()); 94 | } 95 | 96 | public function testFindAll(){ 97 | $enum = new EnumerableContainer([1, 'two', 3, 'four', 5]); 98 | 99 | $arr = $enum->__findAll(function($e, $i){ 100 | return is_string($e); 101 | }); 102 | 103 | $this->assertSame(['two', 'four'], $arr); 104 | } 105 | 106 | public function testReject(){ 107 | $enum = new EnumerableContainer([1, 'two', 3, 'four', 5]); 108 | 109 | $arr = $enum->__reject(function($e, $i){ 110 | return is_string($e); 111 | }); 112 | 113 | $this->assertSame([1, 3, 5], $arr); 114 | } 115 | 116 | public function testMapInvalidCallback(){ 117 | $this->expectException(TraitException::class); 118 | $this->expectExceptionMessage('invalid callback'); 119 | 120 | (new EnumerableContainer([]))->__map('foo'); 121 | } 122 | 123 | public function testFindAllInvalidCallback(){ 124 | $this->expectException(TraitException::class); 125 | $this->expectExceptionMessage('invalid callback'); 126 | 127 | (new EnumerableContainer([]))->__findAll('foo'); 128 | } 129 | 130 | public function testRejectInvalidCallback(){ 131 | $this->expectException(TraitException::class); 132 | $this->expectExceptionMessage('invalid callback'); 133 | 134 | (new EnumerableContainer([]))->__reject('foo'); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /tests/Interfaces/ArrayAccessTest.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\Interfaces; 14 | 15 | use OutOfBoundsException; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class ArrayAccessTest extends TestCase{ 19 | 20 | public function testInstance(){ 21 | $x = new TestArrayAccessContainer; 22 | 23 | foreach(range(0, 68) as $k){ 24 | $x[$k] = ['id' => $k, 'hash' => md5($k)]; 25 | } 26 | 27 | $x[] = ['id' => 69, 'hash' => md5(69)]; // coverage 28 | 29 | $this->assertCount(70, $x); // \Countable 30 | 31 | $x->seek(69); 32 | 33 | $this->assertSame(md5(69), $x->current()['hash']); 34 | 35 | foreach($x as $k => $v){ // \Iterator 36 | if(isset($x[$k])){ // coverage 37 | $this->assertSame(md5($k), $v['hash']); 38 | $this->assertSame($v, $x[$k]); // coverage 39 | unset($x[$k]); // coverage 40 | } 41 | } 42 | 43 | $this->assertCount(0, $x); 44 | } 45 | 46 | public function testSeekInvalidPos(){ 47 | $this->expectException(OutOfBoundsException::class); 48 | $this->expectExceptionMessage('invalid seek position: 69'); 49 | 50 | $x = new TestArrayAccessContainer; 51 | $x->seek(69); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Interfaces/TestArrayAccessContainer.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\Interfaces; 14 | 15 | use chillerlan\Traits\Interfaces\ArrayAccessTrait; 16 | use chillerlan\Traits\SPL\CountableTrait; 17 | use chillerlan\Traits\SPL\SeekableIteratorTrait; 18 | 19 | class TestArrayAccessContainer implements \ArrayAccess, \Countable, \SeekableIterator{ 20 | use ArrayAccessTrait, CountableTrait, SeekableIteratorTrait; 21 | } 22 | -------------------------------------------------------------------------------- /tests/Magic/MagicContainer.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\Magic; 14 | 15 | use chillerlan\Traits\Magic; 16 | 17 | /** 18 | * @property $test 19 | */ 20 | class MagicContainer{ 21 | use Magic; 22 | 23 | protected $foo; 24 | 25 | protected function magic_get_test(){ 26 | return 'Value: '.$this->foo; 27 | } 28 | 29 | protected function magic_set_test($value){ 30 | $this->foo = $value.'bar'; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tests/Magic/MagicTest.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2017 Smiley 10 | * @license MIT 11 | */ 12 | 13 | namespace chillerlan\TraitTest\Magic; 14 | 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class MagicTest extends TestCase{ 18 | 19 | public function testMagic(){ 20 | $magic = new MagicContainer; 21 | 22 | $magic->test = 'foo'; 23 | 24 | $this->assertSame('Value: foobar', $magic->test); 25 | $this->assertnull($magic->foo); 26 | } 27 | } 28 | --------------------------------------------------------------------------------