Skip to content

Commit

Permalink
Fixes + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
uuf6429 committed Apr 23, 2022
1 parent 55450f7 commit ebd93a9
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 30 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/tests export-ignore
.github export-ignore
phpunit.xml.dist export-ignore
11 changes: 11 additions & 0 deletions .github/stale.yml
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
vendor/
/vendor
.phpunit.result.cache
phpunit.xml
.idea
composer.lock
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
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.
9 changes: 9 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "uuf6429/php-castable",
"description": "Type casting functionality for PHP",
"license": "MIT",
"type": "library",
"authors": [
{
Expand All @@ -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"
}
}
22 changes: 22 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
colors="true"
failOnRisky="true"
>
<testsuites>
<testsuite name="All Tests">
<directory>./tests/</directory>
</testsuite>
</testsuites>

<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<file>src/.phpstorm.meta.php</file>
</exclude>
</coverage>
</phpunit>
48 changes: 25 additions & 23 deletions src/functions.php
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
<?php

use uuf6429\Castable\Castable;
use uuf6429\Castable\NotCastableException;
namespace uuf6429\Castable;

use Exception;
use Throwable;

function cast($value, $type)
{
if (!is_object($value)) {
if (settype($value, $type)) {
return $value;
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)
);
}
throw new NotCastableException(
sprintf('Value of type %s cannot be cast to %s', gettype($value), $type)
);
}

if ($value instanceof Castable) {
try {
if ($value instanceof Castable) {
return $value->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
);
}
13 changes: 13 additions & 0 deletions tests/BaseTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace uuf6429\Castable;

if (class_exists(\PHPUnit\Framework\TestCase::class)) {
class BaseTestCase extends \PHPUnit\Framework\TestCase
{
}
} elseif (class_exists(\PHPUnit_Framework_TestCase::class)) {
class BaseTestCase extends \PHPUnit_Framework_TestCase
{
}
}
87 changes: 87 additions & 0 deletions tests/CastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace uuf6429\Castable;

use ArrayObject;

class CastTest extends BaseTestCase
{
/**
* @dataProvider invalidCastingDataProvider
*/
public function test_that_invalid_casting_triggers_exception($originalValue, $targetType)
{
$this->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,
],
];
}
}
22 changes: 22 additions & 0 deletions tests/ExampleCastableClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace uuf6429\Castable;

use InvalidArgumentException;

class ExampleCastableClass implements Castable
{
public function castTo($type)
{
switch ($type) {
case 'int':
return 123;

case 'string':
return 'example';

default:
throw new InvalidArgumentException("Unsupported cast type: $type");
}
}
}

0 comments on commit ebd93a9

Please sign in to comment.