Skip to content

Commit

Permalink
Update to v2 (#1)
Browse files Browse the repository at this point in the history
* Update min PHP version to 8
* Add more linters
* Update type-hinting system to PHPDoc/PHPStan
* Rework casting logic + a few bug fixes
  • Loading branch information
uuf6429 authored Jun 2, 2024
1 parent 32868e6 commit 26d5257
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 166 deletions.
10 changes: 7 additions & 3 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/tests export-ignore
.github export-ignore
phpunit.xml.dist export-ignore
.github/ export-ignore
tests/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.php-cs-fixer.dist.php export-ignore
phpstan.dist.neon export-ignore
phpunit.dist.xml export-ignore
40 changes: 18 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,31 @@ on:

jobs:

build:
name: Test
Lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- run: composer update --ansi --no-progress --prefer-dist --no-interaction
- run: composer run lint

Test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [ '5.6', '7.0', '7.4', '8.0' ]

php: [ '8.0', '8.1', '8.2', '8.3' ]
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
- uses: actions/checkout@v4
- 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:
- run: composer update --ansi --no-progress --prefer-dist --no-interaction
- run: composer run test:cover
- env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: bash <(curl -s https://codecov.io/bash)
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/vendor
.phpunit.result.cache
.php-cs-fixer.cache
phpstan.neon
phpunit.xml
.idea
composer.lock
coverage.xml
14 changes: 14 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRules([
'@PER-CS2.0' => true,
'@PER-CS2.0:risky' => true,
'trailing_comma_in_multiline' => false,
])
->setFinder(
(new PhpCsFixer\Finder())
->in(__DIR__)
);
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![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/)
[![Minimum PHP Version](https://img.shields.io/badge/php-%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)
Expand All @@ -21,9 +21,8 @@ composer require uuf6429/php-castable "^1.0"
- Works with simple types and objects
- `cast($value, $type)` function that converts a value to a target type.
- `Castable` interface, exposes method that is called whenever `cast()` is called on objects implementing this interface.
- Error handling - all errors routed to `NotCastableException`
- Fixes type-hinting for PhpStorm
- PHP 5.6+ (but seriously, stop using PHP 5 :))
- Error handling - all errors routed to `NotCastableException`.
- Fixes type-hinting for IDEs understanding PHPDoc Generics.

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)`...).

Expand Down Expand Up @@ -51,14 +50,14 @@ $cat = \uuf6429\Castable\cast($dog, Cat::class); // not allowed
## 🔍 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.
1. If the value is an object or value of the desired type, then it is returned unchanged.
2. If the value is an *object* that *implements `Castable` interface*, `castTo()` is called and its value returned.
3. Otherwise, PHP's `settype()` is attempted.

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.

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.
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.
31 changes: 27 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
"email": "[email protected]"
}
],
"require": {
"php": "^8"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.7",
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.4",
"phpunit/phpunit": "^9 || ^10 || ^11",
"roave/security-advisories": "dev-latest"
},
"autoload": {
"psr-4": {
"uuf6429\\Castable\\": "src/"
Expand All @@ -22,10 +32,23 @@
"uuf6429\\Castable\\": "tests/"
}
},
"require": {
"php": "^5.6 || ^7 || ^8"
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true
},
"process-timeout": 0
},
"require-dev": {
"phpunit/phpunit": "^5 | ^6 | ^7 | ^8 | ^9"
"scripts": {
"lint": [
"composer normalize --dry-run",
"composer exec phpstan -- analyse --no-progress",
"composer exec php-cs-fixer -- fix --dry-run --show-progress=none --diff"
],
"lint:fix": [
"composer normalize",
"composer exec php-cs-fixer -- fix --diff"
],
"test": "phpunit ./tests/",
"test:cover": "@php -dzend_extension=php_xdebug -dxdebug.mode=coverage vendor/bin/phpunit --coverage-clover coverage.xml ./tests/"
}
}
14 changes: 14 additions & 0 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
parameters:
level: 9
paths:
- src
- tests
ignoreErrors:
-
message: "#^Function uuf6429\\\\Castable\\\\cast\\(\\) should return T but returns object\\.$#"
count: 1
path: src/functions.php
-
message: "#^Unable to resolve the template type T in call to method uuf6429\\\\Castable\\\\Castable\\:\\:castTo\\(\\)$#"
count: 1
path: src/functions.php
13 changes: 13 additions & 0 deletions phpunit.dist.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" failOnRisky="true">
<testsuites>
<testsuite name="All Tests">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">./src/</directory>
</include>
</source>
</phpunit>
22 changes: 0 additions & 22 deletions phpunit.xml.dist

This file was deleted.

6 changes: 0 additions & 6 deletions src/.phpstorm.meta.php

This file was deleted.

8 changes: 5 additions & 3 deletions src/Castable.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
interface Castable
{
/**
* @param string $type
* @return mixed
* @template T
* @param class-string<T> $type
* @return T
* @throws NotCastableException
*/
public function castTo($type);
public function castTo(string $type);
}
5 changes: 1 addition & 4 deletions src/NotCastableException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@

use RuntimeException;

class NotCastableException extends RuntimeException
{

}
class NotCastableException extends RuntimeException {}
49 changes: 30 additions & 19 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,47 @@

namespace uuf6429\Castable;

use Exception;
use Throwable;

function cast($value, $type)
/**
* @template T
* @param mixed $value
* @param class-string<T> $type
* @return T
* @throws NotCastableException
*/
function cast(mixed $value, string $type)
{
static $basicTypes = ['bool', 'string', 'int', 'float', 'array', 'object', 'null'];
static $typeAliases = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'];
$type = $typeAliases[$type] ?? $type;

try {
if (!is_object($value)) {
if (@settype($value, $type)) {
return $value;
}
throw new NotCastableException(
sprintf('Value of type %s cannot be cast to %s', gettype($value), $type)
);
if (is_object($value) && is_a($value, $type)) {
return $value;
}

if (gettype($value) === $type) {
return $value;
}

if ($value instanceof Castable) {
return $value->castTo($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)
);
if (in_array($type, $basicTypes, true)) {
@settype($value, $type);
return $value;
}

return $value;
} catch (Exception $exception) {
} catch (Throwable $exception) {
throw new NotCastableException(
sprintf('Cannot cast %s to %s', get_debug_type($value), $type)
);
} catch (Throwable $ex) {
throw new NotCastableException(
sprintf('Cannot cast %s to %s: %s', get_debug_type($value), $type, $ex->getMessage()),
0,
$ex
);
}
throw new NotCastableException(
sprintf('Castable object could not be cast to %s', $type), 0, $exception
);
}
13 changes: 0 additions & 13 deletions tests/BaseTestCase.php

This file was deleted.

Loading

0 comments on commit 26d5257

Please sign in to comment.