Repository: zakirullin/mess Branch: master Commit: 76f79b3507be Files: 28 Total size: 92.4 KB Directory structure: gitextract_1f2ttfmh/ ├── .github/ │ └── workflows/ │ └── php.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── infection.json.dist ├── phpunit.xml.dist ├── psalm.xml.dist ├── src/ │ ├── Enum/ │ │ └── TypeEnum.php │ ├── Exception/ │ │ ├── CannotModifyMessException.php │ │ ├── MessExceptionInterface.php │ │ ├── MissingKeyException.php │ │ ├── UncastableValueException.php │ │ ├── UnexpectedKeyTypeException.php │ │ └── UnexpectedTypeException.php │ ├── Mess.php │ ├── MessInterface.php │ ├── MissingMess.php │ ├── TypedAccessor.php │ └── functions.php └── tests/ └── unit/ ├── Exception/ │ ├── MissingKeyExceptionTest.php │ ├── UncastableValueExceptionTest.php │ ├── UnexpectedKeyTypeExceptionTest.php │ └── UnexpectedTypeExceptionTest.php ├── MessTest.php └── MissingMessTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/php.yml ================================================ name: GitHub Build on: push: branches: [ "master" ] pull_request: branches: [ "master" ] permissions: contents: read jobs: build: strategy: matrix: php-versions: ['8.4', '8.3', '8.2', '8.1', '8.0', '7.4', '7.2'] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: setup PHP ${{ matrix.php-versions }} uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - name: php version check run: php -v - name: Validate composer.json run: composer validate --strict - name: run composer (install dependencies) run: composer install --prefer-dist --no-progress - name: run psalm (static analysis) run: vendor/bin/psalm - name: run unit tests ( ${{ matrix.php-versions }} ) run: composer test ================================================ FILE: .gitignore ================================================ /vendor /build /test-reports composer.lock phpunit.xml psalm.xml .phpunit.result.cache .idea ================================================ FILE: .travis.yml ================================================ language: php sudo: false php: - 8.0 before_script: - composer self-update - composer install -n script: - composer test - vendor/bin/phpunit - vendor/bin/psalm --shepherd ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [0.8.4] - 2022-04-05 ### Added - php 8.1 support ## [0.8.0] - 2020-12-19 ### Added - `float` is now supported ### Changed - trim is applied to bool so to have a consistent behaviour among all methods ## [0.7.2] - 2020-08-08 ### Changed - static methods are now functions ## [0.7.1] - 2020-08-06 ### Changed - find/get methods refactoring ## [0.7.0] - 2020-08-04 ### Changed - `find*` methods can now throw `UnexpectedValueException` & `UncastableValueException` - renamed `Finder` to `Checker` (not to confuse with the logic in `find*` methods) ## [0.6.0] - 2020-08-03 ### Added - access to object's properties through `Mess` (useful when doing `json_decode`) - `Throwable` to `MessExceptionInterface` ## [0.5.5] - 2020-08-02 ### Added - case insensitive 'true' & 'false' to bool casting ## [0.5.4] - 2020-08-02 ### Removed - 'true' & 'false' to bool casting ## [0.5.3] - 2020-08-02 ### Removed - 'true' & 'false' to bool casting ## [0.5.1] - 2020-08-01 ### Added - `getArrayOfStringToMixed()` - `findArrayOfStringToMixed()` ## [0.5.0] - 2020-08-01 ### Added - It is Mess now 🍺 ## [0.4.0] - 2020-08-01 ### Removed - `TypedAccessor` (deprecated, use `Mess` instead) ## [0.3.1] - 2020-08-01 ### Added - `Mess` alias (can be used instead long and boring `TypedAccessor`) - more tests ## [0.3.0] - 2020-08-01 ### Added - `getObject()` - `getArray()` - `getArrayOfStringToInt()` - `getArrayOfStringToBool()` - `getArrayOfStringToString()` ### Fix - static classes instead of stateless type objects - separate finder classes - type asserts ## [0.2.1] - 2020-07-18 ### Fix - Psalm warnings (errorLevel="1") ## [0.2.0] - 2020-07-17 ### Added - Separate type classes - List types ## [0.1.0] - 2020-05-21 ### Added - Initial release ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2018 Artem Zakirullin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ## Mess ![GitHub Build Status](https://github.com/zakirullin/mess/actions/workflows/php.yml/badge.svg) ![Psalm coverage](https://shepherd.dev/github/zakirullin/mess/coverage.svg?) ![PHP from Packagist](https://img.shields.io/packagist/php-v/zakirullin/mess.svg?style=flat-square) [![Latest Stable Version](https://poser.pugx.org/zakirullin/mess/v/stable.svg)](https://packagist.org/packages/zakirullin/mess) ![GitHub commits](https://img.shields.io/github/commits-since/zakirullin/mess/0.1.0.svg?style=flat-square) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) ## We face a few problems in our PHP projects - Illogical type casting (`PHP`'s native implementation is way too "smart") - Pointless casts like `array => float` are **allowed** - Boilerplate code to work with arrays (check if `isset()`, throw an exception, cast the type, etc.) Consider an example: ```php $userId = $queryParams['userId'] ?? null; if ($userId === null) { throw ... } $userId = (int)$userId; ``` ## Way too verbose. Any ideas? ```php $userId = (new Mess($queryParams))['userId']->getAsInt(); ``` You can mess with API responses/configs/whatever: ```php $mess = new Mess($response); $book = new Book( $mess['title']->getString(), $mess['isBestseller']->getBool(), $mess['stats']['rating']->getInt(), $mess['tags']->getListOfString() ); ``` ## Generics support (Psalm compatible) - `getListOfString()` - `getListOfInt()` - `getArrayOfStringToString()` - `getArrayOfStringToBool()` - etc. ## Installation ```bash $ composer require zakirullin/mess ``` ## Dealing with mess ```php $queryParams = new Mess(['isDeleted' => 'true']); $queryParams['isDeleted']->getBool(); // UnexpectedTypeException $queryParams['isDeleted']->getAsBool(); // true $value = new Mess('25'); $value->getInt(); // UnexpectedTypeException $value->getAsInt(); // 25 $value->getString(); // '25' $value = new Mess('25a'); $value->getInt(); // UnexpectedTypeException $value->getAsInt(); // UncastableValueException $config = new Mess(['param' => '1']); $config['a']['b']->getInt(); // MissingKeyException: "MissingKeyException: a.b" $config['a']['b']->findInt(); // null $config['param']->getInt(); // UnexpectedTypeException $config['param']->getAsInt(); // 1 $config['param']->findInt(); // UnexpectedTypeException $config['param']->findAsInt(); // 1 ``` As you might notice, type casting is performed while using `(find|get)As*` methods. Having trouble grasping `get*()`/`find*()`? Check out brilliant [Ocramius's slides](https://ocramius.github.io/doctrine-best-practices/#/94). ## Type casting with Mess is rather predictable ```php '\d+' => int // OK 'buzz12' => int // UncastableValueException bool => int // UncastableValueException array => int // UncastableValueException object => int // UncastableValueException resource => int // UncastableValueException ``` Fairly simple, isn't it? Let us **fail fast**! ### Why one needs THAT naive type casting? Let's imagine a library that is configured this way: ```php $config = [ 'retries' => 5, // int 'delay' => 20, // int ] // Initialization $retries = $config['retries'] ?? null; if ($retries === null) { throw new MissingConfigKeyException(...); } ... $retries = (int)$retries; $delay = (int)$delay; ``` Client-side code: ```php $config => [ 'retries' => [5, 10, 30], // (int)array => 1 'delay' => true, // (int)bool => 1 ] ``` No matter if that's a misuse, or a result of major update: The system will continue to work. And that's the worst thing about it. It will continue to work, though, not in a way it was supposed to work. `PHP` is trying to do its best to let it work **at least somehow**. ## The library comes in handy in a variety of scenarios 🚀 - Deserialized data - Request `body`/`query` - `API` response - Config - etc. ================================================ FILE: composer.json ================================================ { "name": "zakirullin/mess", "type": "library", "description": "Convenient array-related routine & better type casting", "keywords": [ "array", "type", "assert", "cast", "mess", "language", "php", "json" ], "homepage": "https://github.com/zakirullin/mess", "license": "MIT", "authors": [ { "name": "Artem Zakirullin", "email": "artemzr@gmail.com" } ], "config": { "sort-packages": true, "allow-plugins": { "infection/extension-installer": true } }, "require": { "php": "^7.2|^8.0" }, "require-dev": { "composer/xdebug-handler": "^1.4|2.0", "infection/infection": "*", "phpunit/phpunit": "^8.0|^9.0", "symfony/string": "^5.4.43", "sanmai/pipeline": "5.2.1", "vimeo/psalm": "*" }, "autoload": { "psr-4": { "Zakirullin\\Mess\\": "src/", "Zakirullin\\TypedAccessor\\": "src/" }, "files": [ "src/functions.php" ] }, "scripts": { "test": [ "@php vendor/bin/phpunit --stderr --coverage-text" ], "coverage": "phpunit --coverage-html=coverage" }, "autoload-dev": { "psr-4": { "Zakirullin\\Mess\\Tests\\": "tests/unit/" } } } ================================================ FILE: infection.json.dist ================================================ { "timeout": 10, "source": { "directories": [ "src" ] }, "logs": { "text": "build/log/infection/text.txt", "summary": "build/log/infection/summary.txt", "debug": "build/log/infection/debug.txt", "perMutator": "build/log/infection/per-mutator.md" } } ================================================ FILE: phpunit.xml.dist ================================================ tests/unit/ tests/unit/ src/ src/Enum src/Caster src/Finder ================================================ FILE: psalm.xml.dist ================================================ ================================================ FILE: src/Enum/TypeEnum.php ================================================ '; public const LIST_OF_FLOAT = 'list'; public const LIST_OF_STRING = 'list'; public const ARRAY_OF_STRING_TO_INT = 'array'; public const ARRAY_OF_STRING_TO_FLOAT = 'array'; public const ARRAY_OF_STRING_TO_BOOL = 'array'; public const ARRAY_OF_STRING_TO_STRING = 'array'; public const ARRAY_OF_STRING_TO_MIXED = 'array'; } ================================================ FILE: src/Exception/CannotModifyMessException.php ================================================ * * @var array */ private $keySequence; /** * @psalm-param list $keySequence * * @param array $keySequence * @param Throwable|null $previous */ public function __construct(array $keySequence, Throwable $previous = null) { $this->keySequence = $keySequence; $message = "Mess cannot modify it's value"; parent::__construct($message, 0, $previous); } /** * @psalm-return list * * @return array */ public function getKeySequence(): array { return $this->keySequence; } } ================================================ FILE: src/Exception/MessExceptionInterface.php ================================================ * * @return array */ public function getKeySequence(): array; } ================================================ FILE: src/Exception/MissingKeyException.php ================================================ * * @var array */ private $keySequence; /** * @psalm-param list $keySequence * * @param array $keySequence * @param Throwable|null $previous */ public function __construct(array $keySequence, Throwable $previous = null) { $this->keySequence = $keySequence; $message = "Missing key: '{$this->getAbsoluteKey()}'"; parent::__construct($message, 0, $previous); } /** * @psalm-return list * * @return array */ public function getKeySequence(): array { return $this->keySequence; } /** * @return string */ private function getAbsoluteKey(): string { return implode('.', $this->keySequence); } } ================================================ FILE: src/Exception/UncastableValueException.php ================================================ * * @var array */ private $keySequence; /** * @psalm-param list $keySequence * * @param string $desiredType * @param mixed $value * @param array $keySequence * @param Throwable|null $previous */ public function __construct(string $desiredType, $value, array $keySequence, Throwable $previous = null) { $this->desiredType = $desiredType; $this->value = $value; $this->keySequence = $keySequence; $actualType = gettype($value); $message = "Cannot cast value of type '{$actualType}' to '{$desiredType}'"; parent::__construct($message, 0, $previous); } /** * @return string */ public function getDesiredType(): string { return $this->desiredType; } /** * @return mixed */ public function getValue() { return $this->value; } /** * @psalm-return list * * @return array */ public function getKeySequence(): array { return $this->keySequence; } } ================================================ FILE: src/Exception/UnexpectedKeyTypeException.php ================================================ * * @var array */ private $keySequence; /** * @psalm-param list $keySequence * * @param mixed $key * @param array $keySequence * @param Throwable|null $previous */ public function __construct($key, array $keySequence, Throwable $previous = null) { $this->key = $key; $this->keySequence = $keySequence; $keyType = gettype($key); $message = "Unexpected key type: '{$keyType}', expected key types are: string, integer"; parent::__construct($message, 0, $previous); } /** * @return mixed */ public function getKey() { return $this->key; } /** * @psalm-return list * * @return array */ public function getKeySequence(): array { return $this->keySequence; } } ================================================ FILE: src/Exception/UnexpectedTypeException.php ================================================ * * @var array */ private $keySequence; /** * @psalm-param list $keySequence * * @param string $expectedType * @param mixed $value * @param array $keySequence * @param Throwable|null $previous */ public function __construct(string $expectedType, $value, array $keySequence, Throwable $previous = null) { $this->expectedType = $expectedType; $this->value = $value; $this->keySequence = $keySequence; $actualType = gettype($value); $message = "Expected type for key '{$this->getAbsoluteKey()}' is '{$expectedType}', got '{$actualType}' instead"; parent::__construct($message, 0, $previous); } /** * @return string */ public function getExpectedType(): string { return $this->expectedType; } /** * @return mixed */ public function getValue() { return $this->value; } /** * @psalm-return list * * @return array */ public function getKeySequence(): array { return $this->keySequence; } /** * @return string */ private function getAbsoluteKey(): string { return sprintf('[%s]', implode('.', $this->keySequence)); } } ================================================ FILE: src/Mess.php ================================================ * * @var array */ private $keySequence = []; /** * @param mixed $value */ public function __construct($value) { $this->value = $value; } /** * @return int */ public function getInt(): int { $this->assertType(is_int($this->value), TypeEnum::INT); /** * @var int */ return $this->value; } /** * @return float */ public function getFloat(): float { $this->assertType(is_float($this->value), TypeEnum::FLOAT); /** * @var float */ return $this->value; } /** * @return bool */ public function getBool(): bool { $this->assertType(is_bool($this->value), TypeEnum::BOOL); /** * @var bool */ return $this->value; } /** * @return string */ public function getString(): string { $this->assertType(is_string($this->value), TypeEnum::STRING); /** * @var string */ return $this->value; } /** * @psalm-return list * * @return array */ public function getListOfInt(): array { $this->assertType(isListOfType($this->value, 'is_int'), TypeEnum::LIST_OF_INT); /** * @psalm-var list */ return $this->value; } /** * @psalm-return list * * @return array */ public function getListOfFloat(): array { $this->assertType(isListOfType($this->value, 'is_float'), TypeEnum::FLOAT); /** * @psalm-var list */ return $this->value; } /** * @psalm-return list * * @return array */ public function getListOfString(): array { $this->assertType(isListOfType($this->value, 'is_string'), TypeEnum::LIST_OF_STRING); /** * @psalm-var list */ return $this->value; } /** * @psalm-return array * * @return array */ public function getArrayOfStringToInt(): array { $this->assertType(isArrayOfStringToType($this->value, 'is_int'), TypeEnum::ARRAY_OF_STRING_TO_INT); /** * @psalm-var array */ return $this->value; } /** * @psalm-return array * * @return array */ public function getArrayOfStringToFloat(): array { $this->assertType(isArrayOfStringToType($this->value, 'is_float'), TypeEnum::ARRAY_OF_STRING_TO_FLOAT); /** * @psalm-var array */ return $this->value; } /** * @psalm-return array * * @return array */ public function getArrayOfStringToBool(): array { $this->assertType( isArrayOfStringToType($this->value, 'is_bool'), TypeEnum::ARRAY_OF_STRING_TO_BOOL ); /** * @var array */ return $this->value; } /** * @psalm-return array * * @return array */ public function getArrayOfStringToString(): array { $this->assertType( isArrayOfStringToType($this->value, 'is_string'), TypeEnum::ARRAY_OF_STRING_TO_STRING ); /** * @psalm-var array */ return $this->value; } /** * @return int */ public function getAsInt(): int { $int = toInt($this->value); $this->assertCastable($int !== null, TypeEnum::INT); /** * @var int */ return $int; } /** * @return float */ public function getAsFloat(): float { $float = toFloat($this->value); $this->assertCastable($float !== null, TypeEnum::FLOAT); /** * @var float */ return $float; } /** * @return bool */ public function getAsBool(): bool { $bool = toBool($this->value); $this->assertCastable($bool !== null, TypeEnum::BOOL); return $bool; } /** * @return string */ public function getAsString(): string { $string = toString($this->value); $this->assertCastable($string !== null, TypeEnum::STRING); return $string; } /** * @psalm-return list * * @return array */ public function getAsListOfInt(): array { /** * @psalm-var list|null */ $listOfInt = toListOfType($this->value, '\Zakirullin\Mess\toInt'); $this->assertCastable($listOfInt !== null, TypeEnum::LIST_OF_INT); return $listOfInt; } /** * @psalm-return list * * @return array */ public function getAsListOfFloat(): array { /** * @psalm-var list|null */ $listOfFloat = toListOfType($this->value, '\Zakirullin\Mess\toFloat'); $this->assertCastable($listOfFloat !== null, TypeEnum::LIST_OF_FLOAT); return $listOfFloat; } /** * @psalm-return list * * @return array */ public function getAsListOfString(): array { /** * @psalm-var list|null */ $listOfString = toListOfType($this->value, '\Zakirullin\Mess\toString'); $this->assertCastable($listOfString !== null, TypeEnum::LIST_OF_STRING); return $listOfString; } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToInt(): array { /** * @psalm-var array|null */ $arrayOfStringToInt = toArrayOfStringToType($this->value, '\Zakirullin\Mess\toInt'); $this->assertCastable($arrayOfStringToInt !== null, TypeEnum::ARRAY_OF_STRING_TO_INT); return $arrayOfStringToInt; } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToFloat(): array { /** * @psalm-var array|null */ $arrayOfStringToFloat = toArrayOfStringToType($this->value, '\Zakirullin\Mess\toFloat'); $this->assertCastable($arrayOfStringToFloat !== null, TypeEnum::ARRAY_OF_STRING_TO_FLOAT); return $arrayOfStringToFloat; } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToBool(): array { /** * @psalm-var array|null */ $arrayOfStringToBool = toArrayOfStringToType($this->value, '\Zakirullin\Mess\toBool'); $this->assertCastable($arrayOfStringToBool !== null, TypeEnum::ARRAY_OF_STRING_TO_BOOL); return $arrayOfStringToBool; } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToString(): array { /** * @psalm-var array|null */ $arrayOfStringToString = toArrayOfStringToType($this->value, '\Zakirullin\Mess\toString'); $this->assertCastable($arrayOfStringToString !== null, TypeEnum::ARRAY_OF_STRING_TO_STRING); return $arrayOfStringToString; } /** * @return int|null */ public function findInt(): ?int { if ($this->value === null) { return null; } return $this->getInt(); } /** * @return float|null */ public function findFloat(): ?float { if ($this->value === null) { return null; } return $this->getFloat(); } /** * @return bool|null */ public function findBool(): ?bool { if ($this->value === null) { return null; } return $this->getBool(); } /** * @return string|null */ public function findString(): ?string { if ($this->value === null) { return null; } return $this->getString(); } /** * @psalm-return list|null * * @return int[]|null */ public function findListOfInt(): ?array { if ($this->value === null) { return null; } return $this->getListOfInt(); } /** * @psalm-return list|null * * @return float[]|null */ public function findListOfFloat(): ?array { if ($this->value === null) { return null; } return $this->getListOfFloat(); } /** * @psalm-return list|null * * @return array|null */ public function findListOfString(): ?array { if ($this->value === null) { return null; } return $this->getListOfString(); } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToInt(): ?array { if ($this->value === null) { return null; } return $this->getArrayOfStringToInt(); } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToFloat(): ?array { if ($this->value === null) { return null; } return $this->getArrayOfStringToFloat(); } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToBool(): ?array { if ($this->value === null) { return null; } return $this->getArrayOfStringToBool(); } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToString(): ?array { if ($this->value === null) { return null; } return $this->getArrayOfStringToString(); } /** * @return int|null */ public function findAsInt(): ?int { if ($this->value === null) { return null; } return $this->getAsInt(); } /** * @return float|null */ public function findAsFloat(): ?float { if ($this->value === null) { return null; } return $this->getAsFloat(); } /** * @return bool|null */ public function findAsBool(): ?bool { if ($this->value === null) { return null; } return $this->getAsBool(); } /** * @return string|null */ public function findAsString(): ?string { if ($this->value === null) { return null; } return $this->getAsString(); } /** * @psalm-return list|null * * @return int[]|null */ public function findAsListOfInt(): ?array { if ($this->value === null) { return null; } return $this->getAsListOfInt(); } /** * @return float[]|null */ public function findAsListOfFloat(): ?array { if ($this->value === null) { return null; } return $this->getAsListOfFloat(); } /** * @psalm-return list|null * * @return string[]|null */ public function findAsListOfString(): ?array { if ($this->value === null) { return null; } return $this->getAsListOfString(); } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToInt(): ?array { if ($this->value === null) { return null; } return $this->getAsArrayOfStringToInt(); } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToFloat(): ?array { if ($this->value === null) { return null; } return $this->getAsArrayOfStringToFloat(); } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToBool(): ?array { if ($this->value === null) { return null; } return $this->getAsArrayOfStringToBool(); } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToString(): ?array { if ($this->value === null) { return null; } return $this->getAsArrayOfStringToString(); } /** * @return mixed */ public function getMixed() { return $this->value; } /** * @return object */ public function getObject(): object { $this->assertType(is_object($this->value), TypeEnum::OBJECT); /** * @var object */ return $this->value; } /** * @return array */ public function getArray(): array { $this->assertType(is_array($this->value), TypeEnum::ARRAY); /** * @var array */ return $this->value; } /** * @psalm-return array * * @return array */ public function getArrayOfStringToMixed(): array { $this->assertType(isArrayOfStringToMixed($this->value), TypeEnum::ARRAY_OF_STRING_TO_MIXED); /** * @psalm-var array */ return $this->value; } /** * @return mixed */ public function findMixed() { return $this->value; } /** * @return object|null */ public function findObject(): ?object { if ($this->value === null) { return null; } return $this->getObject(); } /** * @return array|null */ public function findArray(): ?array { if ($this->value === null) { return null; } return $this->getArray(); } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToMixed(): ?array { if ($this->value === null) { return null; } return $this->getArrayOfStringToMixed(); } /** * @param string|int $offset * @return MessInterface */ public function offsetGet($offset): MessInterface { /** * @psalm-suppress DocblockTypeContradiction */ if (!is_string($offset) && !is_int($offset)) { throw new UnexpectedKeyTypeException($offset, $this->keySequence); } $clonedKeySequence = $this->keySequence; $clonedKeySequence[] = $offset; if (!$this->offsetExists($offset)) { return new MissingMess($clonedKeySequence); } return (new self($this->getByOffset($offset)))->setKeySequence($clonedKeySequence); } /** * @param string|int $offset * @return bool */ public function offsetExists($offset): bool { if (is_array($this->value)) { return key_exists($offset, $this->value); } if (is_object($this->value) && is_string($offset)) { return property_exists($this->value, $offset); } return false; } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value): void { throw new CannotModifyMessException($this->keySequence); } /** * @param mixed $offset */ public function offsetUnset($offset): void { throw new CannotModifyMessException($this->keySequence); } /** * @psalm-param list $keySequence * * @param array $keySequence * @return Mess */ private function setKeySequence(array $keySequence): self { $this->keySequence = $keySequence; return $this; } /** * @param bool $isExpected * @param string $expectedType */ private function assertType(bool $isExpected, string $expectedType): void { if (!$isExpected) { throw new UnexpectedTypeException($expectedType, $this->value, $this->keySequence); } } /** * @param bool $isCastable * @param string $desiredType */ private function assertCastable(bool $isCastable, string $desiredType): void { if (!$isCastable) { throw new UncastableValueException($desiredType, $this->value, $this->keySequence); } } /** * @param string|int $offset * @return mixed */ private function getByOffset($offset) { /** * @var object|array $this ->value */ if (is_object($this->value) && is_string($offset)) { return $this->value->$offset; } /** * @var array $this->value */ return $this->value[$offset]; } } ================================================ FILE: src/MessInterface.php ================================================ * * @return int[] */ public function getListOfInt(): array; /** * @psalm-return list * * @return float[] */ public function getListOfFloat(): array; /** * @psalm-return list * * @return string[] */ public function getListOfString(): array; /** * @psalm-return array * * @return array */ public function getArrayOfStringToInt(): array; /** * @psalm-return array * * @return array */ public function getArrayOfStringToFloat(): array; /** * @psalm-return array * * @return array */ public function getArrayOfStringToBool(): array; /** * @psalm-return array * * @return array */ public function getArrayOfStringToString(): array; /** * @return int */ public function getAsInt(): int; /** * @return float */ public function getAsFloat(): float; /** * @return bool */ public function getAsBool(): bool; /** * @return string */ public function getAsString(): string; /** * @psalm-return list * * @return array */ public function getAsListOfInt(): array; /** * @psalm-return list * * @return array */ public function getAsListOfFloat(): array; /** * @psalm-return list * * @return array */ public function getAsListOfString(): array; /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToInt(): array; /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToFloat(): array; /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToBool(): array; /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToString(): array; /** * @return int|null */ public function findInt(): ?int; /** * @return float|null */ public function findFloat(): ?float; /** * @return bool|null */ public function findBool(): ?bool; /** * @return string|null */ public function findString(): ?string; /** * @psalm-return list|null * * @return int[]|null */ public function findListOfInt(): ?array; /** * @psalm-return list|null * * @return float[]|null */ public function findListOfFloat(): ?array; /** * @psalm-return list|null * * @return string[]|null */ public function findListOfString(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToInt(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToFloat(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToBool(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToString(): ?array; /** * @return int|null */ public function findAsInt(): ?int; /** * @return float|null */ public function findAsFloat(): ?float; /** * @return bool|null */ public function findAsBool(): ?bool; /** * @return string|null */ public function findAsString(): ?string; /** * @psalm-return list|null * * @return int[]|null */ public function findAsListOfInt(): ?array; /** * list|null * * @return float[]|null */ public function findAsListOfFloat(): ?array; /** * @psalm-return list|null * * @return string[]|null */ public function findAsListOfString(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToInt(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToFloat(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToBool(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToString(): ?array; /** * @return mixed */ public function getMixed(); /** * @return object */ public function getObject(): object; /** * @return array */ public function getArray(): array; /** * @psalm-return array * * @return array */ public function getArrayOfStringToMixed(): array; /** * @return mixed */ public function findMixed(); /** * @return object|null */ public function findObject(): ?object; /** * @return array|null */ public function findArray(): ?array; /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToMixed(): ?array; /** * @param string|int $offset * @return MessInterface */ public function offsetGet($offset): MessInterface; } ================================================ FILE: src/MissingMess.php ================================================ * * @var array */ private $keySequence; /** * @psalm-param list $keySequence * * @param array $keySequence */ public function __construct(array $keySequence) { $this->keySequence = $keySequence; } /** * @return int */ public function getInt(): int { throw new MissingKeyException($this->keySequence); } /** * @return float */ public function getFloat(): float { throw new MissingKeyException($this->keySequence); } /** * @return bool */ public function getBool(): bool { throw new MissingKeyException($this->keySequence); } /** * @return string */ public function getString(): string { throw new MissingKeyException($this->keySequence); } /** * @psalm-return list * * @return int[] */ public function getListOfInt(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return list * * @return float[] */ public function getListOfFloat(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return list * * @return string[] */ public function getListOfString(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getArrayOfStringToInt(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getArrayOfStringToFloat(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getArrayOfStringToBool(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getArrayOfStringToString(): array { throw new MissingKeyException($this->keySequence); } /** * @return int */ public function getAsInt(): int { throw new MissingKeyException($this->keySequence); } /** * @return float */ public function getAsFloat(): float { throw new MissingKeyException($this->keySequence); } /** * @return bool */ public function getAsBool(): bool { throw new MissingKeyException($this->keySequence); } /** * @return string */ public function getAsString(): string { throw new MissingKeyException($this->keySequence); } /** * @psalm-return list * * @return int[] */ public function getAsListOfInt(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return list * * @return float[] */ public function getAsListOfFloat(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return list * * @return string[] */ public function getAsListOfString(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToInt(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToFloat(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToBool(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getAsArrayOfStringToString(): array { throw new MissingKeyException($this->keySequence); } /** * @return int|null */ public function findInt(): ?int { return null; } /** * @return float|null */ public function findFloat(): ?float { return null; } /** * @return bool|null */ public function findBool(): ?bool { return null; } /** * @return string|null */ public function findString(): ?string { return null; } /** * @psalm-return list * * @return int[]|null */ public function findListOfInt(): ?array { return null; } /** * @psalm-return list * * @return float[]|null */ public function findListOfFloat(): ?array { return null; } /** * @psalm-return list * * @return string[]|null */ public function findListOfString(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToInt(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToBool(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToString(): ?array { return null; } /** * @return int|null */ public function findAsInt(): ?int { return null; } /** * @return float|null */ public function findAsFloat(): ?float { return null; } /** * @return bool|null */ public function findAsBool(): ?bool { return null; } /** * @return string|null */ public function findAsString(): ?string { return null; } /** * @psalm-return list|null * * @return int[]|null */ public function findAsListOfInt(): ?array { return null; } /** * @psalm-return list|null * * @return float[]|null */ public function findAsListOfFloat(): ?array { return null; } /** * @psalm-return list|null * * @return string[]|null */ public function findAsListOfString(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToInt(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToFloat(): ?array { return null; } /** * @pslam-pure * @psalm-return array|null * @return array|null */ public function findAsArrayOfStringToFloat(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToBool(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findAsArrayOfStringToString(): ?array { return null; } /** * @return mixed */ public function getMixed() { throw new MissingKeyException($this->keySequence); } /** * @return object */ public function getObject(): object { throw new MissingKeyException($this->keySequence); } /** * @return array */ public function getArray(): array { throw new MissingKeyException($this->keySequence); } /** * @psalm-return array * * @return array */ public function getArrayOfStringToMixed(): array { throw new MissingKeyException($this->keySequence); } /** * @return mixed */ public function findMixed() { return null; } /** * @return object|null */ public function findObject(): ?object { return null; } /** * @return array|null */ public function findArray(): ?array { return null; } /** * @psalm-return array|null * * @return array|null */ public function findArrayOfStringToMixed(): ?array { return null; } /** * @param mixed $offset * * @return bool */ public function offsetExists($offset): bool { return false; } /** * @param int|string $offset */ public function offsetGet($offset): MessInterface { /** * @psalm-suppress DocblockTypeContradiction */ if (!is_string($offset) && !is_int($offset)) { throw new UnexpectedKeyTypeException($offset, $this->keySequence); } $clonedKeySequence = $this->keySequence; $clonedKeySequence[] = $offset; return new self($clonedKeySequence); } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value): void { throw new CannotModifyMessException($this->keySequence); } /** * @param mixed $offset */ public function offsetUnset($offset): void { throw new CannotModifyMessException($this->keySequence); } } ================================================ FILE: src/TypedAccessor.php ================================================ |null * * @param mixed $value * @param pure-callable $caster * @return array|null */ function toArrayOfStringToType($value, callable $caster): ?array { if (!isArrayOfStringToMixed($value)) { return null; } $arrayOfStringToCasted = []; /** * @var array $value * @var string $key * @var mixed $val */ foreach ($value as $key => $val) { /** * @var mixed */ $castedValue = $caster($val); if ($castedValue === null) { return null; } /** * @var mixed */ $arrayOfStringToCasted[$key] = $castedValue; } return $arrayOfStringToCasted; } /** * @psalm-pure * * @param mixed $value * @return bool */ function isListOfMixed($value): bool { if (!is_array($value)) { return false; } if ($value === []) { return true; } /** * @psalm-var list */ $keys = array_keys($value); return $keys === range(0, count($value) - 1); } /** * @psalm-pure * * @param mixed $value * @param pure-callable $typeChecker * @return bool */ function isListOfType($value, callable $typeChecker): bool { if (!isListOfMixed($value)) { return false; } /** * @psalm-var list $value * @var mixed $val */ foreach ($value as $val) { if (!$typeChecker($val)) { return false; } } return true; } /** * @psalm-pure * * @param mixed $value * @return bool */ function isArrayOfStringToMixed($value): bool { if (!is_array($value)) { return false; } foreach (array_keys($value) as $key) { if (!is_string($key)) { return false; } } return true; } /** * @psalm-pure * * @param mixed $value * @param pure-callable $isType * @return bool */ function isArrayOfStringToType($value, callable $isType) { if (!isArrayOfStringToMixed($value)) { return false; } /** * @psalm-var array $value * @var mixed $val */ foreach ($value as $val) { if (!$isType($val)) { return false; } } return true; } } ================================================ FILE: tests/unit/Exception/MissingKeyExceptionTest.php ================================================ assertSame(['a', 'b', 'c'], $e->getKeySequence()); } } ================================================ FILE: tests/unit/Exception/UncastableValueExceptionTest.php ================================================ assertSame('desired', $e->getDesiredType()); } public function testGetOutputType_OutputType_ReturnsSameOutputType() { $e = new UncastableValueException('', 'value', []); $this->assertSame('value', $e->getValue()); } } ================================================ FILE: tests/unit/Exception/UnexpectedKeyTypeExceptionTest.php ================================================ assertSame('key', $e->getKey()); } } ================================================ FILE: tests/unit/Exception/UnexpectedTypeExceptionTest.php ================================================ assertSame('expected', $e->getExpectedType()); } public function testGetActualType_ActualType_ReturnsSameActualType() { $e = new UnexpectedTypeException('', 'value', []); $this->assertSame('value', $e->getValue()); } } ================================================ FILE: tests/unit/MessTest.php ================================================ getInt(); $this->assertSame(1, $actualValue); } public function testGetInt_String_ThrowsUnexpectedTypeException() { $this->expectException(UnexpectedTypeException::class); (new Mess('crusoe'))->getInt(); } public function testGetFloat_FloatValue_ReturnsSameFloatValue() { $actualValue = (new Mess(1.5))->getFloat(); $this->assertSame(1.5, $actualValue); } public function testGetBool_Bool_ReturnsSameBoolValue() { $actualValue = (new Mess(true))->getBool(); $this->assertSame(true, $actualValue); } public function testGetBool_String_ThrowsUnexpectedTypeException() { $this->expectException(UnexpectedTypeException::class); (new Mess('true'))->getBool(); } public function testGetString_String_ReturnsSameString() { $actualValue = (new Mess('crusoe'))->getString(); $this->assertSame('crusoe', $actualValue); } public function testGetString_Int_ThrowsUnexpectedTypeException() { $this->expectException(UnexpectedTypeException::class); (new Mess(1))->getString(); } public function testGetListOfInt_ListOfInt_ReturnsSameListOfInt() { $actualValue = (new Mess([1, 5, 10]))->getListOfInt(); $this->assertSame([1, 5, 10], $actualValue); } /** * @dataProvider providerNotAListOfInt */ public function testGetListOfInt_NotAListOfIntGiven_ThrowsUnexpectedTypeException($notAListOfInt) { $this->expectException(UnexpectedTypeException::class); (new Mess($notAListOfInt))->getListOfInt(); } public function providerNotAListOfInt() { return [ 'associative array' => [[1, 2 => 3]], 'array of mixed' => [[1, 'a']], 'int' => [1], ]; } public function testGetListOfFloat_ListOfFloat_ReturnsSameListOfFloat() { $actualValue = (new Mess([1.2, 0.5]))->getListOfFloat(); $this->assertSame([1.2, 0.5], $actualValue); } /** * @dataProvider providerNotAListOfFloat */ public function testGetListOfFloat_NotAListOfFloatGiven_ThrowsUnexpectedTypeException($notAListOfFloat) { $this->expectException(UnexpectedTypeException::class); (new Mess($notAListOfFloat))->getListOfFloat(); } public function providerNotAListOfFloat() { return [ 'associative array' => [[1.5, 2 => 3.5]], 'array of mixed' => [[1.5, 'a']], 'float' => [1.5], ]; } public function testGetListOfString_ListOfString_ReturnsSameListOfString() { $actualValue = (new Mess(['a', 'b']))->getListOfString(); $this->assertSame(['a', 'b'], $actualValue); } /** * @dataProvider providerNotAListOfString */ public function testGetListOfString_GivenNotAListOfString_ThrowsUnexpectedTypeException($notAListOfString) { $this->expectException(UnexpectedTypeException::class); (new Mess($notAListOfString))->getListOfString(); } public function providerNotAListOfString() { return [ 'associative array' => [['s', 2 => 's']], 'array of mixed' => [['s', 1]], 'string' => ['s'], ]; } public function testGetArrayOfStringToInt_ArrayOfStringToInt_ReturnsSameValue() { $actualValue = (new Mess(['a' => 1, 'b' => 2]))->getArrayOfStringToInt(); $this->assertSame(['a' => 1, 'b' => 2], $actualValue); } public function testGetArrayOfStringToFloat_ArrayOfStringToFloat_ReturnsSameValue() { $actualValue = (new Mess(['a' => 1.5, 'b' => 2.5]))->getArrayOfStringToFloat(); $this->assertSame(['a' => 1.5, 'b' => 2.5], $actualValue); } public function testGetArrayOfStringToBool_ArrayOfStringToBool_ReturnsSameValue() { $actualValue = (new Mess(['a' => true, 'b' => false]))->getAsArrayOfStringToBool(); $this->assertSame(['a' => true, 'b' => false], $actualValue); } public function testGetArrayOfStringToString_ArrayOfStringToString_ReturnsSameValue() { $actualValue = (new Mess(['a' => 'A', 'b' => 'B']))->getAsArrayOfStringToString(); $this->assertSame(['a' => 'A', 'b' => 'B'], $actualValue); } /** * @dataProvider providerCanCastToIntValues */ public function testGetAsInt_GivenCastableValue_ReturnsMatchingCastedValue($value, int $castedValue) { $actualValue = (new Mess($value))->getAsInt(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to int */ public function providerCanCastToIntValues() { return [ 'int' => [1, 1], 'int in float' => [1.0, 1], 'positive int in string' => ['1', 1], 'signed positive int in string' => ['+1', 1], 'negative int in string' => ['-1', -1], 'positive zero' => ['+0', 0], 'negative zero' => ['-0', 0], 'int in string with trailing spaces' => [' 1 ', 1], ]; } /** * @dataProvider providerCannotCastToIntValues */ public function testGetAsInt_GivenUncastableValue_ThrowsUncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsInt(); } /** * Cannot cast to int */ public function providerCannotCastToIntValues() { return [ 'object' => [(object) []], 'array' => [[]], 'function' => [ function () { }, ], 'boolean' => [true], 'float' => [1.1], 'float in string' => ['1.1'], 'malformed int string' => ['1 25'], 'leading zeroes' => ['001'], ]; } /** * @dataProvider providerCanCastToFloatValues */ public function testGetAsFloat_GivenCastableValue_ReturnsMatchingCastedValue($value, float $castedValue) { $actualValue = (new Mess($value))->getAsFloat(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to int */ public function providerCanCastToFloatValues() { return [ 'float' => [1.5, 1.5], 'float in positive int' => [1, 1.0], 'float in negative int' => [-1, -1.0], 'positive int in string' => ['1', 1.0], 'signed positive int in string' => ['+1', 1.0], 'negative int in string' => ['-1', -1], 'positive float in string' => ['1.5', 1.5], 'signed positive float in string' => ['+1.5', 1.5], 'negative float in string' => ['-1.5', -1.5], 'positive exponent' => ['1.5E2', 150.0], 'zero exponent' => ['1.5E0', 1.5], 'negative exponent' => ['1E-2', 0.01], 'positive zero' => ['+0', 0], 'negative zero' => ['-0', 0], 'short float in string' => ['.5', 0.5], 'float in string with trailing spaces' => [' 1.5 ', 1.5], 'leading zeroes' => ['001', 1.0], ]; } /** * @dataProvider providerCannotCastToFloatValues */ public function testGetAsFloat_GivenUncastableValue_ThrowsUncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsFloat(); } /** * Cannot cast to int */ public function providerCannotCastToFloatValues() { return [ 'object' => [(object) []], 'array' => [[]], 'function' => [ function () { }, ], 'boolean' => [true], 'malformed float string' => ['1 25'], 'malformed exponent' => ['1E'], ]; } /** * @dataProvider providerCanCastToBoolValues */ public function testGetAsBool_GivenCastableValue_ReturnsMatchingCastedValue($value, bool $castedValue) { $actualValue = (new Mess($value))->getAsBool(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to bool */ public function providerCanCastToBoolValues() { return [ 'bool true' => [true, true], 'bool false' => [false, false], '0' => [0, false], '1' => [1, true], '0 in string' => ['0', false], '1 in string' => ['1', true], 'true in string' => ['true', true], 'false in string' => ['false', false], 'bool in string with trailing spaces' => [' false ', false], ]; } /** * @dataProvider providerCannotCastToBoolValues */ public function testGetAsBool_GivenUncastableValue_ThrowsUncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsBool(); } /** * Cannot cast to bool */ public function providerCannotCastToBoolValues() { return [ 'object' => [(object) []], 'array' => [[]], 'function' => [ function () { }, ], 'float' => [1.1], 'float in string' => ['1.1'], ]; } /** * @dataProvider providerCanCastToStringValues */ public function testGetAsString_GivenCastableValue_ReturnsMatchingCastedValue($value, string $castedValue) { $actualValue = (new Mess($value))->getAsString(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to string */ public function providerCanCastToStringValues() { return [ 'string' => ['crusoe', 'crusoe'], 'int' => [1, '1'], ]; } /** * @dataProvider providerCannotCastToStringValues */ public function testGetAsString_GiveUncastableValue_ThrowsUncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsString(); } /** * Cannot cast to string */ public function providerCannotCastToStringValues() { return [ 'object' => [(object) []], 'array' => [[]], 'function' => [ function () { }, ], 'boolean' => [true], ]; } /** * @dataProvider providerCanCastToListOfIntValues */ public function testGetAsListOfInt_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->getAsListOfInt(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to list of int */ public function providerCanCastToListOfIntValues() { return [ 'empty list' => [[], []], 'list of int' => [[1], [1]], 'list of castable to int' => [['1'], [1]], ]; } /** * @dataProvider providerCannotCastToListOfIntValues */ public function testGetAsListOfInt_GivenUncastableValue_ThrowsUuncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsListOfInt(); } /** * Cannot cast to list of int */ public function providerCannotCastToListOfIntValues() { return [ 'not array' => [1], 'associative array' => [[1, 2 => 3]], 'list of uncastable values' => [['a']], ]; } /** * @dataProvider providerCanCastToListOfFloatValues */ public function testGetAsListOfFloat_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->getAsListOfFloat(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to list of float */ public function providerCanCastToListOfFloatValues() { return [ 'empty list' => [[], []], 'list of float' => [[1.5], [1.5]], 'list of castable to float' => [['1.5'], [1.5]], ]; } /** * @dataProvider providerCannotCastToListOfFloatValues */ public function testGetAsListOfFloat_GivenUncastableValue_ThrowsUuncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsListOfFloat(); } /** * Cannot cast to list of float */ public function providerCannotCastToListOfFloatValues() { return [ 'not array' => [1.5], 'associative array' => [[1.5, 2 => 3.5]], 'list of uncastable values' => [['a']], ]; } /** * @dataProvider providerCanCastToListOfStringValues */ public function testGetAsListOfString_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->getAsListOfString(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to list of string */ public function providerCanCastToListOfStringValues() { return [ 'empty list' => [[], []], 'list of string' => [['crusoe'], ['crusoe']], 'list of castable to strign' => [[1], ['1']], ]; } /** * @dataProvider providerCannotCastToListOfStringValues */ public function testGetAsListOfString_GivenUncastableValue_ThrowsUncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsListOfString(); } /** * Cannot cast to list */ public function providerCannotCastToListOfStringValues() { return [ 'not array' => [1], 'associative array' => [['a', 2 => 'b']], 'list of uncastable values' => [[true]], ]; } /** * @dataProvider providerCanCastToArrayOfStringToIntValues */ public function testGetAsArrayOfStringToInt_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->getAsArrayOfStringToInt(); $this->assertSame($castedValue, $actualValue); } /** * Can cast to array */ public function providerCanCastToArrayOfStringToIntValues() { return [ 'empty array' => [[], []], 'array' => [['a' => 1], ['a' => 1]], 'array' => [['a' => '1'], ['a' => 1]], ]; } /** * @dataProvider providerCannotCastToArrayOfStringToIntValues */ public function testGetAsArrayOfStringToInt_GivenUncastableValue_ThrowsUncastableValueException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->getAsArrayOfStringToInt(); } /** * Cannot cast to list */ public function providerCannotCastToArrayOfStringToIntValues() { return [ 'not array' => [1], 'array' => [[1 => 1, 'a' => 'b']], 'array' => [[true]], ]; } public function testFindInt_IntValue_ReturnsSameIntValue() { $actualValue = (new Mess(1))->findInt(); $this->assertSame(1, $actualValue); } public function testFindInt_StringValue_ThrowsException() { $this->expectException(UnexpectedTypeException::class); (new Mess('crusoe'))->findInt(); } public function testFindInt_Null_ReturnsNull() { $actualValue = (new Mess(null))->findInt(); $this->assertNull($actualValue); } public function testFindFloat_FloatValue_ReturnsSameFloatValue() { $actualValue = (new Mess(1.5))->findFloat(); $this->assertSame(1.5, $actualValue); } public function testFindFloat_StringValue_ThrowsException() { $this->expectException(UnexpectedTypeException::class); (new Mess('crusoe'))->findFloat(); } public function testFindFloat_Null_ReturnsNull() { $actualValue = (new Mess(null))->findFloat(); $this->assertNull($actualValue); } public function testFindBool_BoolValue_ReturnsSameBoolValue() { $actualValue = (new Mess(true))->findBool(); $this->assertSame(true, $actualValue); } public function testFindBool_StringValue_ThrowsException() { $this->expectException(UnexpectedTypeException::class); (new Mess('crusoe'))->findBool(); } public function testFindBool_Null_ReturnsNull() { $actualValue = (new Mess(null))->findBool(); $this->assertNull($actualValue); } public function testFindString_StringValue_ReturnsSameStringValue() { $actualValue = (new Mess('crusoe'))->findString(); $this->assertSame('crusoe', $actualValue); } public function testFindString_GivenUncastableValue_ThrowsException() { $this->expectException(UnexpectedTypeException::class); (new Mess(1))->findString(); } public function testFindString_Null_ReturnsSameNull() { $actualValue = (new Mess(null))->findString(); $this->assertNull($actualValue); } public function testFindListOfInt_ListOfInt_ReturnsSameListOfInt() { $actualValue = (new Mess([1, 5, 10]))->findListOfInt(); $this->assertSame([1, 5, 10], $actualValue); } /** * @dataProvider providerNotAListOfInt */ public function testFindListOfInt_GivenNotAListOfInt_ThrowsException($notAListOfInt) { $this->expectException(UnexpectedTypeException::class); (new Mess($notAListOfInt))->findListOfInt(); } public function testFindListOfInt_Null_ReturnsNull() { $actualValue = (new Mess(null))->findListOfInt(); $this->assertNull($actualValue); } public function testFindListOfFloat_ListOfFloat_ReturnsSameListOfFloat() { $actualValue = (new Mess([1.5, 0.1]))->findListOfFloat(); $this->assertSame([1.5, 0.1], $actualValue); } /** * @dataProvider providerNotAListOfInt */ public function testFindListOfFloat_GivenNotAListOfFloat_ThrowsException($notAlistOfInt) { $this->expectException(UnexpectedTypeException::class); (new Mess($notAlistOfInt))->findListOfFloat(); } public function testFindListOfString_ListOfStringValue_ReturnsSameListOfStringValue() { $actualValue = (new Mess(['a', 'b']))->findListOfString(); $this->assertSame(['a', 'b'], $actualValue); } /** * @dataProvider providerNotAListOfString */ public function testFindListOfString_GivenNotAListOfInt_ThrowsException($notAListOfInt) { $this->expectException(UnexpectedTypeException::class); (new Mess($notAListOfInt))->findListOfString(); } public function testFindListOfString_Null_ReturnsNull() { $actualValue = (new Mess(null))->findListOfString(); $this->assertNull($actualValue); } /** * @dataProvider providerCanCastToIntValues */ public function testFindAsInt_GivenCastableValue_ReturnsMatchingCastedValue($value, int $castedValue) { $actualValue = (new Mess($value))->findAsInt(); $this->assertSame($castedValue, $actualValue); } /** * @dataProvider providerCannotCastToIntValues */ public function testFindAsInt_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsInt(); } /** * @dataProvider providerCannotCastToFloatValues */ public function testFindAsFloat_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsFloat(); } /** * @dataProvider providerCanCastToBoolValues */ public function testFindAsBool_GivenCastableValue_ReturnsMatchingCastedValue($value, bool $castedValue) { $actualValue = (new Mess($value))->findAsBool(); $this->assertSame($castedValue, $actualValue); } /** * @dataProvider providerCannotCastToBoolValues */ public function testFindAsBool_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsBool(); } /** * @dataProvider providerCanCastToStringValues */ public function testFindAsString_GivenCastableValue_ReturnsMatchingCastedValue($value, string $castedValue) { $actualValue = (new Mess($value))->findAsString(); $this->assertSame($castedValue, $actualValue); } /** * @dataProvider providerCannotCastToStringValues */ public function testFindAsString_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsString(); } /** * @dataProvider providerCanCastToListOfIntValues */ public function testFindAsListOfInt_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->findAsListOfInt(); $this->assertSame($castedValue, $actualValue); } /** * @dataProvider providerCannotCastToListOfIntValues */ public function testFindAsListOfInt_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsListOfInt(); } /** * @dataProvider providerCanCastToListOfFloatValues */ public function testFindAsListOfFloat_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->findAsListOfFloat(); $this->assertSame($castedValue, $actualValue); } /** * @dataProvider providerCannotCastToListOfFloatValues */ public function testFindAsListOfFloat_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsListOfFloat(); } /** * @dataProvider providerCanCastToListOfStringValues */ public function testFindAsListOfString_GivenCastableValue_ReturnsMatchingCastedValue($value, array $castedValue) { $actualValue = (new Mess($value))->findAsListOfString(); $this->assertSame($castedValue, $actualValue); } /** * @dataProvider providerCannotCastToListOfStringValues */ public function testFindAsListOfString_GivenUncastableValue_ThrowsException($value) { $this->expectException(UncastableValueException::class); (new Mess($value))->findAsListOfString(); } public function testGetMixed_AnyValue_ReturnsSameValue() { $actualValue = (new Mess('1'))->getMixed(); $this->assertSame('1', $actualValue); } public function testGetObject_Object_ReturnsSameObject() { $object = new stdClass(); $actualValue = (new Mess($object))->getObject(); $this->assertSame($object, $actualValue); } public function testGetObject_Int_ThrowsUnexpectedTypeException() { $this->expectException(UnexpectedTypeException::class); (new Mess(1))->getObject(); } public function testGetArray_Array_ReturnsSameArray() { $actualValue = (new Mess([1]))->getArray(); $this->assertSame([1], $actualValue); } public function testGetArray_Int_ThrowsUnexpectedTypeException() { $this->expectException(UnexpectedTypeException::class); (new Mess(1))->getArray(); } public function testFindMixed_AnyValue_ReturnsSameValue() { $actualValue = (new Mess('1'))->findMixed(); $this->assertSame('1', $actualValue); } public function testFindObject_Object_ReturnsSameObject() { $object = new stdClass(); $actualValue = (new Mess($object))->findObject(); $this->assertSame($object, $actualValue); } public function testFindObject_Array_ThrowsException() { $this->expectException(UnexpectedTypeException::class); (new Mess([]))->findObject(); } public function testFindObject_Null_ReturnsNull() { $actualValue = (new Mess(null))->findObject(); $this->assertNull($actualValue); } public function testFindArray_Array_ReturnsSameArray() { $actualValue = (new Mess([1]))->findArray(); $this->assertSame([1], $actualValue); } public function testFindArray_Int_ThrowsException() { $this->expectException(UnexpectedTypeException::class); (new Mess(1))->findArray(); } public function testFindArray_Null_ReturnsNull() { $actualValue = (new Mess(null))->findArray(); $this->assertNull($actualValue); } public function testOffsetExists_ArrayWithExistingOffset_ReturnsTrue() { $accessor = new Mess([1]); $this->assertTrue(isset($accessor[0])); } public function testOffsetExists_ArrayWithNonExistingOffset_ReturnsFalse() { $accessor = new Mess([]); $this->assertFalse(isset($accessor[0])); } public function testOffsetExists_NonArray_ReturnsFalse() { $accessor = new Mess(1); $this->assertFalse(isset($accessor[0])); } public function testOffsetGet_ArrayWithExistingOffset_ReturnsTypedValueAccessor() { $accessor = new Mess([1]); $this->assertInstanceOf(Mess::class, $accessor[0]); } public function testOffsetGet_InnerArrayWithExistingOffset_ReturnsTypedValueAccessor() { $accessor = new Mess([0 => [0 => 1]]); $this->assertInstanceOf(Mess::class, $accessor[0][0]); } public function testOffsetGet_ArrayWithNonExistingOffset_ReturnsMissingValueAccessor() { $accessor = new Mess([1]); $this->assertInstanceOf(MissingMess::class, $accessor[1]); } public function testOffsetGet_InnerArrayWithNonExistingOffset_ReturnsMissingValueAccessor() { $accessor = new Mess([0 => [0 => 1]]); $this->assertInstanceOf(MissingMess::class, $accessor[0][1]); } public function testOffsetGet_String_ReturnsValueByKey() { $accessor = new Mess(['key' => 1]); $this->assertSame(1, $accessor['key']->getInt()); } public function testOffsetGet_Int_ReturnsValueByKey() { $accessor = new Mess([0 => 1]); $this->assertSame(1, $accessor[0]->getInt()); } public function testOffsetGet_Bool_ThrowsUnexpectedKeyTypeException() { $accessor = new Mess([0 => 1]); $this->expectException(UnexpectedKeyTypeException::class); $accessor[false]; } public function testOffsetSet_Offset_ThrowsCannotModifyAccessorException() { $this->expectException(CannotModifyMessException::class); $accessor = new Mess([1]); $accessor[0] = 1; } public function testOffsetUnset_Offset_ThrowsCannotModifyAccessorException() { $this->expectException(CannotModifyMessException::class); $accessor = new Mess([1]); unset($accessor[0]); } } ================================================ FILE: tests/unit/MissingMessTest.php ================================================ expectException(MissingKeyException::class); $accessor->getInt(); } public function testGetFloat_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getFloat(); } public function testGetBool_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getBool(); } public function testGetString_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getString(); } public function testGetAsInt_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getInt(); } public function testGetAsFloat_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getFloat(); } public function testGetAsBool_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getBool(); } public function testGetAsString_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getString(); } public function testFindInt_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findInt()); } public function testFindFloat_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findFloat()); } public function testFindBool_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findBool()); } public function testFindString_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findString()); } public function testFindAsInt_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findInt()); } public function testFindAsFloat_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findFloat()); } public function testFindAsBool_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findBool()); } public function testFindAsString_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findString()); } public function testGetMixed_CorrectAccessor_ThrowsMissingKeyException() { $accessor = new MissingMess([]); $this->expectException(MissingKeyException::class); $accessor->getMixed(); } public function testFindMixed_CorrectAccessor_ReturnsNull() { $accessor = new MissingMess([]); $this->assertNull($accessor->findMixed()); } public function testOffsetExists_CorrectAccessor_ReturnsFalse() { $accessor = new MissingMess([]); $this->assertFalse($accessor->offsetExists(0)); } public function testOffsetGet_CorrectAccessor_ReturnsMissingValueAccessor() { $accessor = new Mess([]); $this->assertInstanceOf(MissingMess::class, $accessor[0]); } public function testOffsetGet_CorrectAccessorWithFluentAccess_ThrowsMissingKeyException() { $accessor = new Mess([]); try { $accessor['a']['b']['c']->getInt(); } catch (MissingKeyException $e) { $this->assertSame(['a', 'b', 'c'], $e->getKeySequence()); return; } $this->fail(); } public function testOffsetSet_CorrectAccessor_ThrowsCannotModifyAccessorException() { $accessor = new MissingMess([]); $this->expectException(CannotModifyMessException::class); $accessor[0] = 1; } public function testOffsetUnset_CorrectAccessor_ThrowsCannotModifyAccessorException() { $accessor = new MissingMess([]); $this->expectException(CannotModifyMessException::class); unset($accessor[0]); } }