Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fab2s committed Apr 22, 2024
1 parent 9870691 commit 067bc81
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ matrix.php-versions }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-

- name: Remove composer.lock
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ jobs:
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
key: 8.1-composer-${{ hashFiles('**/composer.json') }}
restore-keys: 8.1-composer-

- name: Remove composer.lock
run: rm -f composer.lock
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,53 @@ $dt0->toJsonArray();

````

`Dt0`'s can have a constructor with promoted props given they call their parent

`````php

class ConstructedDt0 extends Dt0
{
// un-casted
public readonly string $stringNoCast;

#[Cast(/*...*/)]
public readonly ?string $stringCasted;

public function __construct(
public readonly string $promotedPropNoCast,
#[Cast(/*...*/)]
public readonly string $promotedPropCasted = 'default',
// all constructor parameters, promoted on not, can be casted
#[Cast(/*...*/)]
?string $myCustomVar = null,
// Mandatory, the remaining $args will be used to further
// initialize other public properties in this class
...$args,
) {
// where the magic happens
parent::__construct(...$args);
}
}

// now you can
$dt0 = new ConstructedDt0(
promotedPropNoCast: 'The order',
promotedPropCasted: 'matters',
myCustomVar: 'for constructor parameters',
stringCasted: 'but not',
stringNoCast: 'for regular props',
);

// ::make, ::fromArray, ::fromString, ::from ... don't care about argument orders
$dt0 = ConstructedDt0::make(
stringCasted: 'The Order',
stringNoCast: 'never',
promotedPropNoCast: 'matter',
promotedPropCasted: 'outside',
myCustomVar: 'of the constructor',
);
`````

## Requirements

