diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9be7991 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/tests export-ignore +.github export-ignore +phpunit.xml.dist export-ignore diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..dfbb8c2 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,11 @@ +daysUntilStale: 30 +daysUntilClose: 7 +exemptLabels: + - pinned + - security +staleLabel: wontfix +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +closeComment: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5aacd86 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + + build: + name: Test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [ '5.6', '7.0', '7.4', '8.0' ] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 2 + + - name: Download dependencies + uses: ramsey/composer-install@v1 + with: + composer-options: --no-interaction --prefer-dist --optimize-autoloader + + - name: Run tests + run: ./vendor/bin/phpunit --coverage-clover coverage.xml + + - name: Upload to Codecov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: bash <(curl -s https://codecov.io/bash) diff --git a/.gitignore b/.gitignore index d1502b0..b3f378b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -vendor/ +/vendor +.phpunit.result.cache +phpunit.xml +.idea composer.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..512723c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Christian Sciberras + +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. diff --git a/README.md b/README.md index 943ee58..a5ee948 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,22 @@ -# PHP Castable +# 🎭 PHP Castable + +[![CI](https://github.com/uuf6429/php-castable/actions/workflows/ci.yml/badge.svg)](https://github.com/uuf6429/php-castable/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/uuf6429/php-castable/branch/main/graph/badge.svg)](https://codecov.io/gh/uuf6429/php-castable) +[![Minimum PHP Version](https://img.shields.io/badge/php-%5E5.6%20%7C%20%5E7%20%7C%20%5E8-8892BF.svg)](https://php.net/) +[![License](https://poser.pugx.org/uuf6429/php-castable/license)](https://packagist.org/packages/uuf6429/php-castable) +[![Latest Stable Version](https://poser.pugx.org/uuf6429/php-castable/version)](https://packagist.org/packages/uuf6429/php-castable) +[![Latest Unstable Version](https://poser.pugx.org/uuf6429/php-castable/v/unstable)](https://packagist.org/packages/uuf6429/php-castable) Basic groundwork for type-casting in PHP. -## Features / Functionality +## 🔌 Installation +The recommended and easiest way to install this library is through Composer: + +```shell +composer require uuf6429/php-castable "^1.0" +``` + +## ⭐️ Features / Functionality - Works with simple types and objects - `cast($value, $type)` function that converts a value to a target type. @@ -13,13 +27,17 @@ Basic groundwork for type-casting in PHP. While `cast()` is just a regular PHP function, it would be the equivalent to type-casting operators in other languages (e.g. `val as Type`, `(Type)val`, `val.to(Type)`, `CAST(val, TYPE)`...). -## Behaviour +## 🔍 Casting Behaviour +The casting process follows these steps: 1. If the value to be type-casted is not an object, PHP's `settype()` is used. 2. If, instead, it is an object that implements `Castable` interface, `castTo()` is called and its value returned. 3. Otherwise, if the object is the same or a subclass of the desired type, then it is returned unchanged. -## Motivation +At any point in time, errors or unsupported type-casting could occur, in which case a `NotCastableException` is thrown. + +## 💰 Motivation + +In many cases, having specific `castToX()` methods in your classes is enough, and it typically works adequately. -In many cases, having specific `castToX()` methods in your classes is enough and the behaviour typically works adequately. -However, sometimes this becomes too much or a more dynamic solution is needed. In this case, this package helps to avoid writing the boilerplate code. \ No newline at end of file +However, this could get very repetitive and somewhat error-prone, until a more dynamic solution is needed. This package helps to safely avoid all that boilerplate code. \ No newline at end of file diff --git a/composer.json b/composer.json index 868ec64..20dabed 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "uuf6429/php-castable", "description": "Type casting functionality for PHP", + "license": "MIT", "type": "library", "authors": [ { @@ -16,7 +17,15 @@ "src/functions.php" ] }, + "autoload-dev": { + "psr-4": { + "uuf6429\\Castable\\": "tests/" + } + }, "require": { "php": "^5.6 || ^7 || ^8" + }, + "require-dev": { + "phpunit/phpunit": "^5 | ^6 | ^7 | ^8 | ^9" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..8c43c07 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,22 @@ + + + + + + ./tests/ + + + + + + src + + + src/.phpstorm.meta.php + + + diff --git a/src/functions.php b/src/functions.php index 6f61310..19d08cd 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,35 +1,37 @@ castTo($type); - } catch (Exception $exception) { - } catch (Throwable $exception) { } - throw new NotCastableException( - sprintf('Castable object could not be cast to %s', $type), 0, $exception - ); - } - if (!is_a($value, $type)) { - throw new NotCastableException( - sprintf('Object of class %s is not compatible with class %s', get_class($value), $type) - ); - } + if ($type !== 'object' && !is_a($value, $type)) { + throw new NotCastableException( + sprintf('Object of class %s is not compatible with class %s', get_class($value), $type) + ); + } - return $value; + return $value; + } catch (Exception $exception) { + } catch (Throwable $exception) { + } + throw new NotCastableException( + sprintf('Castable object could not be cast to %s', $type), 0, $exception + ); } diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php new file mode 100644 index 0000000..37ad3b0 --- /dev/null +++ b/tests/BaseTestCase.php @@ -0,0 +1,13 @@ +expectException(NotCastableException::class); + + cast($originalValue, $targetType); + } + + public function invalidCastingDataProvider() + { + return [ + 'invalid data type of target type' => [ + '$originalValue' => 1, + '$targetType' => 1, + ], + 'invalid target type' => [ + '$originalValue' => 1, + '$targetType' => 'invalid', + ], + 'invalid conversion; object to integer' => [ + '$originalValue' => (object)[], + '$targetType' => 'integer', + ], + 'invalid conversion; object to specific class' => [ + '$originalValue' => (object)[], + '$targetType' => ArrayObject::class, + ], + 'converting example object unsupported type' => [ + '$originalValue' => new ExampleCastableClass(), + '$targetType' => 'someType', + ], + 'as per php, aliases should not work' => [ + '$originalValue' => new ExampleCastableClass(), + '$targetType' => 'integer', + ], + ]; + } + + /** + * @dataProvider validCastingDataProvider + */ + public function test_that_valid_casting_returns_expected_value($originalValue, $targetType, $expectedValue) + { + $this->assertSame($expectedValue, cast($originalValue, $targetType)); + } + + public function validCastingDataProvider() + { + return [ + 'converting number to string' => [ + '$originalValue' => 123, + '$targetType' => 'string', + '$expectedValue' => '123', + ], + 'converting number to boolean' => [ + '$originalValue' => 1, + '$targetType' => 'bool', + '$expectedValue' => true, + ], + 'converting float to integer (lossy)' => [ + '$originalValue' => 123.456, + '$targetType' => 'int', + '$expectedValue' => 123, + ], + 'converting specific object to generic object' => [ + '$originalValue' => ($inst = new ArrayObject()), + '$targetType' => 'object', + '$expectedValue' => $inst, + ], + 'converting example object to number should work' => [ + '$originalValue' => new ExampleCastableClass(), + '$targetType' => 'int', + '$expectedValue' => 123, + ], + ]; + } +} diff --git a/tests/ExampleCastableClass.php b/tests/ExampleCastableClass.php new file mode 100644 index 0000000..41b8439 --- /dev/null +++ b/tests/ExampleCastableClass.php @@ -0,0 +1,22 @@ +