├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── AbstractOptions.php ├── ArrayObject.php ├── ArraySerializableInterface.php ├── ArrayStack.php ├── ArrayUtils.php ├── ArrayUtils ├── MergeRemoveKey.php ├── MergeReplaceKey.php └── MergeReplaceKeyInterface.php ├── ConsoleHelper.php ├── DispatchableInterface.php ├── ErrorHandler.php ├── Exception ├── BadMethodCallException.php ├── DomainException.php ├── ExceptionInterface.php ├── ExtensionNotLoadedException.php ├── InvalidArgumentException.php ├── LogicException.php └── RuntimeException.php ├── FastPriorityQueue.php ├── Glob.php ├── Guard ├── AllGuardsTrait.php ├── ArrayOrTraversableGuardTrait.php ├── EmptyGuardTrait.php └── NullGuardTrait.php ├── InitializableInterface.php ├── JsonSerializable.php ├── Message.php ├── MessageInterface.php ├── ParameterObjectInterface.php ├── Parameters.php ├── ParametersInterface.php ├── PriorityList.php ├── PriorityQueue.php ├── Request.php ├── RequestInterface.php ├── Response.php ├── ResponseInterface.php ├── SplPriorityQueue.php ├── SplQueue.php ├── SplStack.php ├── StringUtils.php └── StringWrapper ├── AbstractStringWrapper.php ├── Iconv.php ├── Intl.php ├── MbString.php ├── Native.php └── StringWrapperInterface.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 3.2.2 - TBD 6 | 7 | ### Added 8 | 9 | - [#96](https://github.com/zendframework/zend-stdlib/pull/96) Added PHP 7.3 support 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 3.2.1 - 2018-08-28 28 | 29 | ### Added 30 | 31 | - Nothing. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - [#92](https://github.com/zendframework/zend-stdlib/pull/92) fixes serialization of `SplPriorityQueue` by ensuring its `$serial` 48 | property is also serialized. 49 | 50 | - [#91](https://github.com/zendframework/zend-stdlib/pull/91) fixes behavior in the `ArrayObject` implementation that was not 51 | compatible with PHP 7.3. 52 | 53 | ## 3.2.0 - 2018-04-30 54 | 55 | ### Added 56 | 57 | - [#87](https://github.com/zendframework/zend-stdlib/pull/87) adds support for PHP 7.2. 58 | 59 | ### Changed 60 | 61 | - Nothing. 62 | 63 | ### Deprecated 64 | 65 | - Nothing. 66 | 67 | ### Removed 68 | 69 | - [#87](https://github.com/zendframework/zend-stdlib/pull/87) removes support for HHVM. 70 | 71 | ### Fixed 72 | 73 | - Nothing. 74 | 75 | ## 3.1.1 - 2018-04-12 76 | 77 | ### Added 78 | 79 | - Nothing. 80 | 81 | ### Changed 82 | 83 | - [#67](https://github.com/zendframework/zend-stdlib/pull/67) changes the typehint of the `$content` property 84 | of the `Message` class to indicate it is a string. All known implementations 85 | already assumed this. 86 | 87 | ### Deprecated 88 | 89 | - Nothing. 90 | 91 | ### Removed 92 | 93 | - Nothing. 94 | 95 | ### Fixed 96 | 97 | - [#60](https://github.com/zendframework/zend-stdlib/pull/60) fixes an issue whereby calling `remove()` would 98 | incorrectly re-calculate the maximum priority stored in the queue. 99 | 100 | - [#60](https://github.com/zendframework/zend-stdlib/pull/60) fixes an infinite loop condition that can occur when 101 | inserting an item at 0 priority. 102 | 103 | ## 3.1.0 - 2016-09-13 104 | 105 | ### Added 106 | 107 | - [#63](https://github.com/zendframework/zend-stdlib/pull/63) adds a new 108 | `Zend\Stdlib\ConsoleHelper` class, providing minimal support for writing 109 | output to `STDOUT` and `STDERR`, with optional colorization, when the console 110 | supports that feature. 111 | 112 | ### Deprecated 113 | 114 | - [#38](https://github.com/zendframework/zend-stdlib/pull/38) deprecates 115 | `Zend\Stdlib\JsonSerializable`, as all supported version of PHP now support 116 | it. 117 | 118 | ### Removed 119 | 120 | - Nothing. 121 | 122 | ### Fixed 123 | 124 | - Nothing. 125 | 126 | ## 3.0.1 - 2016-04-12 127 | 128 | ### Added 129 | 130 | - Nothing. 131 | 132 | ### Deprecated 133 | 134 | - Nothing. 135 | 136 | ### Removed 137 | 138 | - Nothing. 139 | 140 | ### Fixed 141 | 142 | - [#59](https://github.com/zendframework/zend-stdlib/pull/59) fixes a notice 143 | when defining the `Zend\Json\Json::GLOB_BRACE` constant on systems using 144 | non-gcc glob implementations. 145 | 146 | ## 3.0.0 - 2016-02-03 147 | 148 | ### Added 149 | 150 | - [#51](https://github.com/zendframework/zend-stdlib/pull/51) adds PHP 7 as a 151 | supported PHP version. 152 | - [#51](https://github.com/zendframework/zend-stdlib/pull/51) adds a migration 153 | document from v2 to v3. Hint: if you use hydrators, you need to be using 154 | zend-hydrator instead! 155 | - [#51](https://github.com/zendframework/zend-stdlib/pull/51) adds automated 156 | documentation builds to gh-pages. 157 | 158 | ### Deprecated 159 | 160 | - Nothing. 161 | 162 | ### Removed 163 | 164 | - [#33](https://github.com/zendframework/zend-stdlib/pull/33) - removed 165 | deprecated classes 166 | - *All Hydrator classes* see #22. 167 | - `Zend\Stdlib\CallbackHandler` see #35 168 | - [#37](https://github.com/zendframework/zend-stdlib/pull/37) - removed 169 | deprecated classes and polyfills: 170 | - `Zend\Stdlib\DateTime`; this had been deprecated since 2.5, and only 171 | existed as a polyfill for the `createFromISO8601()` support, now standard 172 | in all PHP versions we support. 173 | - `Zend\Stdlib\Exception\InvalidCallbackException`, which was unused since #33. 174 | - `Zend\Stdlib\Guard\GuardUtils`, which duplicated `Zend\Stdlib\Guard\AllGuardsTrait` 175 | to allow usage with pre-PHP 5.4 versions. 176 | - `src/compatibility/autoload.php`, which has been dprecated since 2.5. 177 | - [#37](https://github.com/zendframework/zend-stdlib/pull/37) - removed 178 | unneeded dependencies: 179 | - zend-config (used only in testing ArrayUtils, and the test was redundant) 180 | - zend-serializer (no longer used) 181 | - [#51](https://github.com/zendframework/zend-stdlib/pull/51) removes the 182 | documentation for hydrators, as those are part of the zend-hydrator 183 | component. 184 | 185 | ### Fixed 186 | 187 | - Nothing. 188 | 189 | ## 2.7.4 - 2015-10-15 190 | 191 | ### Added 192 | 193 | - Nothing. 194 | 195 | ### Deprecated 196 | 197 | - [#35](https://github.com/zendframework/zend-stdlib/pull/35) deprecates 198 | `Zend\Stdlib\CallbackHandler`, as the one component that used it, 199 | zend-eventmanager, will no longer depend on it starting in v3. 200 | 201 | ### Removed 202 | 203 | - Nothing. 204 | 205 | ### Fixed 206 | 207 | - Nothing. 208 | 209 | ## 2.7.3 - 2015-09-24 210 | 211 | ### Added 212 | 213 | - Nothing. 214 | 215 | ### Deprecated 216 | 217 | - Nothing. 218 | 219 | ### Removed 220 | 221 | - Nothing. 222 | 223 | ### Fixed 224 | 225 | - [#27](https://github.com/zendframework/zend-stdlib/pull/27) fixes a race 226 | condition in the `FastPriorityQueue::remove()` logic that occurs when removing 227 | items iteratively from the same priority of a queue. 228 | 229 | ## 2.7.2 - 2015-09-23 230 | 231 | ### Added 232 | 233 | - Nothing. 234 | 235 | ### Deprecated 236 | 237 | - Nothing. 238 | 239 | ### Removed 240 | 241 | - Nothing. 242 | 243 | ### Fixed 244 | 245 | - [#26](https://github.com/zendframework/zend-stdlib/pull/26) fixes a subtle 246 | inheritance issue with deprecation in the hydrators, and updates the 247 | `HydratorInterface` to also extend the zend-hydrator `HydratorInterface` to 248 | ensure LSP is preserved. 249 | 250 | ## 2.7.1 - 2015-09-22 251 | 252 | ### Added 253 | 254 | - Nothing. 255 | 256 | ### Deprecated 257 | 258 | - Nothing. 259 | 260 | ### Removed 261 | 262 | - Nothing. 263 | 264 | ### Fixed 265 | 266 | - [#24](https://github.com/zendframework/zend-stdlib/pull/24) fixes an import in 267 | `FastPriorityQueue` to alias `SplPriorityQueue` in order to disambiguate with 268 | the local override present in the component. 269 | 270 | ## 2.7.0 - 2015-09-22 271 | 272 | ### Added 273 | 274 | - [#19](https://github.com/zendframework/zend-stdlib/pull/19) adds a new 275 | `FastPriorityQueue` implementation. It follows the same signature as 276 | `SplPriorityQueue`, but uses a performance-optimized algorithm: 277 | 278 | - inserts are 2x faster than `SplPriorityQueue` and 3x faster than the 279 | `Zend\Stdlib\PriorityQueue` implementation. 280 | - extracts are 4x faster than `SplPriorityQueue` and 4-5x faster than the 281 | `Zend\Stdlib\PriorityQueue` implementation. 282 | 283 | The intention is to use this as a drop-in replacement in the 284 | `zend-eventmanager` component to provide performance benefits. 285 | 286 | ### Deprecated 287 | 288 | - [#20](https://github.com/zendframework/zend-stdlib/pull/20) deprecates *all 289 | hydrator* classes, in favor of the new [zend-hydrator](https://github.com/zendframework/zend-hydrator) 290 | component. All classes were updated to extend their zend-hydrator equivalents, 291 | and marked as `@deprecated`, indicating the equivalent class from the other 292 | repository. 293 | 294 | Users *should* immediately start changing their code to use the zend-hydrator 295 | equivalents; in most cases, this can be as easy as removing the `Stdlib` 296 | namespace from import statements or hydrator configuration. Hydrators will be 297 | removed entirely from zend-stdlib in v3.0, and all future updates to hydrators 298 | will occur in the zend-hydrator library. 299 | 300 | Changes with backwards compatibility implications: 301 | 302 | - Users implementing `Zend\Stdlib\Hydrator\HydratorAwareInterface` will need to 303 | update their `setHydrator()` implementation to typehint on 304 | `Zend\Hydrator\HydratorInterface`. This can be done by changing the import 305 | statement for that interface as follows: 306 | 307 | ```php 308 | // Replace this: 309 | use Zend\Stdlib\Hydrator\HydratorInterface; 310 | // with this: 311 | use Zend\Hydrator\HydratorInterface; 312 | ``` 313 | 314 | If you are not using imports, change the typehint within the signature itself: 315 | 316 | ```php 317 | // Replace this: 318 | public function setHydrator(\Zend\Stdlib\Hydrator\HydratorInterface $hydrator) 319 | // with this: 320 | public function setHydrator(\Zend\Hydrator\HydratorInterface $hydrator) 321 | ``` 322 | 323 | If you are using `Zend\Stdlib\Hydrator\HydratorAwareTrait`, no changes are 324 | necessary, unless you override that method. 325 | 326 | - If you were catching hydrator-generated exceptions, these were previously in 327 | the `Zend\Stdlib\Exception` namespace. You will need to update your code to 328 | catch exceptions in the `Zend\Hydrator\Exception` namespace. 329 | 330 | - Users who *do* migrate to zend-hydrator may end up in a situation where 331 | their code will not work with existing libraries that are still type-hinting 332 | on the zend-stdlib interfaces. We will be attempting to address that ASAP, 333 | but the deprecation within zend-stdlib is necessary as a first step. 334 | 335 | In the meantime, you can write hydrators targeting zend-stdlib still in 336 | order to guarantee compatibility. 337 | 338 | ### Removed 339 | 340 | - Nothing. 341 | 342 | ### Fixed 343 | 344 | - Nothing. 345 | 346 | ## 2.6.0 - 2015-07-21 347 | 348 | ### Added 349 | 350 | - [#13](https://github.com/zendframework/zend-stdlib/pull/13) adds 351 | `Zend\Stdlib\Hydrator\Iterator`, which provides mechanisms for hydrating 352 | objects when iterating a traversable. This allows creating generic collection 353 | resultsets; the original idea was pulled from 354 | [PhlyMongo](https://github.com/phly/PhlyMongo), where it was used to hydrate 355 | collections retrieved from MongoDB. 356 | 357 | ### Deprecated 358 | 359 | - Nothing. 360 | 361 | ### Removed 362 | 363 | - Nothing. 364 | 365 | ### Fixed 366 | 367 | - Nothing. 368 | 369 | ## 2.5.2 - 2015-07-21 370 | 371 | ### Added 372 | 373 | - Nothing. 374 | 375 | ### Deprecated 376 | 377 | - Nothing. 378 | 379 | ### Removed 380 | 381 | - Nothing. 382 | 383 | ### Fixed 384 | 385 | - [#9](https://github.com/zendframework/zend-stdlib/pull/9) fixes an issue with 386 | count incrementation during insert in PriorityList, ensuring that incrementation only 387 | occurs when the item inserted was not previously present in the list. 388 | 389 | ## 2.4.4 - 2015-07-21 390 | 391 | ### Added 392 | 393 | - Nothing. 394 | 395 | ### Deprecated 396 | 397 | - Nothing. 398 | 399 | ### Removed 400 | 401 | - Nothing. 402 | 403 | ### Fixed 404 | 405 | - [#9](https://github.com/zendframework/zend-stdlib/pull/9) fixes an issue with 406 | count incrementation during insert in PriorityList, ensuring that incrementation only 407 | occurs when the item inserted was not previously present in the list. 408 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2018, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-stdlib 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-stdlib](https://github.com/laminas/laminas-stdlib). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-stdlib.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-stdlib) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-stdlib/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-stdlib?branch=master) 9 | 10 | `Zend\Stdlib` is a set of components that implements general purpose utility 11 | class for different scopes like: 12 | 13 | - array utilities functions; 14 | - general messaging systems; 15 | - string wrappers; 16 | - etc. 17 | 18 | --- 19 | 20 | - File issues at https://github.com/zendframework/zend-stdlib/issues 21 | - Documentation is at https://docs.zendframework.com/zend-stdlib/ 22 | 23 | ## Benchmarks 24 | 25 | We provide scripts for benchmarking zend-stdlib using the 26 | [PHPBench](https://github.com/phpbench/phpbench) framework; these can be 27 | found in the `benchmark/` directory. 28 | 29 | To execute the benchmarks you can run the following command: 30 | 31 | ```bash 32 | $ vendor/bin/phpbench run --report=aggregate 33 | ``` 34 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-stdlib", 3 | "description": "SPL extensions, array utilities, error handlers, and more", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "stdlib" 9 | ], 10 | "support": { 11 | "docs": "https://docs.zendframework.com/zend-stdlib/", 12 | "issues": "https://github.com/zendframework/zend-stdlib/issues", 13 | "source": "https://github.com/zendframework/zend-stdlib", 14 | "rss": "https://github.com/zendframework/zend-stdlib/releases.atom", 15 | "slack": "https://zendframework-slack.herokuapp.com", 16 | "forum": "https://discourse.zendframework.com/c/questions/components" 17 | }, 18 | "require": { 19 | "php": "^5.6 || ^7.0" 20 | }, 21 | "require-dev": { 22 | "phpbench/phpbench": "^0.13", 23 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", 24 | "zendframework/zend-coding-standard": "~1.0.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Zend\\Stdlib\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "ZendTest\\Stdlib\\": "test/", 34 | "ZendBench\\Stdlib\\": "benchmark/" 35 | } 36 | }, 37 | "config": { 38 | "sort-packages": true 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "3.2.x-dev", 43 | "dev-develop": "3.3.x-dev" 44 | } 45 | }, 46 | "scripts": { 47 | "check": [ 48 | "@cs-check", 49 | "@test" 50 | ], 51 | "cs-check": "phpcs", 52 | "cs-fix": "phpcbf", 53 | "test": "phpunit --colors=always", 54 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/AbstractOptions.php: -------------------------------------------------------------------------------- 1 | setFromArray($options); 35 | } 36 | } 37 | 38 | /** 39 | * Set one or more configuration properties 40 | * 41 | * @param array|Traversable|AbstractOptions $options 42 | * @throws Exception\InvalidArgumentException 43 | * @return AbstractOptions Provides fluent interface 44 | */ 45 | public function setFromArray($options) 46 | { 47 | if ($options instanceof self) { 48 | $options = $options->toArray(); 49 | } 50 | 51 | if (! is_array($options) && ! $options instanceof Traversable) { 52 | throw new Exception\InvalidArgumentException( 53 | sprintf( 54 | 'Parameter provided to %s must be an %s, %s or %s', 55 | __METHOD__, 56 | 'array', 57 | 'Traversable', 58 | 'Zend\Stdlib\AbstractOptions' 59 | ) 60 | ); 61 | } 62 | 63 | foreach ($options as $key => $value) { 64 | $this->__set($key, $value); 65 | } 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * Cast to array 72 | * 73 | * @return array 74 | */ 75 | public function toArray() 76 | { 77 | $array = []; 78 | $transform = function ($letters) { 79 | $letter = array_shift($letters); 80 | return '_' . strtolower($letter); 81 | }; 82 | foreach ($this as $key => $value) { 83 | if ($key === '__strictMode__') { 84 | continue; 85 | } 86 | $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key); 87 | $array[$normalizedKey] = $value; 88 | } 89 | return $array; 90 | } 91 | 92 | /** 93 | * Set a configuration property 94 | * 95 | * @see ParameterObject::__set() 96 | * @param string $key 97 | * @param mixed $value 98 | * @throws Exception\BadMethodCallException 99 | * @return void 100 | */ 101 | public function __set($key, $value) 102 | { 103 | $setter = 'set' . str_replace('_', '', $key); 104 | 105 | if (is_callable([$this, $setter])) { 106 | $this->{$setter}($value); 107 | 108 | return; 109 | } 110 | 111 | if ($this->__strictMode__) { 112 | throw new Exception\BadMethodCallException(sprintf( 113 | 'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined', 114 | $key, 115 | 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))), 116 | $setter 117 | )); 118 | } 119 | } 120 | 121 | /** 122 | * Get a configuration property 123 | * 124 | * @see ParameterObject::__get() 125 | * @param string $key 126 | * @throws Exception\BadMethodCallException 127 | * @return mixed 128 | */ 129 | public function __get($key) 130 | { 131 | $getter = 'get' . str_replace('_', '', $key); 132 | 133 | if (is_callable([$this, $getter])) { 134 | return $this->{$getter}(); 135 | } 136 | 137 | throw new Exception\BadMethodCallException(sprintf( 138 | 'The option "%s" does not have a callable "%s" getter method which must be defined', 139 | $key, 140 | 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))) 141 | )); 142 | } 143 | 144 | /** 145 | * Test if a configuration property is null 146 | * @see ParameterObject::__isset() 147 | * @param string $key 148 | * @return bool 149 | */ 150 | public function __isset($key) 151 | { 152 | $getter = 'get' . str_replace('_', '', $key); 153 | 154 | return method_exists($this, $getter) && null !== $this->__get($key); 155 | } 156 | 157 | /** 158 | * Set a configuration property to NULL 159 | * 160 | * @see ParameterObject::__unset() 161 | * @param string $key 162 | * @throws Exception\InvalidArgumentException 163 | * @return void 164 | */ 165 | public function __unset($key) 166 | { 167 | try { 168 | $this->__set($key, null); 169 | } catch (Exception\BadMethodCallException $e) { 170 | throw new Exception\InvalidArgumentException( 171 | 'The class property $' . $key . ' cannot be unset as' 172 | . ' NULL is an invalid value for it', 173 | 0, 174 | $e 175 | ); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/ArrayObject.php: -------------------------------------------------------------------------------- 1 | setFlags($flags); 65 | $this->storage = $input; 66 | $this->setIteratorClass($iteratorClass); 67 | $this->protectedProperties = array_keys(get_object_vars($this)); 68 | } 69 | 70 | /** 71 | * Returns whether the requested key exists 72 | * 73 | * @param mixed $key 74 | * @return bool 75 | */ 76 | public function __isset($key) 77 | { 78 | if ($this->flag == self::ARRAY_AS_PROPS) { 79 | return $this->offsetExists($key); 80 | } 81 | if (in_array($key, $this->protectedProperties)) { 82 | throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); 83 | } 84 | 85 | return isset($this->$key); 86 | } 87 | 88 | /** 89 | * Sets the value at the specified key to value 90 | * 91 | * @param mixed $key 92 | * @param mixed $value 93 | * @return void 94 | */ 95 | public function __set($key, $value) 96 | { 97 | if ($this->flag == self::ARRAY_AS_PROPS) { 98 | return $this->offsetSet($key, $value); 99 | } 100 | if (in_array($key, $this->protectedProperties)) { 101 | throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); 102 | } 103 | $this->$key = $value; 104 | } 105 | 106 | /** 107 | * Unsets the value at the specified key 108 | * 109 | * @param mixed $key 110 | * @return void 111 | */ 112 | public function __unset($key) 113 | { 114 | if ($this->flag == self::ARRAY_AS_PROPS) { 115 | return $this->offsetUnset($key); 116 | } 117 | if (in_array($key, $this->protectedProperties)) { 118 | throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); 119 | } 120 | unset($this->$key); 121 | } 122 | 123 | /** 124 | * Returns the value at the specified key by reference 125 | * 126 | * @param mixed $key 127 | * @return mixed 128 | */ 129 | public function &__get($key) 130 | { 131 | $ret = null; 132 | if ($this->flag == self::ARRAY_AS_PROPS) { 133 | $ret =& $this->offsetGet($key); 134 | 135 | return $ret; 136 | } 137 | if (in_array($key, $this->protectedProperties)) { 138 | throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); 139 | } 140 | 141 | return $this->$key; 142 | } 143 | 144 | /** 145 | * Appends the value 146 | * 147 | * @param mixed $value 148 | * @return void 149 | */ 150 | public function append($value) 151 | { 152 | $this->storage[] = $value; 153 | } 154 | 155 | /** 156 | * Sort the entries by value 157 | * 158 | * @return void 159 | */ 160 | public function asort() 161 | { 162 | asort($this->storage); 163 | } 164 | 165 | /** 166 | * Get the number of public properties in the ArrayObject 167 | * 168 | * @return int 169 | */ 170 | public function count() 171 | { 172 | return count($this->storage); 173 | } 174 | 175 | /** 176 | * Exchange the array for another one. 177 | * 178 | * @param array|ArrayObject $data 179 | * @return array 180 | */ 181 | public function exchangeArray($data) 182 | { 183 | if (! is_array($data) && ! is_object($data)) { 184 | throw new Exception\InvalidArgumentException( 185 | 'Passed variable is not an array or object, using empty array instead' 186 | ); 187 | } 188 | 189 | if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) { 190 | $data = $data->getArrayCopy(); 191 | } 192 | if (! is_array($data)) { 193 | $data = (array) $data; 194 | } 195 | 196 | $storage = $this->storage; 197 | 198 | $this->storage = $data; 199 | 200 | return $storage; 201 | } 202 | 203 | /** 204 | * Creates a copy of the ArrayObject. 205 | * 206 | * @return array 207 | */ 208 | public function getArrayCopy() 209 | { 210 | return $this->storage; 211 | } 212 | 213 | /** 214 | * Gets the behavior flags. 215 | * 216 | * @return int 217 | */ 218 | public function getFlags() 219 | { 220 | return $this->flag; 221 | } 222 | 223 | /** 224 | * Create a new iterator from an ArrayObject instance 225 | * 226 | * @return \Iterator 227 | */ 228 | public function getIterator() 229 | { 230 | $class = $this->iteratorClass; 231 | 232 | return new $class($this->storage); 233 | } 234 | 235 | /** 236 | * Gets the iterator classname for the ArrayObject. 237 | * 238 | * @return string 239 | */ 240 | public function getIteratorClass() 241 | { 242 | return $this->iteratorClass; 243 | } 244 | 245 | /** 246 | * Sort the entries by key 247 | * 248 | * @return void 249 | */ 250 | public function ksort() 251 | { 252 | ksort($this->storage); 253 | } 254 | 255 | /** 256 | * Sort an array using a case insensitive "natural order" algorithm 257 | * 258 | * @return void 259 | */ 260 | public function natcasesort() 261 | { 262 | natcasesort($this->storage); 263 | } 264 | 265 | /** 266 | * Sort entries using a "natural order" algorithm 267 | * 268 | * @return void 269 | */ 270 | public function natsort() 271 | { 272 | natsort($this->storage); 273 | } 274 | 275 | /** 276 | * Returns whether the requested key exists 277 | * 278 | * @param mixed $key 279 | * @return bool 280 | */ 281 | public function offsetExists($key) 282 | { 283 | return isset($this->storage[$key]); 284 | } 285 | 286 | /** 287 | * Returns the value at the specified key 288 | * 289 | * @param mixed $key 290 | * @return mixed 291 | */ 292 | public function &offsetGet($key) 293 | { 294 | $ret = null; 295 | if (! $this->offsetExists($key)) { 296 | return $ret; 297 | } 298 | $ret =& $this->storage[$key]; 299 | 300 | return $ret; 301 | } 302 | 303 | /** 304 | * Sets the value at the specified key to value 305 | * 306 | * @param mixed $key 307 | * @param mixed $value 308 | * @return void 309 | */ 310 | public function offsetSet($key, $value) 311 | { 312 | $this->storage[$key] = $value; 313 | } 314 | 315 | /** 316 | * Unsets the value at the specified key 317 | * 318 | * @param mixed $key 319 | * @return void 320 | */ 321 | public function offsetUnset($key) 322 | { 323 | if ($this->offsetExists($key)) { 324 | unset($this->storage[$key]); 325 | } 326 | } 327 | 328 | /** 329 | * Serialize an ArrayObject 330 | * 331 | * @return string 332 | */ 333 | public function serialize() 334 | { 335 | return serialize(get_object_vars($this)); 336 | } 337 | 338 | /** 339 | * Sets the behavior flags 340 | * 341 | * @param int $flags 342 | * @return void 343 | */ 344 | public function setFlags($flags) 345 | { 346 | $this->flag = $flags; 347 | } 348 | 349 | /** 350 | * Sets the iterator classname for the ArrayObject 351 | * 352 | * @param string $class 353 | * @return void 354 | */ 355 | public function setIteratorClass($class) 356 | { 357 | if (class_exists($class)) { 358 | $this->iteratorClass = $class; 359 | 360 | return ; 361 | } 362 | 363 | if (strpos($class, '\\') === 0) { 364 | $class = '\\' . $class; 365 | if (class_exists($class)) { 366 | $this->iteratorClass = $class; 367 | 368 | return ; 369 | } 370 | } 371 | 372 | throw new Exception\InvalidArgumentException('The iterator class does not exist'); 373 | } 374 | 375 | /** 376 | * Sort the entries with a user-defined comparison function and maintain key association 377 | * 378 | * @param callable $function 379 | * @return void 380 | */ 381 | public function uasort($function) 382 | { 383 | if (is_callable($function)) { 384 | uasort($this->storage, $function); 385 | } 386 | } 387 | 388 | /** 389 | * Sort the entries by keys using a user-defined comparison function 390 | * 391 | * @param callable $function 392 | * @return void 393 | */ 394 | public function uksort($function) 395 | { 396 | if (is_callable($function)) { 397 | uksort($this->storage, $function); 398 | } 399 | } 400 | 401 | /** 402 | * Unserialize an ArrayObject 403 | * 404 | * @param string $data 405 | * @return void 406 | */ 407 | public function unserialize($data) 408 | { 409 | $ar = unserialize($data); 410 | $this->protectedProperties = array_keys(get_object_vars($this)); 411 | 412 | $this->setFlags($ar['flag']); 413 | $this->exchangeArray($ar['storage']); 414 | $this->setIteratorClass($ar['iteratorClass']); 415 | 416 | foreach ($ar as $k => $v) { 417 | switch ($k) { 418 | case 'flag': 419 | $this->setFlags($v); 420 | break; 421 | case 'storage': 422 | $this->exchangeArray($v); 423 | break; 424 | case 'iteratorClass': 425 | $this->setIteratorClass($v); 426 | break; 427 | case 'protectedProperties': 428 | break; 429 | default: 430 | $this->__set($k, $v); 431 | } 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/ArraySerializableInterface.php: -------------------------------------------------------------------------------- 1 | getArrayCopy(); 31 | return new ArrayIterator(array_reverse($array)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ArrayUtils.php: -------------------------------------------------------------------------------- 1 | 0; 51 | } 52 | 53 | /** 54 | * Test whether an array contains one or more integer keys 55 | * 56 | * @param mixed $value 57 | * @param bool $allowEmpty Should an empty array() return true 58 | * @return bool 59 | */ 60 | public static function hasIntegerKeys($value, $allowEmpty = false) 61 | { 62 | if (! is_array($value)) { 63 | return false; 64 | } 65 | 66 | if (! $value) { 67 | return $allowEmpty; 68 | } 69 | 70 | return count(array_filter(array_keys($value), 'is_int')) > 0; 71 | } 72 | 73 | /** 74 | * Test whether an array contains one or more numeric keys. 75 | * 76 | * A numeric key can be one of the following: 77 | * - an integer 1, 78 | * - a string with a number '20' 79 | * - a string with negative number: '-1000' 80 | * - a float: 2.2120, -78.150999 81 | * - a string with float: '4000.99999', '-10.10' 82 | * 83 | * @param mixed $value 84 | * @param bool $allowEmpty Should an empty array() return true 85 | * @return bool 86 | */ 87 | public static function hasNumericKeys($value, $allowEmpty = false) 88 | { 89 | if (! is_array($value)) { 90 | return false; 91 | } 92 | 93 | if (! $value) { 94 | return $allowEmpty; 95 | } 96 | 97 | return count(array_filter(array_keys($value), 'is_numeric')) > 0; 98 | } 99 | 100 | /** 101 | * Test whether an array is a list 102 | * 103 | * A list is a collection of values assigned to continuous integer keys 104 | * starting at 0 and ending at count() - 1. 105 | * 106 | * For example: 107 | * 108 | * $list = array('a', 'b', 'c', 'd'); 109 | * $list = array( 110 | * 0 => 'foo', 111 | * 1 => 'bar', 112 | * 2 => array('foo' => 'baz'), 113 | * ); 114 | * 115 | * 116 | * @param mixed $value 117 | * @param bool $allowEmpty Is an empty list a valid list? 118 | * @return bool 119 | */ 120 | public static function isList($value, $allowEmpty = false) 121 | { 122 | if (! is_array($value)) { 123 | return false; 124 | } 125 | 126 | if (! $value) { 127 | return $allowEmpty; 128 | } 129 | 130 | return (array_values($value) === $value); 131 | } 132 | 133 | /** 134 | * Test whether an array is a hash table. 135 | * 136 | * An array is a hash table if: 137 | * 138 | * 1. Contains one or more non-integer keys, or 139 | * 2. Integer keys are non-continuous or misaligned (not starting with 0) 140 | * 141 | * For example: 142 | * 143 | * $hash = array( 144 | * 'foo' => 15, 145 | * 'bar' => false, 146 | * ); 147 | * $hash = array( 148 | * 1995 => 'Birth of PHP', 149 | * 2009 => 'PHP 5.3.0', 150 | * 2012 => 'PHP 5.4.0', 151 | * ); 152 | * $hash = array( 153 | * 'formElement, 154 | * 'options' => array( 'debug' => true ), 155 | * ); 156 | * 157 | * 158 | * @param mixed $value 159 | * @param bool $allowEmpty Is an empty array() a valid hash table? 160 | * @return bool 161 | */ 162 | public static function isHashTable($value, $allowEmpty = false) 163 | { 164 | if (! is_array($value)) { 165 | return false; 166 | } 167 | 168 | if (! $value) { 169 | return $allowEmpty; 170 | } 171 | 172 | return (array_values($value) !== $value); 173 | } 174 | 175 | /** 176 | * Checks if a value exists in an array. 177 | * 178 | * Due to "foo" == 0 === TRUE with in_array when strict = false, an option 179 | * has been added to prevent this. When $strict = 0/false, the most secure 180 | * non-strict check is implemented. if $strict = -1, the default in_array 181 | * non-strict behaviour is used. 182 | * 183 | * @param mixed $needle 184 | * @param array $haystack 185 | * @param int|bool $strict 186 | * @return bool 187 | */ 188 | public static function inArray($needle, array $haystack, $strict = false) 189 | { 190 | if (! $strict) { 191 | if (is_int($needle) || is_float($needle)) { 192 | $needle = (string) $needle; 193 | } 194 | if (is_string($needle)) { 195 | foreach ($haystack as &$h) { 196 | if (is_int($h) || is_float($h)) { 197 | $h = (string) $h; 198 | } 199 | } 200 | } 201 | } 202 | return in_array($needle, $haystack, $strict); 203 | } 204 | 205 | /** 206 | * Convert an iterator to an array. 207 | * 208 | * Converts an iterator to an array. The $recursive flag, on by default, 209 | * hints whether or not you want to do so recursively. 210 | * 211 | * @param array|Traversable $iterator The array or Traversable object to convert 212 | * @param bool $recursive Recursively check all nested structures 213 | * @throws Exception\InvalidArgumentException if $iterator is not an array or a Traversable object 214 | * @return array 215 | */ 216 | public static function iteratorToArray($iterator, $recursive = true) 217 | { 218 | if (! is_array($iterator) && ! $iterator instanceof Traversable) { 219 | throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object'); 220 | } 221 | 222 | if (! $recursive) { 223 | if (is_array($iterator)) { 224 | return $iterator; 225 | } 226 | 227 | return iterator_to_array($iterator); 228 | } 229 | 230 | if (method_exists($iterator, 'toArray')) { 231 | return $iterator->toArray(); 232 | } 233 | 234 | $array = []; 235 | foreach ($iterator as $key => $value) { 236 | if (is_scalar($value)) { 237 | $array[$key] = $value; 238 | continue; 239 | } 240 | 241 | if ($value instanceof Traversable) { 242 | $array[$key] = static::iteratorToArray($value, $recursive); 243 | continue; 244 | } 245 | 246 | if (is_array($value)) { 247 | $array[$key] = static::iteratorToArray($value, $recursive); 248 | continue; 249 | } 250 | 251 | $array[$key] = $value; 252 | } 253 | 254 | return $array; 255 | } 256 | 257 | /** 258 | * Merge two arrays together. 259 | * 260 | * If an integer key exists in both arrays and preserveNumericKeys is false, the value 261 | * from the second array will be appended to the first array. If both values are arrays, they 262 | * are merged together, else the value of the second array overwrites the one of the first array. 263 | * 264 | * @param array $a 265 | * @param array $b 266 | * @param bool $preserveNumericKeys 267 | * @return array 268 | */ 269 | public static function merge(array $a, array $b, $preserveNumericKeys = false) 270 | { 271 | foreach ($b as $key => $value) { 272 | if ($value instanceof MergeReplaceKeyInterface) { 273 | $a[$key] = $value->getData(); 274 | } elseif (isset($a[$key]) || array_key_exists($key, $a)) { 275 | if ($value instanceof MergeRemoveKey) { 276 | unset($a[$key]); 277 | } elseif (! $preserveNumericKeys && is_int($key)) { 278 | $a[] = $value; 279 | } elseif (is_array($value) && is_array($a[$key])) { 280 | $a[$key] = static::merge($a[$key], $value, $preserveNumericKeys); 281 | } else { 282 | $a[$key] = $value; 283 | } 284 | } else { 285 | if (! $value instanceof MergeRemoveKey) { 286 | $a[$key] = $value; 287 | } 288 | } 289 | } 290 | 291 | return $a; 292 | } 293 | 294 | /** 295 | * @deprecated Since 3.2.0; use the native array_filter methods 296 | * 297 | * @param array $data 298 | * @param callable $callback 299 | * @param null|int $flag 300 | * @return array 301 | * @throws Exception\InvalidArgumentException 302 | */ 303 | public static function filter(array $data, $callback, $flag = null) 304 | { 305 | if (! is_callable($callback)) { 306 | throw new Exception\InvalidArgumentException(sprintf( 307 | 'Second parameter of %s must be callable', 308 | __METHOD__ 309 | )); 310 | } 311 | 312 | return array_filter($data, $callback, $flag); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/ArrayUtils/MergeRemoveKey.php: -------------------------------------------------------------------------------- 1 | data = $data; 25 | } 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | public function getData() 31 | { 32 | return $this->data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ArrayUtils/MergeReplaceKeyInterface.php: -------------------------------------------------------------------------------- 1 | message`, 16 | * `message`) 17 | * - Write output to a specified stream, optionally with colorization. 18 | * - Write a line of output to a specified stream, optionally with 19 | * colorization, using the system EOL sequence.. 20 | * - Write an error message to STDERR. 21 | * 22 | * Colorization will only occur when expected sequences are discovered, and 23 | * then, only if the console terminal allows it. 24 | * 25 | * Essentially, provides the bare minimum to allow you to provide messages to 26 | * the current console. 27 | */ 28 | class ConsoleHelper 29 | { 30 | const COLOR_GREEN = "\033[32m"; 31 | const COLOR_RED = "\033[31m"; 32 | const COLOR_RESET = "\033[0m"; 33 | 34 | const HIGHLIGHT_INFO = 'info'; 35 | const HIGHLIGHT_ERROR = 'error'; 36 | 37 | private $highlightMap = [ 38 | self::HIGHLIGHT_INFO => self::COLOR_GREEN, 39 | self::HIGHLIGHT_ERROR => self::COLOR_RED, 40 | ]; 41 | 42 | /** 43 | * @var string Exists only for testing. 44 | */ 45 | private $eol = PHP_EOL; 46 | 47 | /** 48 | * @var resource Exists only for testing. 49 | */ 50 | private $stderr = STDERR; 51 | 52 | /** 53 | * @var bool 54 | */ 55 | private $supportsColor; 56 | 57 | /** 58 | * @param resource $resource 59 | */ 60 | public function __construct($resource = STDOUT) 61 | { 62 | $this->supportsColor = $this->detectColorCapabilities($resource); 63 | } 64 | 65 | /** 66 | * Colorize a string for use with the terminal. 67 | * 68 | * Takes strings formatted as `string` and formats them per the 69 | * $highlightMap; if color support is disabled, simply removes the formatting 70 | * tags. 71 | * 72 | * @param string $string 73 | * @return string 74 | */ 75 | public function colorize($string) 76 | { 77 | $reset = $this->supportsColor ? self::COLOR_RESET : ''; 78 | foreach ($this->highlightMap as $key => $color) { 79 | $pattern = sprintf('#<%s>(.*?)#s', $key, $key); 80 | $color = $this->supportsColor ? $color : ''; 81 | $string = preg_replace($pattern, $color . '$1' . $reset, $string); 82 | } 83 | return $string; 84 | } 85 | 86 | /** 87 | * @param string $string 88 | * @param bool $colorize Whether or not to colorize the string 89 | * @param resource $resource Defaults to STDOUT 90 | * @return void 91 | */ 92 | public function write($string, $colorize = true, $resource = STDOUT) 93 | { 94 | if ($colorize) { 95 | $string = $this->colorize($string); 96 | } 97 | 98 | $string = $this->formatNewlines($string); 99 | 100 | fwrite($resource, $string); 101 | } 102 | 103 | /** 104 | * @param string $string 105 | * @param bool $colorize Whether or not to colorize the line 106 | * @param resource $resource Defaults to STDOUT 107 | * @return void 108 | */ 109 | public function writeLine($string, $colorize = true, $resource = STDOUT) 110 | { 111 | $this->write($string . $this->eol, $colorize, $resource); 112 | } 113 | 114 | /** 115 | * Emit an error message. 116 | * 117 | * Wraps the message in ``, and passes it to `writeLine()`, 118 | * using STDERR as the resource; emits an additional empty line when done, 119 | * also to STDERR. 120 | * 121 | * @param string $message 122 | * @return void 123 | */ 124 | public function writeErrorMessage($message) 125 | { 126 | $this->writeLine(sprintf('%s', $message), true, $this->stderr); 127 | $this->writeLine('', false, $this->stderr); 128 | } 129 | 130 | /** 131 | * @param resource $resource 132 | * @return bool 133 | */ 134 | private function detectColorCapabilities($resource = STDOUT) 135 | { 136 | if ('\\' === DIRECTORY_SEPARATOR) { 137 | // Windows 138 | return false !== getenv('ANSICON') 139 | || 'ON' === getenv('ConEmuANSI') 140 | || 'xterm' === getenv('TERM'); 141 | } 142 | 143 | return function_exists('posix_isatty') && posix_isatty($resource); 144 | } 145 | 146 | /** 147 | * Ensure newlines are appropriate for the current terminal. 148 | * 149 | * @param string 150 | * @return string 151 | */ 152 | private function formatNewlines($string) 153 | { 154 | $string = str_replace($this->eol, "\0PHP_EOL\0", $string); 155 | $string = preg_replace("/(\r\n|\n|\r)/", $this->eol, $string); 156 | return str_replace("\0PHP_EOL\0", $this->eol, $string); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/DispatchableInterface.php: -------------------------------------------------------------------------------- 1 | values[$priority][] = $value; 97 | if (! isset($this->priorities[$priority])) { 98 | $this->priorities[$priority] = $priority; 99 | $this->maxPriority = $this->maxPriority === null ? $priority : max($priority, $this->maxPriority); 100 | } 101 | ++$this->count; 102 | } 103 | 104 | /** 105 | * Extract an element in the queue according to the priority and the 106 | * order of insertion 107 | * 108 | * @return mixed 109 | */ 110 | public function extract() 111 | { 112 | if (! $this->valid()) { 113 | return false; 114 | } 115 | $value = $this->current(); 116 | $this->nextAndRemove(); 117 | return $value; 118 | } 119 | 120 | /** 121 | * Remove an item from the queue 122 | * 123 | * This is different than {@link extract()}; its purpose is to dequeue an 124 | * item. 125 | * 126 | * Note: this removes the first item matching the provided item found. If 127 | * the same item has been added multiple times, it will not remove other 128 | * instances. 129 | * 130 | * @param mixed $datum 131 | * @return bool False if the item was not found, true otherwise. 132 | */ 133 | public function remove($datum) 134 | { 135 | $currentIndex = $this->index; 136 | $currentSubIndex = $this->subIndex; 137 | $currentPriority = $this->maxPriority; 138 | 139 | $this->rewind(); 140 | while ($this->valid()) { 141 | if (current($this->values[$this->maxPriority]) === $datum) { 142 | $index = key($this->values[$this->maxPriority]); 143 | unset($this->values[$this->maxPriority][$index]); 144 | 145 | // The `next()` method advances the internal array pointer, so we need to use the `reset()` function, 146 | // otherwise we would lose all elements before the place the pointer points. 147 | reset($this->values[$this->maxPriority]); 148 | 149 | $this->index = $currentIndex; 150 | $this->subIndex = $currentSubIndex; 151 | 152 | // If the array is empty we need to destroy the unnecessary priority, 153 | // otherwise we would end up with an incorrect value of `$this->count` 154 | // {@see \Zend\Stdlib\FastPriorityQueue::nextAndRemove()}. 155 | if (empty($this->values[$this->maxPriority])) { 156 | unset($this->values[$this->maxPriority]); 157 | unset($this->priorities[$this->maxPriority]); 158 | if ($this->maxPriority === $currentPriority) { 159 | $this->subIndex = 0; 160 | } 161 | } 162 | 163 | $this->maxPriority = empty($this->priorities) ? null : max($this->priorities); 164 | --$this->count; 165 | return true; 166 | } 167 | $this->next(); 168 | } 169 | return false; 170 | } 171 | 172 | /** 173 | * Get the total number of elements in the queue 174 | * 175 | * @return integer 176 | */ 177 | public function count() 178 | { 179 | return $this->count; 180 | } 181 | 182 | /** 183 | * Get the current element in the queue 184 | * 185 | * @return mixed 186 | */ 187 | public function current() 188 | { 189 | switch ($this->extractFlag) { 190 | case self::EXTR_DATA: 191 | return current($this->values[$this->maxPriority]); 192 | case self::EXTR_PRIORITY: 193 | return $this->maxPriority; 194 | case self::EXTR_BOTH: 195 | return [ 196 | 'data' => current($this->values[$this->maxPriority]), 197 | 'priority' => $this->maxPriority 198 | ]; 199 | } 200 | } 201 | 202 | /** 203 | * Get the index of the current element in the queue 204 | * 205 | * @return integer 206 | */ 207 | public function key() 208 | { 209 | return $this->index; 210 | } 211 | 212 | /** 213 | * Set the iterator pointer to the next element in the queue 214 | * removing the previous element 215 | */ 216 | protected function nextAndRemove() 217 | { 218 | $key = key($this->values[$this->maxPriority]); 219 | 220 | if (false === next($this->values[$this->maxPriority])) { 221 | unset($this->priorities[$this->maxPriority]); 222 | unset($this->values[$this->maxPriority]); 223 | $this->maxPriority = empty($this->priorities) ? null : max($this->priorities); 224 | $this->subIndex = -1; 225 | } else { 226 | unset($this->values[$this->maxPriority][$key]); 227 | } 228 | ++$this->index; 229 | ++$this->subIndex; 230 | --$this->count; 231 | } 232 | 233 | /** 234 | * Set the iterator pointer to the next element in the queue 235 | * without removing the previous element 236 | */ 237 | public function next() 238 | { 239 | if (false === next($this->values[$this->maxPriority])) { 240 | unset($this->subPriorities[$this->maxPriority]); 241 | reset($this->values[$this->maxPriority]); 242 | $this->maxPriority = empty($this->subPriorities) ? null : max($this->subPriorities); 243 | $this->subIndex = -1; 244 | } 245 | ++$this->index; 246 | ++$this->subIndex; 247 | } 248 | 249 | /** 250 | * Check if the current iterator is valid 251 | * 252 | * @return boolean 253 | */ 254 | public function valid() 255 | { 256 | return isset($this->values[$this->maxPriority]); 257 | } 258 | 259 | /** 260 | * Rewind the current iterator 261 | */ 262 | public function rewind() 263 | { 264 | $this->subPriorities = $this->priorities; 265 | $this->maxPriority = empty($this->priorities) ? 0 : max($this->priorities); 266 | $this->index = 0; 267 | $this->subIndex = 0; 268 | } 269 | 270 | /** 271 | * Serialize to an array 272 | * 273 | * Array will be priority => data pairs 274 | * 275 | * @return array 276 | */ 277 | public function toArray() 278 | { 279 | $array = []; 280 | foreach (clone $this as $item) { 281 | $array[] = $item; 282 | } 283 | return $array; 284 | } 285 | 286 | /** 287 | * Serialize 288 | * 289 | * @return string 290 | */ 291 | public function serialize() 292 | { 293 | $clone = clone $this; 294 | $clone->setExtractFlags(self::EXTR_BOTH); 295 | 296 | $data = []; 297 | foreach ($clone as $item) { 298 | $data[] = $item; 299 | } 300 | 301 | return serialize($data); 302 | } 303 | 304 | /** 305 | * Deserialize 306 | * 307 | * @param string $data 308 | * @return void 309 | */ 310 | public function unserialize($data) 311 | { 312 | foreach (unserialize($data) as $item) { 313 | $this->insert($item['data'], $item['priority']); 314 | } 315 | } 316 | 317 | /** 318 | * Set the extract flag 319 | * 320 | * @param integer $flag 321 | */ 322 | public function setExtractFlags($flag) 323 | { 324 | switch ($flag) { 325 | case self::EXTR_DATA: 326 | case self::EXTR_PRIORITY: 327 | case self::EXTR_BOTH: 328 | $this->extractFlag = $flag; 329 | break; 330 | default: 331 | throw new Exception\InvalidArgumentException("The extract flag specified is not valid"); 332 | } 333 | } 334 | 335 | /** 336 | * Check if the queue is empty 337 | * 338 | * @return boolean 339 | */ 340 | public function isEmpty() 341 | { 342 | return empty($this->values); 343 | } 344 | 345 | /** 346 | * Does the queue contain the given datum? 347 | * 348 | * @param mixed $datum 349 | * @return bool 350 | */ 351 | public function contains($datum) 352 | { 353 | foreach ($this->values as $values) { 354 | if (in_array($datum, $values)) { 355 | return true; 356 | } 357 | } 358 | return false; 359 | } 360 | 361 | /** 362 | * Does the queue have an item with the given priority? 363 | * 364 | * @param int $priority 365 | * @return bool 366 | */ 367 | public function hasPriority($priority) 368 | { 369 | return isset($this->values[$priority]); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/Glob.php: -------------------------------------------------------------------------------- 1 | GLOB_MARK, 61 | self::GLOB_NOSORT => GLOB_NOSORT, 62 | self::GLOB_NOCHECK => GLOB_NOCHECK, 63 | self::GLOB_NOESCAPE => GLOB_NOESCAPE, 64 | self::GLOB_BRACE => defined('GLOB_BRACE') ? GLOB_BRACE : 0, 65 | self::GLOB_ONLYDIR => GLOB_ONLYDIR, 66 | self::GLOB_ERR => GLOB_ERR, 67 | ]; 68 | 69 | $globFlags = 0; 70 | 71 | foreach ($flagMap as $internalFlag => $globFlag) { 72 | if ($flags & $internalFlag) { 73 | $globFlags |= $globFlag; 74 | } 75 | } 76 | } else { 77 | $globFlags = 0; 78 | } 79 | 80 | ErrorHandler::start(); 81 | $res = glob($pattern, $globFlags); 82 | $err = ErrorHandler::stop(); 83 | if ($res === false) { 84 | throw new Exception\RuntimeException("glob('{$pattern}', {$globFlags}) failed", 0, $err); 85 | } 86 | return $res; 87 | } 88 | 89 | /** 90 | * Expand braces manually, then use the system glob. 91 | * 92 | * @param string $pattern 93 | * @param int $flags 94 | * @return array 95 | * @throws Exception\RuntimeException 96 | */ 97 | protected static function fallbackGlob($pattern, $flags) 98 | { 99 | if (! $flags & self::GLOB_BRACE) { 100 | return static::systemGlob($pattern, $flags); 101 | } 102 | 103 | $flags &= ~self::GLOB_BRACE; 104 | $length = strlen($pattern); 105 | $paths = []; 106 | 107 | if ($flags & self::GLOB_NOESCAPE) { 108 | $begin = strpos($pattern, '{'); 109 | } else { 110 | $begin = 0; 111 | 112 | while (true) { 113 | if ($begin === $length) { 114 | $begin = false; 115 | break; 116 | } elseif ($pattern[$begin] === '\\' && ($begin + 1) < $length) { 117 | $begin++; 118 | } elseif ($pattern[$begin] === '{') { 119 | break; 120 | } 121 | 122 | $begin++; 123 | } 124 | } 125 | 126 | if ($begin === false) { 127 | return static::systemGlob($pattern, $flags); 128 | } 129 | 130 | $next = static::nextBraceSub($pattern, $begin + 1, $flags); 131 | 132 | if ($next === null) { 133 | return static::systemGlob($pattern, $flags); 134 | } 135 | 136 | $rest = $next; 137 | 138 | while ($pattern[$rest] !== '}') { 139 | $rest = static::nextBraceSub($pattern, $rest + 1, $flags); 140 | 141 | if ($rest === null) { 142 | return static::systemGlob($pattern, $flags); 143 | } 144 | } 145 | 146 | $p = $begin + 1; 147 | 148 | while (true) { 149 | $subPattern = substr($pattern, 0, $begin) 150 | . substr($pattern, $p, $next - $p) 151 | . substr($pattern, $rest + 1); 152 | 153 | $result = static::fallbackGlob($subPattern, $flags | self::GLOB_BRACE); 154 | 155 | if ($result) { 156 | $paths = array_merge($paths, $result); 157 | } 158 | 159 | if ($pattern[$next] === '}') { 160 | break; 161 | } 162 | 163 | $p = $next + 1; 164 | $next = static::nextBraceSub($pattern, $p, $flags); 165 | } 166 | 167 | return array_unique($paths); 168 | } 169 | 170 | /** 171 | * Find the end of the sub-pattern in a brace expression. 172 | * 173 | * @param string $pattern 174 | * @param int $begin 175 | * @param int $flags 176 | * @return int|null 177 | */ 178 | protected static function nextBraceSub($pattern, $begin, $flags) 179 | { 180 | $length = strlen($pattern); 181 | $depth = 0; 182 | $current = $begin; 183 | 184 | while ($current < $length) { 185 | if (! $flags & self::GLOB_NOESCAPE && $pattern[$current] === '\\') { 186 | if (++$current === $length) { 187 | break; 188 | } 189 | 190 | $current++; 191 | } else { 192 | if (($pattern[$current] === '}' && $depth-- === 0) || ($pattern[$current] === ',' && $depth === 0)) { 193 | break; 194 | } elseif ($pattern[$current++] === '{') { 195 | $depth++; 196 | } 197 | } 198 | } 199 | 200 | return ($current < $length ? $current : null); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Guard/AllGuardsTrait.php: -------------------------------------------------------------------------------- 1 | metadata[$spec] = $value; 41 | return $this; 42 | } 43 | if (! is_array($spec) && ! $spec instanceof Traversable) { 44 | throw new Exception\InvalidArgumentException(sprintf( 45 | 'Expected a string, array, or Traversable argument in first position; received "%s"', 46 | (is_object($spec) ? get_class($spec) : gettype($spec)) 47 | )); 48 | } 49 | foreach ($spec as $key => $value) { 50 | $this->metadata[$key] = $value; 51 | } 52 | return $this; 53 | } 54 | 55 | /** 56 | * Retrieve all metadata or a single metadatum as specified by key 57 | * 58 | * @param null|string|int $key 59 | * @param null|mixed $default 60 | * @throws Exception\InvalidArgumentException 61 | * @return mixed 62 | */ 63 | public function getMetadata($key = null, $default = null) 64 | { 65 | if (null === $key) { 66 | return $this->metadata; 67 | } 68 | 69 | if (! is_scalar($key)) { 70 | throw new Exception\InvalidArgumentException('Non-scalar argument provided for key'); 71 | } 72 | 73 | if (array_key_exists($key, $this->metadata)) { 74 | return $this->metadata[$key]; 75 | } 76 | 77 | return $default; 78 | } 79 | 80 | /** 81 | * Set message content 82 | * 83 | * @param mixed $value 84 | * @return Message 85 | */ 86 | public function setContent($value) 87 | { 88 | $this->content = $value; 89 | return $this; 90 | } 91 | 92 | /** 93 | * Get message content 94 | * 95 | * @return mixed 96 | */ 97 | public function getContent() 98 | { 99 | return $this->content; 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | public function toString() 106 | { 107 | $request = ''; 108 | foreach ($this->getMetadata() as $key => $value) { 109 | $request .= sprintf( 110 | "%s: %s\r\n", 111 | (string) $key, 112 | (string) $value 113 | ); 114 | } 115 | $request .= "\r\n" . $this->getContent(); 116 | return $request; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/MessageInterface.php: -------------------------------------------------------------------------------- 1 | exchangeArray($values); 41 | } 42 | 43 | /** 44 | * Populate from query string 45 | * 46 | * @param string $string 47 | * @return void 48 | */ 49 | public function fromString($string) 50 | { 51 | $array = []; 52 | parse_str($string, $array); 53 | $this->fromArray($array); 54 | } 55 | 56 | /** 57 | * Serialize to native PHP array 58 | * 59 | * @return array 60 | */ 61 | public function toArray() 62 | { 63 | return $this->getArrayCopy(); 64 | } 65 | 66 | /** 67 | * Serialize to query string 68 | * 69 | * @return string 70 | */ 71 | public function toString() 72 | { 73 | return http_build_query($this->toArray()); 74 | } 75 | 76 | /** 77 | * Retrieve by key 78 | * 79 | * Returns null if the key does not exist. 80 | * 81 | * @param string $name 82 | * @return mixed 83 | */ 84 | public function offsetGet($name) 85 | { 86 | if ($this->offsetExists($name)) { 87 | return parent::offsetGet($name); 88 | } 89 | return; 90 | } 91 | 92 | /** 93 | * @param string $name 94 | * @param mixed $default optional default value 95 | * @return mixed 96 | */ 97 | public function get($name, $default = null) 98 | { 99 | if ($this->offsetExists($name)) { 100 | return parent::offsetGet($name); 101 | } 102 | return $default; 103 | } 104 | 105 | /** 106 | * @param string $name 107 | * @param mixed $value 108 | * @return Parameters 109 | */ 110 | public function set($name, $value) 111 | { 112 | $this[$name] = $value; 113 | return $this; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/ParametersInterface.php: -------------------------------------------------------------------------------- 1 | items[$name])) { 66 | $this->count++; 67 | } 68 | 69 | $this->sorted = false; 70 | 71 | $this->items[$name] = [ 72 | 'data' => $value, 73 | 'priority' => (int) $priority, 74 | 'serial' => $this->serial++, 75 | ]; 76 | } 77 | 78 | /** 79 | * @param string $name 80 | * @param int $priority 81 | * 82 | * @return $this 83 | * 84 | * @throws \Exception 85 | */ 86 | public function setPriority($name, $priority) 87 | { 88 | if (! isset($this->items[$name])) { 89 | throw new \Exception("item $name not found"); 90 | } 91 | 92 | $this->items[$name]['priority'] = (int) $priority; 93 | $this->sorted = false; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Remove a item. 100 | * 101 | * @param string $name 102 | * @return void 103 | */ 104 | public function remove($name) 105 | { 106 | if (isset($this->items[$name])) { 107 | $this->count--; 108 | } 109 | 110 | unset($this->items[$name]); 111 | } 112 | 113 | /** 114 | * Remove all items. 115 | * 116 | * @return void 117 | */ 118 | public function clear() 119 | { 120 | $this->items = []; 121 | $this->serial = 0; 122 | $this->count = 0; 123 | $this->sorted = false; 124 | } 125 | 126 | /** 127 | * Get a item. 128 | * 129 | * @param string $name 130 | * @return mixed 131 | */ 132 | public function get($name) 133 | { 134 | if (! isset($this->items[$name])) { 135 | return; 136 | } 137 | 138 | return $this->items[$name]['data']; 139 | } 140 | 141 | /** 142 | * Sort all items. 143 | * 144 | * @return void 145 | */ 146 | protected function sort() 147 | { 148 | if (! $this->sorted) { 149 | uasort($this->items, [$this, 'compare']); 150 | $this->sorted = true; 151 | } 152 | } 153 | 154 | /** 155 | * Compare the priority of two items. 156 | * 157 | * @param array $item1, 158 | * @param array $item2 159 | * @return int 160 | */ 161 | protected function compare(array $item1, array $item2) 162 | { 163 | return ($item1['priority'] === $item2['priority']) 164 | ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO 165 | : ($item1['priority'] > $item2['priority'] ? -1 : 1); 166 | } 167 | 168 | /** 169 | * Get/Set serial order mode 170 | * 171 | * @param bool|null $flag 172 | * 173 | * @return bool 174 | */ 175 | public function isLIFO($flag = null) 176 | { 177 | if ($flag !== null) { 178 | $isLifo = $flag === true ? 1 : -1; 179 | 180 | if ($isLifo !== $this->isLIFO) { 181 | $this->isLIFO = $isLifo; 182 | $this->sorted = false; 183 | } 184 | } 185 | 186 | return 1 === $this->isLIFO; 187 | } 188 | 189 | /** 190 | * {@inheritDoc} 191 | */ 192 | public function rewind() 193 | { 194 | $this->sort(); 195 | reset($this->items); 196 | } 197 | 198 | /** 199 | * {@inheritDoc} 200 | */ 201 | public function current() 202 | { 203 | $this->sorted || $this->sort(); 204 | $node = current($this->items); 205 | 206 | return $node ? $node['data'] : false; 207 | } 208 | 209 | /** 210 | * {@inheritDoc} 211 | */ 212 | public function key() 213 | { 214 | $this->sorted || $this->sort(); 215 | return key($this->items); 216 | } 217 | 218 | /** 219 | * {@inheritDoc} 220 | */ 221 | public function next() 222 | { 223 | $node = next($this->items); 224 | 225 | return $node ? $node['data'] : false; 226 | } 227 | 228 | /** 229 | * {@inheritDoc} 230 | */ 231 | public function valid() 232 | { 233 | return current($this->items) !== false; 234 | } 235 | 236 | /** 237 | * @return self 238 | */ 239 | public function getIterator() 240 | { 241 | return clone $this; 242 | } 243 | 244 | /** 245 | * {@inheritDoc} 246 | */ 247 | public function count() 248 | { 249 | return $this->count; 250 | } 251 | 252 | /** 253 | * Return list as array 254 | * 255 | * @param int $flag 256 | * 257 | * @return array 258 | */ 259 | public function toArray($flag = self::EXTR_DATA) 260 | { 261 | $this->sort(); 262 | 263 | if ($flag == self::EXTR_BOTH) { 264 | return $this->items; 265 | } 266 | 267 | return array_map( 268 | function ($item) use ($flag) { 269 | return ($flag == PriorityList::EXTR_PRIORITY) ? $item['priority'] : $item['data']; 270 | }, 271 | $this->items 272 | ); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/PriorityQueue.php: -------------------------------------------------------------------------------- 1 | items[] = [ 66 | 'data' => $data, 67 | 'priority' => $priority, 68 | ]; 69 | $this->getQueue()->insert($data, $priority); 70 | return $this; 71 | } 72 | 73 | /** 74 | * Remove an item from the queue 75 | * 76 | * This is different than {@link extract()}; its purpose is to dequeue an 77 | * item. 78 | * 79 | * This operation is potentially expensive, as it requires 80 | * re-initialization and re-population of the inner queue. 81 | * 82 | * Note: this removes the first item matching the provided item found. If 83 | * the same item has been added multiple times, it will not remove other 84 | * instances. 85 | * 86 | * @param mixed $datum 87 | * @return bool False if the item was not found, true otherwise. 88 | */ 89 | public function remove($datum) 90 | { 91 | $found = false; 92 | foreach ($this->items as $key => $item) { 93 | if ($item['data'] === $datum) { 94 | $found = true; 95 | break; 96 | } 97 | } 98 | if ($found) { 99 | unset($this->items[$key]); 100 | $this->queue = null; 101 | 102 | if (! $this->isEmpty()) { 103 | $queue = $this->getQueue(); 104 | foreach ($this->items as $item) { 105 | $queue->insert($item['data'], $item['priority']); 106 | } 107 | } 108 | return true; 109 | } 110 | return false; 111 | } 112 | 113 | /** 114 | * Is the queue empty? 115 | * 116 | * @return bool 117 | */ 118 | public function isEmpty() 119 | { 120 | return (0 === $this->count()); 121 | } 122 | 123 | /** 124 | * How many items are in the queue? 125 | * 126 | * @return int 127 | */ 128 | public function count() 129 | { 130 | return count($this->items); 131 | } 132 | 133 | /** 134 | * Peek at the top node in the queue, based on priority. 135 | * 136 | * @return mixed 137 | */ 138 | public function top() 139 | { 140 | return $this->getIterator()->top(); 141 | } 142 | 143 | /** 144 | * Extract a node from the inner queue and sift up 145 | * 146 | * @return mixed 147 | */ 148 | public function extract() 149 | { 150 | return $this->getQueue()->extract(); 151 | } 152 | 153 | /** 154 | * Retrieve the inner iterator 155 | * 156 | * SplPriorityQueue acts as a heap, which typically implies that as items 157 | * are iterated, they are also removed. This does not work for situations 158 | * where the queue may be iterated multiple times. As such, this class 159 | * aggregates the values, and also injects an SplPriorityQueue. This method 160 | * retrieves the inner queue object, and clones it for purposes of 161 | * iteration. 162 | * 163 | * @return SplPriorityQueue 164 | */ 165 | public function getIterator() 166 | { 167 | $queue = $this->getQueue(); 168 | return clone $queue; 169 | } 170 | 171 | /** 172 | * Serialize the data structure 173 | * 174 | * @return string 175 | */ 176 | public function serialize() 177 | { 178 | return serialize($this->items); 179 | } 180 | 181 | /** 182 | * Unserialize a string into a PriorityQueue object 183 | * 184 | * Serialization format is compatible with {@link Zend\Stdlib\SplPriorityQueue} 185 | * 186 | * @param string $data 187 | * @return void 188 | */ 189 | public function unserialize($data) 190 | { 191 | foreach (unserialize($data) as $item) { 192 | $this->insert($item['data'], $item['priority']); 193 | } 194 | } 195 | 196 | /** 197 | * Serialize to an array 198 | * 199 | * By default, returns only the item data, and in the order registered (not 200 | * sorted). You may provide one of the EXTR_* flags as an argument, allowing 201 | * the ability to return priorities or both data and priority. 202 | * 203 | * @param int $flag 204 | * @return array 205 | */ 206 | public function toArray($flag = self::EXTR_DATA) 207 | { 208 | switch ($flag) { 209 | case self::EXTR_BOTH: 210 | return $this->items; 211 | case self::EXTR_PRIORITY: 212 | return array_map(function ($item) { 213 | return $item['priority']; 214 | }, $this->items); 215 | case self::EXTR_DATA: 216 | default: 217 | return array_map(function ($item) { 218 | return $item['data']; 219 | }, $this->items); 220 | } 221 | } 222 | 223 | /** 224 | * Specify the internal queue class 225 | * 226 | * Please see {@link getIterator()} for details on the necessity of an 227 | * internal queue class. The class provided should extend SplPriorityQueue. 228 | * 229 | * @param string $class 230 | * @return PriorityQueue 231 | */ 232 | public function setInternalQueueClass($class) 233 | { 234 | $this->queueClass = (string) $class; 235 | return $this; 236 | } 237 | 238 | /** 239 | * Does the queue contain the given datum? 240 | * 241 | * @param mixed $datum 242 | * @return bool 243 | */ 244 | public function contains($datum) 245 | { 246 | foreach ($this->items as $item) { 247 | if ($item['data'] === $datum) { 248 | return true; 249 | } 250 | } 251 | return false; 252 | } 253 | 254 | /** 255 | * Does the queue have an item with the given priority? 256 | * 257 | * @param int $priority 258 | * @return bool 259 | */ 260 | public function hasPriority($priority) 261 | { 262 | foreach ($this->items as $item) { 263 | if ($item['priority'] === $priority) { 264 | return true; 265 | } 266 | } 267 | return false; 268 | } 269 | 270 | /** 271 | * Get the inner priority queue instance 272 | * 273 | * @throws Exception\DomainException 274 | * @return SplPriorityQueue 275 | */ 276 | protected function getQueue() 277 | { 278 | if (null === $this->queue) { 279 | $this->queue = new $this->queueClass(); 280 | if (! $this->queue instanceof \SplPriorityQueue) { 281 | throw new Exception\DomainException(sprintf( 282 | 'PriorityQueue expects an internal queue of type SplPriorityQueue; received "%s"', 283 | get_class($this->queue) 284 | )); 285 | } 286 | } 287 | return $this->queue; 288 | } 289 | 290 | /** 291 | * Add support for deep cloning 292 | * 293 | * @return void 294 | */ 295 | public function __clone() 296 | { 297 | if (null !== $this->queue) { 298 | $this->queue = clone $this->queue; 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | serial--]; 41 | } 42 | parent::insert($datum, $priority); 43 | } 44 | 45 | /** 46 | * Serialize to an array 47 | * 48 | * Array will be priority => data pairs 49 | * 50 | * @return array 51 | */ 52 | public function toArray() 53 | { 54 | $array = []; 55 | foreach (clone $this as $item) { 56 | $array[] = $item; 57 | } 58 | return $array; 59 | } 60 | 61 | /** 62 | * Serialize 63 | * 64 | * @return string 65 | */ 66 | public function serialize() 67 | { 68 | $clone = clone $this; 69 | $clone->setExtractFlags(self::EXTR_BOTH); 70 | 71 | $data = []; 72 | foreach ($clone as $item) { 73 | $data[] = $item; 74 | } 75 | 76 | return serialize($data); 77 | } 78 | 79 | /** 80 | * Deserialize 81 | * 82 | * @param string $data 83 | * @return void 84 | */ 85 | public function unserialize($data) 86 | { 87 | $this->serial = PHP_INT_MAX; 88 | foreach (unserialize($data) as $item) { 89 | $this->serial--; 90 | $this->insert($item['data'], $item['priority']); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/SplQueue.php: -------------------------------------------------------------------------------- 1 | toArray()); 41 | } 42 | 43 | /** 44 | * Unserialize 45 | * 46 | * @param string $data 47 | * @return void 48 | */ 49 | public function unserialize($data) 50 | { 51 | foreach (unserialize($data) as $item) { 52 | $this->push($item); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/SplStack.php: -------------------------------------------------------------------------------- 1 | toArray()); 41 | } 42 | 43 | /** 44 | * Unserialize 45 | * 46 | * @param string $data 47 | * @return void 48 | */ 49 | public function unserialize($data) 50 | { 51 | foreach (unserialize($data) as $item) { 52 | $this->unshift($item); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/StringUtils.php: -------------------------------------------------------------------------------- 1 | setEncoding($encoding, $convertEncoding); 131 | return $wrapper; 132 | } 133 | } 134 | 135 | throw new Exception\RuntimeException( 136 | 'No wrapper found supporting "' . $encoding . '"' 137 | . (($convertEncoding !== null) ? ' and "' . $convertEncoding . '"' : '') 138 | ); 139 | } 140 | 141 | /** 142 | * Get a list of all known single-byte character encodings 143 | * 144 | * @return string[] 145 | */ 146 | public static function getSingleByteEncodings() 147 | { 148 | return static::$singleByteEncodings; 149 | } 150 | 151 | /** 152 | * Check if a given encoding is a known single-byte character encoding 153 | * 154 | * @param string $encoding 155 | * @return bool 156 | */ 157 | public static function isSingleByteEncoding($encoding) 158 | { 159 | return in_array(strtoupper($encoding), static::$singleByteEncodings); 160 | } 161 | 162 | /** 163 | * Check if a given string is valid UTF-8 encoded 164 | * 165 | * @param string $str 166 | * @return bool 167 | */ 168 | public static function isValidUtf8($str) 169 | { 170 | return is_string($str) && ($str === '' || preg_match('/^./su', $str) == 1); 171 | } 172 | 173 | /** 174 | * Is PCRE compiled with Unicode support? 175 | * 176 | * @return bool 177 | */ 178 | public static function hasPcreUnicodeSupport() 179 | { 180 | if (static::$hasPcreUnicodeSupport === null) { 181 | ErrorHandler::start(); 182 | static::$hasPcreUnicodeSupport = defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') == 1; 183 | ErrorHandler::stop(); 184 | } 185 | return static::$hasPcreUnicodeSupport; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/StringWrapper/AbstractStringWrapper.php: -------------------------------------------------------------------------------- 1 | convertEncoding = $convertEncodingUpper; 79 | } else { 80 | $this->convertEncoding = null; 81 | } 82 | $this->encoding = $encodingUpper; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Get the defined character encoding to work with 89 | * 90 | * @return string 91 | * @throws Exception\LogicException If no encoding was defined 92 | */ 93 | public function getEncoding() 94 | { 95 | return $this->encoding; 96 | } 97 | 98 | /** 99 | * Get the defined character encoding to convert to 100 | * 101 | * @return string|null 102 | */ 103 | public function getConvertEncoding() 104 | { 105 | return $this->convertEncoding; 106 | } 107 | 108 | /** 109 | * Convert a string from defined character encoding to the defined convert encoding 110 | * 111 | * @param string $str 112 | * @param bool $reverse 113 | * @return string|false 114 | */ 115 | public function convert($str, $reverse = false) 116 | { 117 | $encoding = $this->getEncoding(); 118 | $convertEncoding = $this->getConvertEncoding(); 119 | if ($convertEncoding === null) { 120 | throw new Exception\LogicException( 121 | 'No convert encoding defined' 122 | ); 123 | } 124 | 125 | if ($encoding === $convertEncoding) { 126 | return $str; 127 | } 128 | 129 | $from = $reverse ? $convertEncoding : $encoding; 130 | $to = $reverse ? $encoding : $convertEncoding; 131 | throw new Exception\RuntimeException(sprintf( 132 | 'Converting from "%s" to "%s" isn\'t supported by this string wrapper', 133 | $from, 134 | $to 135 | )); 136 | } 137 | 138 | /** 139 | * Wraps a string to a given number of characters 140 | * 141 | * @param string $string 142 | * @param int $width 143 | * @param string $break 144 | * @param bool $cut 145 | * @return string|false 146 | */ 147 | public function wordWrap($string, $width = 75, $break = "\n", $cut = false) 148 | { 149 | $string = (string) $string; 150 | if ($string === '') { 151 | return ''; 152 | } 153 | 154 | $break = (string) $break; 155 | if ($break === '') { 156 | throw new Exception\InvalidArgumentException('Break string cannot be empty'); 157 | } 158 | 159 | $width = (int) $width; 160 | if ($width === 0 && $cut) { 161 | throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); 162 | } 163 | 164 | if (StringUtils::isSingleByteEncoding($this->getEncoding())) { 165 | return wordwrap($string, $width, $break, $cut); 166 | } 167 | 168 | $stringWidth = $this->strlen($string); 169 | $breakWidth = $this->strlen($break); 170 | 171 | $result = ''; 172 | $lastStart = $lastSpace = 0; 173 | 174 | for ($current = 0; $current < $stringWidth; $current++) { 175 | $char = $this->substr($string, $current, 1); 176 | 177 | $possibleBreak = $char; 178 | if ($breakWidth !== 1) { 179 | $possibleBreak = $this->substr($string, $current, $breakWidth); 180 | } 181 | 182 | if ($possibleBreak === $break) { 183 | $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth); 184 | $current += $breakWidth - 1; 185 | $lastStart = $lastSpace = $current + 1; 186 | continue; 187 | } 188 | 189 | if ($char === ' ') { 190 | if ($current - $lastStart >= $width) { 191 | $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; 192 | $lastStart = $current + 1; 193 | } 194 | 195 | $lastSpace = $current; 196 | continue; 197 | } 198 | 199 | if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { 200 | $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; 201 | $lastStart = $lastSpace = $current; 202 | continue; 203 | } 204 | 205 | if ($current - $lastStart >= $width && $lastStart < $lastSpace) { 206 | $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break; 207 | $lastStart = $lastSpace = $lastSpace + 1; 208 | continue; 209 | } 210 | } 211 | 212 | if ($lastStart !== $current) { 213 | $result .= $this->substr($string, $lastStart, $current - $lastStart); 214 | } 215 | 216 | return $result; 217 | } 218 | 219 | /** 220 | * Pad a string to a certain length with another string 221 | * 222 | * @param string $input 223 | * @param int $padLength 224 | * @param string $padString 225 | * @param int $padType 226 | * @return string 227 | */ 228 | public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) 229 | { 230 | if (StringUtils::isSingleByteEncoding($this->getEncoding())) { 231 | return str_pad($input, $padLength, $padString, $padType); 232 | } 233 | 234 | $lengthOfPadding = $padLength - $this->strlen($input); 235 | if ($lengthOfPadding <= 0) { 236 | return $input; 237 | } 238 | 239 | $padStringLength = $this->strlen($padString); 240 | if ($padStringLength === 0) { 241 | return $input; 242 | } 243 | 244 | $repeatCount = floor($lengthOfPadding / $padStringLength); 245 | 246 | if ($padType === STR_PAD_BOTH) { 247 | $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; 248 | 249 | $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; 250 | $lastStringLeftLength = $lastStringRightLength = floor($lastStringLength / 2); 251 | $lastStringRightLength += $lastStringLength % 2; 252 | 253 | $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength); 254 | $lastStringRight = $this->substr($padString, 0, $lastStringRightLength); 255 | 256 | return str_repeat($padString, $repeatCountLeft) . $lastStringLeft 257 | . $input 258 | . str_repeat($padString, $repeatCountRight) . $lastStringRight; 259 | } 260 | 261 | $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength); 262 | 263 | if ($padType === STR_PAD_LEFT) { 264 | return str_repeat($padString, $repeatCount) . $lastString . $input; 265 | } 266 | 267 | return $input . str_repeat($padString, $repeatCount) . $lastString; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/StringWrapper/Iconv.php: -------------------------------------------------------------------------------- 1 | getEncoding()); 233 | } 234 | 235 | /** 236 | * Returns the portion of string specified by the start and length parameters 237 | * 238 | * @param string $str 239 | * @param int $offset 240 | * @param int|null $length 241 | * @return string|false 242 | */ 243 | public function substr($str, $offset = 0, $length = null) 244 | { 245 | return iconv_substr($str, $offset, $length, $this->getEncoding()); 246 | } 247 | 248 | /** 249 | * Find the position of the first occurrence of a substring in a string 250 | * 251 | * @param string $haystack 252 | * @param string $needle 253 | * @param int $offset 254 | * @return int|false 255 | */ 256 | public function strpos($haystack, $needle, $offset = 0) 257 | { 258 | return iconv_strpos($haystack, $needle, $offset, $this->getEncoding()); 259 | } 260 | 261 | /** 262 | * Convert a string from defined encoding to the defined convert encoding 263 | * 264 | * @param string $str 265 | * @param bool $reverse 266 | * @return string|false 267 | */ 268 | public function convert($str, $reverse = false) 269 | { 270 | $encoding = $this->getEncoding(); 271 | $convertEncoding = $this->getConvertEncoding(); 272 | if ($convertEncoding === null) { 273 | throw new Exception\LogicException( 274 | 'No convert encoding defined' 275 | ); 276 | } 277 | 278 | if ($encoding === $convertEncoding) { 279 | return $str; 280 | } 281 | 282 | $fromEncoding = $reverse ? $convertEncoding : $encoding; 283 | $toEncoding = $reverse ? $encoding : $convertEncoding; 284 | 285 | // automatically add "//IGNORE" to not stop converting on invalid characters 286 | // invalid characters triggers a notice anyway 287 | return iconv($fromEncoding, $toEncoding . '//IGNORE', $str); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/StringWrapper/Intl.php: -------------------------------------------------------------------------------- 1 | getEncoding()); 67 | } 68 | 69 | /** 70 | * Returns the portion of string specified by the start and length parameters 71 | * 72 | * @param string $str 73 | * @param int $offset 74 | * @param int|null $length 75 | * @return string|false 76 | */ 77 | public function substr($str, $offset = 0, $length = null) 78 | { 79 | return mb_substr($str, $offset, $length, $this->getEncoding()); 80 | } 81 | 82 | /** 83 | * Find the position of the first occurrence of a substring in a string 84 | * 85 | * @param string $haystack 86 | * @param string $needle 87 | * @param int $offset 88 | * @return int|false 89 | */ 90 | public function strpos($haystack, $needle, $offset = 0) 91 | { 92 | return mb_strpos($haystack, $needle, $offset, $this->getEncoding()); 93 | } 94 | 95 | /** 96 | * Convert a string from defined encoding to the defined convert encoding 97 | * 98 | * @param string $str 99 | * @param bool $reverse 100 | * @return string|false 101 | */ 102 | public function convert($str, $reverse = false) 103 | { 104 | $encoding = $this->getEncoding(); 105 | $convertEncoding = $this->getConvertEncoding(); 106 | 107 | if ($convertEncoding === null) { 108 | throw new Exception\LogicException( 109 | 'No convert encoding defined' 110 | ); 111 | } 112 | 113 | if ($encoding === $convertEncoding) { 114 | return $str; 115 | } 116 | 117 | $fromEncoding = $reverse ? $convertEncoding : $encoding; 118 | $toEncoding = $reverse ? $encoding : $convertEncoding; 119 | return mb_convert_encoding($str, $toEncoding, $fromEncoding); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/StringWrapper/Native.php: -------------------------------------------------------------------------------- 1 | convertEncoding = $encodingUpper; 80 | } 81 | 82 | if ($convertEncoding !== null) { 83 | if ($encodingUpper !== strtoupper($convertEncoding)) { 84 | throw new Exception\InvalidArgumentException( 85 | 'Wrapper doesn\'t support to convert between character encodings' 86 | ); 87 | } 88 | 89 | $this->convertEncoding = $encodingUpper; 90 | } else { 91 | $this->convertEncoding = null; 92 | } 93 | $this->encoding = $encodingUpper; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Returns the length of the given string 100 | * 101 | * @param string $str 102 | * @return int|false 103 | */ 104 | public function strlen($str) 105 | { 106 | return strlen($str); 107 | } 108 | 109 | /** 110 | * Returns the portion of string specified by the start and length parameters 111 | * 112 | * @param string $str 113 | * @param int $offset 114 | * @param int|null $length 115 | * @return string|false 116 | */ 117 | public function substr($str, $offset = 0, $length = null) 118 | { 119 | return substr($str, $offset, $length); 120 | } 121 | 122 | /** 123 | * Find the position of the first occurrence of a substring in a string 124 | * 125 | * @param string $haystack 126 | * @param string $needle 127 | * @param int $offset 128 | * @return int|false 129 | */ 130 | public function strpos($haystack, $needle, $offset = 0) 131 | { 132 | return strpos($haystack, $needle, $offset); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/StringWrapper/StringWrapperInterface.php: -------------------------------------------------------------------------------- 1 |