Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AttributeTypecastHandler #5

Merged
merged 8 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 #4: Add `AttributeTypecastHandler`
vjik marked this conversation as resolved.
Show resolved Hide resolved

## 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
Loading