Skip to content

Commit

Permalink
Add AttributeTypecastHandler (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik authored Jun 17, 2024
1 parent f9f0e37 commit 7e5065b
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 5 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Cycle Typecast Change Log

## 2.2.0 June 17, 2024

- New #5: Add `AttributeTypecastHandler`

## 2.1.0 February 01, 2024

- New #3: Add enum types `IntegerEnumType` and `StringEnumType`
Expand All @@ -8,8 +12,8 @@

- New #2: Add abstract typecast handler
- Chg #2: Refactoring `Typecaster` (changed method names and returned values)
- Chg #2: Raise the minimum PHP version to `^8.0` and Cycle ORM to `^2.1`
- Chg #2: Raise the minimum PHP version to `^8.0` and Cycle ORM to `^2.1`

## 1.0.0 December 1, 2021

- Initial release.
- Initial release
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

The package provides:

- `Typecaster` that help typecast data in [Cycle ORM](https://cycle-orm.dev/);
- `TypeInterface` that must be implemented by classes used in `Typecaster`;
- abstract `TypecastHandler` that used `Typecaster` for typecast data;
- `Typecaster` that help typecast data in [Cycle ORM](https://cycle-orm.dev/) and abstract `TypecastHandler` that used it;
- `AttributeTypecastHandler` that use attributes for typecast data;
- `TypeInterface` that must be implemented by classes used in `Typecaster` and `AttributeTypecastHandler`;
- classes for `DateTimeImmutable`, `UUID`, `Array` and `Enum` types.

## Installation
Expand All @@ -24,6 +24,26 @@ composer require vjik/cycle-typecast

## General Usage

### Attributes

```php
#[Entity(
// ...
typecast: AttributeTypecastHandler::class,
)]
final class User
{
// ...

#[Column(type: 'primary', primary: true)]
#[UuidToBytesType]
private UuidInterface $id;

#[Column(type: 'int')]
#[DateTimeImmutableToIntegerType]
private DateTimeImmutable $createDate;
```

### Custom Typecast Handler

```php
Expand Down
3 changes: 3 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<MixedAssignment errorLevel="suppress" />
</issueHandlers>
</psalm>
3 changes: 3 additions & 0 deletions psalm80.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@
<file name="src/StringEnumType.php" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<MixedAssignment errorLevel="suppress" />
</issueHandlers>
</psalm>
2 changes: 2 additions & 0 deletions src/ArrayToStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Vjik\CycleTypecast;

use Attribute;
use InvalidArgumentException;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ArrayToStringType implements TypeInterface
{
/**
Expand Down
69 changes: 69 additions & 0 deletions src/AttributeTypecastHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Vjik\CycleTypecast;

use Cycle\ORM\Parser\CastableInterface;
use Cycle\ORM\Parser\UncastableInterface;
use Cycle\ORM\SchemaInterface;
use ReflectionAttribute;
use ReflectionClass;

use function class_exists;

final class AttributeTypecastHandler implements CastableInterface, UncastableInterface
{
/**
* @var TypeInterface[]
* @psalm-var array<string, TypeInterface>
*/
private array $types = [];

public function __construct(SchemaInterface $schema, string $role)
{
$entityClass = $schema->define($role, SchemaInterface::ENTITY);
if (is_string($entityClass) && class_exists($entityClass)) {
$reflection = new ReflectionClass($entityClass);
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(TypeInterface::class, ReflectionAttribute::IS_INSTANCEOF);
if (empty($attributes)) {
continue;

Check warning on line 31 in src/AttributeTypecastHandler.php

View workflow job for this annotation

GitHub Actions / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "Continue_": --- Original +++ New @@ @@ foreach ($reflection->getProperties() as $property) { $attributes = $property->getAttributes(TypeInterface::class, ReflectionAttribute::IS_INSTANCEOF); if (empty($attributes)) { - continue; + break; } $this->types[$property->getName()] = $attributes[0]->newInstance(); }
}
$this->types[$property->getName()] = $attributes[0]->newInstance();
}
}
}

public function setRules(array $rules): array
{
foreach ($rules as $key => $_rule) {
if (isset($this->types[$key])) {
unset($rules[$key]);
}
}
return $rules;

Check warning on line 45 in src/AttributeTypecastHandler.php

View workflow job for this annotation

GitHub Actions / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "ArrayOneItem": --- Original +++ New @@ @@ unset($rules[$key]); } } - return $rules; + return count($rules) > 1 ? array_slice($rules, 0, 1, true) : $rules; } public function cast(array $data) : array {
}

public function cast(array $data): array
{
/** @psalm-var array<non-empty-string, mixed> $data */
foreach ($data as $key => $value) {
if (isset($this->types[$key])) {
$data[$key] = $this->types[$key]->convertToPhpValue($value);
}
}
return $data;
}

public function uncast(array $data): array
{
/** @psalm-var array<non-empty-string, mixed> $data */
foreach ($data as $key => $value) {
if (isset($this->types[$key])) {
$data[$key] = $this->types[$key]->convertToDatabaseValue($value);
}
}
return $data;
}
}
2 changes: 2 additions & 0 deletions src/DateTimeImmutable/DateTimeImmutableToIntegerType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

namespace Vjik\CycleTypecast\DateTimeImmutable;

use Attribute;
use DateTimeImmutable;
use InvalidArgumentException;

use function is_int;
use function is_string;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class DateTimeImmutableToIntegerType extends DateTimeImmutableType
{
protected function toDatabaseValue(DateTimeImmutable $value): string
Expand Down
2 changes: 2 additions & 0 deletions src/IntegerEnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Vjik\CycleTypecast;

use Attribute;
use BackedEnum;
use InvalidArgumentException;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class IntegerEnumType implements TypeInterface
{
/**
Expand Down
2 changes: 2 additions & 0 deletions src/StringEnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Vjik\CycleTypecast;

use Attribute;
use BackedEnum;
use InvalidArgumentException;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class StringEnumType implements TypeInterface
{
/**
Expand Down
2 changes: 2 additions & 0 deletions src/UuidString/UuidStringToBytesType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

namespace Vjik\CycleTypecast\UuidString;

use Attribute;
use Exception;
use InvalidArgumentException;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class UuidStringToBytesType extends UuidStringType
{
protected function toDatabaseValue(UuidInterface $value): string
Expand Down
58 changes: 58 additions & 0 deletions tests/AttributeTypecastHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Vjik\CycleTypecast\Tests;

use Cycle\ORM\SchemaInterface;
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Uuid;
use Vjik\CycleTypecast\AttributeTypecastHandler;
use Vjik\CycleTypecast\Tests\Support\EntityWithAttributes;

final class AttributeTypecastHandlerTest extends TestCase
{
public function testBase(): void
{
$schema = $this->createMock(SchemaInterface::class);
$schema
->method('define')
->with('role_user', SchemaInterface::ENTITY)
->willReturn(EntityWithAttributes::class);

$typecastHandler = new AttributeTypecastHandler($schema, 'role_user');

$uuid = Uuid::fromString('1f2d3897-a226-4eec-bd2c-d0145ef25df9');

$data = $typecastHandler->uncast([
'id' => '1f2d3897-a226-4eec-bd2c-d0145ef25df9',
'names' => ['John', 'Doe'],
]);
$this->assertSame(
[
'id' => $uuid->getBytes(),
'names' => 'John,Doe',
],
$data,
);

$data = $typecastHandler->cast([
'id' => $uuid->getBytes(),
'names' => 'John,Doe',
]);
$this->assertSame(
[
'id' => '1f2d3897-a226-4eec-bd2c-d0145ef25df9',
'names' => ['John', 'Doe'],
],
$data,
);

$rules = $typecastHandler->setRules([
'id' => 'string',
'names' => 'array',
'age' => 'int',
]);
$this->assertSame(['age' => 'int'], $rules);
}
}
19 changes: 19 additions & 0 deletions tests/Support/EntityWithAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Vjik\CycleTypecast\Tests\Support;

use Vjik\CycleTypecast\ArrayToStringType;
use Vjik\CycleTypecast\UuidString\UuidStringToBytesType;

final class EntityWithAttributes
{
#[UuidStringToBytesType]
public string $id;

#[ArrayToStringType(',')]
public array $names;

public int $age;
}
2 changes: 2 additions & 0 deletions tests/Support/StubDateTimeImmutableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Vjik\CycleTypecast\Tests\Support;

use Attribute;
use DateTimeImmutable;
use Vjik\CycleTypecast\DateTimeImmutable\DateTimeImmutableType;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class StubDateTimeImmutableType extends DateTimeImmutableType
{
protected function toDatabaseValue(DateTimeImmutable $value): mixed
Expand Down
2 changes: 2 additions & 0 deletions tests/Support/StubUuidStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Vjik\CycleTypecast\Tests\Support;

use Attribute;
use Ramsey\Uuid\UuidInterface;
use Vjik\CycleTypecast\UuidString\UuidStringType;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class StubUuidStringType extends UuidStringType
{
protected function toDatabaseValue(UuidInterface $value): mixed
Expand Down

0 comments on commit 7e5065b

Please sign in to comment.