`Dt0` is tested against php 8.1 and 8.2
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"fab2s/math": "*"
},
"suggest": {
"fab2s/laravel-dt0": "*"
"fab2s/laravel-dt0": "To use Dt0 in Laravel (the awesome) with full validation and attribute casting",
"fab2s/Math": "To cast any Dt0 property in abitrary base ten decimals",
"nesbot/carbon": "To use CarbonCaster and handle both Carbon and CarbonImmutable casts"
},
"autoload": {
"psr-4": {
Expand Down
40 changes: 40 additions & 0 deletions src/Caster/CarbonCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of fab2s/dt0.
* (c) Fabrice de Stefanis / https://github.com/fab2s/dt0
* This source file is licensed under the MIT license which you will
* find in the LICENSE file or at https://opensource.org/licenses/MIT
*/

namespace fab2s\Dt0\Caster;

use Carbon\Carbon;
use Carbon\CarbonImmutable;
use DateTimeInterface;
use DateTimeZone;
use Exception;

class CarbonCaster implements CasterInterface
{
use DateTimeTrait;

/**
* @throws Exception
*/
public function __construct(
DateTimeZone|string|null $timeZone = null,
public readonly bool $immutable = true,
) {
$this->timeZone = $timeZone instanceof DateTimeZone ? $timeZone : ($timeZone ? new DateTimeZone($timeZone) : null);
$this->dateTimeClass = $immutable ? CarbonImmutable::class : Carbon::class;
}

/**
* @throws Exception
*/
public function cast(mixed $value): ?DateTimeInterface
{
return $this->resolve($value);
}
}
39 changes: 3 additions & 36 deletions src/Caster/DateTimeCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,17 @@
use DateTimeInterface;
use DateTimeZone;
use Exception;
use InvalidArgumentException;

class DateTimeCaster implements CasterInterface
{
public readonly ?DateTimeZone $timeZone;
protected readonly DateTimeInterface|string $dateTimeClass;
use DateTimeTrait;

/**
* @throws Exception
*/
public function __construct(
DateTimeZone|string|null $timeZone = 'UTC',
DateTimeZone|string|null $timeZone = null,
public readonly bool $immutable = true,
public readonly bool $nullable = true,
) {
$this->timeZone = $timeZone instanceof DateTimeZone ? $timeZone : ($timeZone ? new DateTimeZone($timeZone) : null);
$this->dateTimeClass = $immutable ? DateTimeImmutable::class : DateTime::class;
Expand All @@ -38,36 +35,6 @@ public function __construct(
*/
public function cast(mixed $value): ?DateTimeInterface
{
$instance = match (true) {
$value instanceof DateTimeInterface => $this->dateTimeClass::createFromInterface($value),
$this->nullable && $value === null => null,
is_array($value) => $this->fromArray($value),
is_string($value) => new $this->dateTimeClass($value),
is_int($value) => (new $this->dateTimeClass)->setTimestamp($value),
default => throw new InvalidArgumentException('Unsupported type'),
};

if ($instance && $this->timeZone) {
$instance = $instance->setTimezone($this->timeZone);
}

return $instance;
}

protected function fromArray(array $date): ?DateTimeInterface
{
$result = null;
if (! empty($date['date'])) {
$result = new $this->dateTimeClass($date['date']);
if (! empty($date['timezone'])) {
$result->setTimeZone($date['timezone'] instanceof DateTimeZone ? $date['timezone'] : new DateTimeZone($date['timezone']));
}
}

if (! $this->nullable && $result === null) {
throw new InvalidArgumentException('This Date is not nullable');
}

return $result;
return $this->resolve($value);
}
}
50 changes: 4 additions & 46 deletions src/Caster/DateTimeFormatCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,29 @@
namespace fab2s\Dt0\Caster;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Exception;
use InvalidArgumentException;

class DateTimeFormatCaster implements CasterInterface
{
public readonly ?DateTimeZone $timeZone;
protected readonly DateTimeInterface|string $dateTimeClass;
use DateTimeTrait;

/**
* @throws Exception
*/
public function __construct(
public readonly string $format,
DateTimeZone|string|null $timeZone = null,
public readonly bool $nullable = true,
) {
$this->timeZone = $timeZone instanceof DateTimeZone ? $timeZone : ($timeZone ? new DateTimeZone($timeZone) : null);
$this->timeZone = $timeZone instanceof DateTimeZone ? $timeZone : ($timeZone ? new DateTimeZone($timeZone) : null);
$this->dateTimeClass = DateTimeImmutable::class;
}

/**
* @throws Exception
*/
public function cast(mixed $value): ?string
{
$instance = match (true) {
$value instanceof DateTimeInterface => DateTimeImmutable::createFromInterface($value),
$this->nullable && $value === null => null,
is_array($value) => $this->fromArray($value),
is_string($value) => new DateTimeImmutable($value),
is_int($value) => (new DateTimeImmutable)->setTimestamp($value),
default => throw new InvalidArgumentException('Unsupported type'),
};

if ($instance) {
if ($this->timeZone) {
$instance = $instance->setTimezone($this->timeZone);
}

return $instance->format($this->format);
}

if (! $this->nullable) {
throw new InvalidArgumentException('Value is not a DateTime');
}

return null;

}

protected function fromArray(array $date): ?DateTimeInterface
{
$result = null;
if (! empty($date['date'])) {
$result = new $this->dateTimeClass($date['date']);
if (! empty($date['timezone'])) {
$result->setTimeZone($date['timezone'] instanceof DateTimeZone ? $date['timezone'] : new DateTimeZone($date['timezone']));
}
}

if (! $this->nullable && $result === null) {
throw new InvalidArgumentException('This Date is not nullable');
}

return $result;
return $this->resolve($value)?->format($this->format);
}
}
60 changes: 60 additions & 0 deletions src/Caster/DateTimeTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of fab2s/dt0.
* (c) Fabrice de Stefanis / https://github.com/fab2s/dt0
* This source file is licensed under the MIT license which you will
* find in the LICENSE file or at https://opensource.org/licenses/MIT
*/

namespace fab2s\Dt0\Caster;

use DateTimeInterface;
use DateTimeZone;
use Exception;

trait DateTimeTrait
{
public readonly ?DateTimeZone $timeZone;

/**
* @var class-string<DateTimeInterface>
*/
protected readonly string $dateTimeClass;

/**
* @throws Exception
*/
public function resolve(mixed $value): ?DateTimeInterface
{
$instance = match (true) {
$value instanceof DateTimeInterface => $this->dateTimeClass::createFromInterface($value),
is_string($value) => new $this->dateTimeClass($value),
is_array($value) => $this->fromArray($value),
is_int($value) => (new $this->dateTimeClass)->setTimestamp($value),
default => null,
};

if ($instance && $this->timeZone) {
$instance = $instance->setTimezone($this->timeZone);
}

return $instance;
}

/**
* @throws Exception
*/
public function fromArray(array $date): ?DateTimeInterface
{
$result = null;
if (! empty($date['date'])) {
$result = new $this->dateTimeClass($date['date']);
if (! empty($date['timezone'])) {
$result->setTimeZone($date['timezone'] instanceof DateTimeZone ? $date['timezone'] : new DateTimeZone($date['timezone']));
}
}

return $result;
}
}
2 changes: 2 additions & 0 deletions src/Caster/Dt0Caster.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Dt0Caster implements CasterInterface
* @throws Dt0Exception
*/
public function __construct(
/** @var class-string<Dt0> */
public readonly string $dt0Fqn,
) {
if (! is_subclass_of($dt0Fqn, Dt0::class)) {
Expand All @@ -28,6 +29,7 @@ public function __construct(

/**
* @throws JsonException
* @throws Dt0Exception
*/
public function cast(mixed $value): ?Dt0
{
Expand Down
14 changes: 1 addition & 13 deletions src/Caster/MathCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,19 @@
namespace fab2s\Dt0\Caster;

use fab2s\Math\Math;
use InvalidArgumentException;

class MathCaster implements CasterInterface
{
public readonly ?Math $number;
public readonly int $precision;

public function __construct(
int $precision = Math::PRECISION,
public readonly bool $nullable = false,
) {
$this->precision = max(0, $precision);
}

public function cast(mixed $value): ?Math
{
if ($this->nullable && $value === null) {
return null;
}

$isNumber = Math::isNumber($value);
if (! $this->nullable && ! $isNumber) {
throw new InvalidArgumentException('Value is not a number');
}

return $isNumber ? Math::number($value) : null;
return Math::isNumber($value) ? Math::number($value)->setPrecision($this->precision) : null;
}
}

0 comments on commit 067bc81

Please sign in to comment.