Skip to content
This repository has been archived by the owner on Feb 1, 2022. It is now read-only.

Commit

Permalink
Merge pull request #4 from Jeckel-Lab/value-object
Browse files Browse the repository at this point in the history
Value object
jeckel authored Dec 19, 2019

Verified

This commit was signed with the committer’s verified signature.
zmiklank Zuzana Miklánková
2 parents 19837e4 + b4d3580 commit 424972d
Showing 20 changed files with 1,662 additions and 172 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ Just extend the `CollectionAbstract` class, and override the constructor like th

```php
<?php
use JeckelLab\Types\Collection\CollectionAbstract;
use JeckelLab\AdvancedTypes\Collection\CollectionAbstract;

class Clients extends CollectionAbstract
{
@@ -47,3 +47,45 @@ class Clients extends CollectionAbstract
}
}
```


# Value Object

## Usage with doctrine

Configure type DBAL:

```yaml
# config/packages/doctrine.yaml

doctrine:
dbal:
types:
time_duration: JeckelLab\AdvancedTypes\DBAL\Type\TimeDurationType
color: JeckelLab\AdvancedTypes\DBAL\Type\ColorType
```
Use it in your entity:
```php
<?php

use Doctrine\ORM\Mapping as ORM;
use JeckelLab\Types\ValueObject\TimeDuration;

/**
* @ORM\Entity(repositoryClass="App\Repository\TimeEntryRepository")
*/
class TimeEntry
{
// ...

/**
* @ORM\Column(type="time_duration", nullable=true)
* @var TimeDuration|null
*/
private $duration;

// ...
}
```
13 changes: 10 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jeckel-lab/advanced-types",
"description": "Advanced PHP types for DDD projects",
"type": "library",
"type": "symfony-bundle",
"license": "MIT",
"authors": [
{
@@ -12,16 +12,23 @@
"minimum-stability": "stable",
"require": {
"php": "^7.1",
"ext-json": "*",
"marc-mabe/php-enum": "^4.1"
},
"autoload": {
"psr-4": {
"JeckelLab\\Types\\": "src/"
"JeckelLab\\AdvancedTypes\\": "src/"
}
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.5",
"phpmd/phpmd": "^2.7",
"phpstan/phpstan": "^0.11.19"
"phpstan/phpstan": "^0.11.19",
"doctrine/dbal": "^2.10",
"twig/twig": "^3.0",
"symfony/http-kernel": "^4"
},
"suggest": {
"doctrine/dbal": "Integrate Value Object as doctrine types"
}
}
1,227 changes: 1,092 additions & 135 deletions composer.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/Collection/CollectionAbstract.php
Original file line number Diff line number Diff line change
@@ -5,15 +5,15 @@
* Created at : 14/11/2019
*/

namespace JeckelLab\Types\Collection;
namespace JeckelLab\AdvancedTypes\Collection;

use JeckelLab\Types\Collection\Exception\InvalidTypeException;
use JeckelLab\Types\Collection\Exception\OutOfRangeException;
use JeckelLab\AdvancedTypes\Collection\Exception\InvalidTypeException;
use JeckelLab\AdvancedTypes\Collection\Exception\OutOfRangeException;
use ArrayIterator;

/**
* Class CollectionAbstract
* @package JeckelLab\Types\Collection
* @package JeckelLab\AdvancedTypes\Collection
*/
abstract class CollectionAbstract implements CollectionInterface
{
4 changes: 2 additions & 2 deletions src/Collection/CollectionInterface.php
Original file line number Diff line number Diff line change
@@ -5,15 +5,15 @@
* Created at : 14/11/2019
*/

namespace JeckelLab\Types\Collection;
namespace JeckelLab\AdvancedTypes\Collection;

use ArrayAccess;
use Countable;
use IteratorAggregate;

/**
* Interface CollectionInterface
* @package JeckelLab\Types\Collection
* @package JeckelLab\AdvancedTypes\Collection
*/
interface CollectionInterface extends Countable, IteratorAggregate, ArrayAccess
{
4 changes: 2 additions & 2 deletions src/Collection/Exception/CollectionException.php
Original file line number Diff line number Diff line change
@@ -5,13 +5,13 @@
* Created at : 14/11/2019
*/

namespace JeckelLab\Types\Collection\Exception;
namespace JeckelLab\AdvancedTypes\Collection\Exception;

use RuntimeException;

/**
* Class CollectionException
* @package JeckelLab\Types\Collection\Exception
* @package JeckelLab\AdvancedTypes\Collection\Exception
*/
abstract class CollectionException extends RuntimeException
{
4 changes: 2 additions & 2 deletions src/Collection/Exception/InvalidTypeException.php
Original file line number Diff line number Diff line change
@@ -5,11 +5,11 @@
* Created at : 14/11/2019
*/

namespace JeckelLab\Types\Collection\Exception;
namespace JeckelLab\AdvancedTypes\Collection\Exception;

/**
* Class InvalidTypeException
* @package JeckelLab\Types\Collection\Exception
* @package JeckelLab\AdvancedTypes\Collection\Exception
*/
class InvalidTypeException extends CollectionException
{
4 changes: 2 additions & 2 deletions src/Collection/Exception/OutOfRangeException.php
Original file line number Diff line number Diff line change
@@ -5,11 +5,11 @@
* Created at : 14/11/2019
*/

namespace JeckelLab\Types\Collection\Exception;
namespace JeckelLab\AdvancedTypes\Collection\Exception;

/**
* Class OutOfRangeException
* @package JeckelLab\Types\Collection\Exception
* @package JeckelLab\AdvancedTypes\Collection\Exception
*/
class OutOfRangeException extends CollectionException
{
69 changes: 69 additions & 0 deletions src/DBAL/Types/ColorType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 20/11/2019
*/

namespace JeckelLab\AdvancedTypes\DBAL\Types;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use JeckelLab\AdvancedTypes\ValueObject\Color;

/**
* Class ColorType
* @package JeckelLab\AdvancedTypes\DBAL\Types
*/
class ColorType extends Type
{
protected const COLOR_TYPE = 'color';

/**
* @param array $fieldDeclaration
* @param AbstractPlatform $platform
* @return string
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
}

/**
* @return string
*/
public function getName(): string
{
return self::COLOR_TYPE;
}

/**
* @param mixed $value
* @param AbstractPlatform $platform
* @return Color|null
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings(PHPMD.StaticAccess)
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?Color
{
if (null === $value) {
return null;
}
return Color::byHex($value);
}

/**
* @param mixed $value
* @param AbstractPlatform $platform
* @return string|null
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value instanceof Color) {
return $value->getHex();
}
return null;
}
}
69 changes: 69 additions & 0 deletions src/DBAL/Types/TimeDurationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 18/11/2019
*/

namespace JeckelLab\AdvancedTypes\DBAL\Types;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use JeckelLab\AdvancedTypes\ValueObject\TimeDuration;

/**
* Class TimeDurationType
* @package JeckelLab\AdvancedTypes\DBAL\Types
*/
class TimeDurationType extends Type
{
protected const TIME_DURATION_TYPE = 'time_duration';

/**
* @param array $fieldDeclaration
* @param AbstractPlatform $platform
* @return string
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration);
}

/**
* @return string
*/
public function getName(): string
{
return self::TIME_DURATION_TYPE;
}

/**
* @param mixed $value
* @param AbstractPlatform $platform
* @return TimeDuration|null
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings(PHPMD.StaticAccess)
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?TimeDuration
{
if (null === $value) {
return null;
}
return new TimeDuration((int) $value);
}

/**
* @param mixed $value
* @param AbstractPlatform $platform
* @return int|null
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?int
{
if ($value instanceof TimeDuration) {
return $value->getValue();
}
return null;
}
}
38 changes: 38 additions & 0 deletions src/DependencyInjection/AdvancedTypesExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 19/12/2019
*/

namespace JeckelLab\AdvancedTypes\DependencyInjection;

use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

/**
* Class AdvancedTypesExtension
*/
class AdvancedTypesExtension extends Extension
{
/**
* {@inheritDoc}
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
}

/**
* {@inheritDoc}
*/
public function getAlias(): string
{
return 'jeckellab_advanced_types';
}
}
16 changes: 14 additions & 2 deletions src/Enum/EnumAbstract.php
Original file line number Diff line number Diff line change
@@ -5,14 +5,14 @@
* Created at : 14/11/2019
*/

namespace JeckelLab\Types\Enum;
namespace JeckelLab\AdvancedTypes\Enum;

use JsonSerializable;
use MabeEnum\Enum;

/**
* Class EnumAbstract
* @package JeckelLab\Types\Enum
* @package JeckelLab\AdvancedTypes\Enum
*/
abstract class EnumAbstract extends Enum implements JsonSerializable
{
@@ -27,4 +27,16 @@ public function jsonSerialize()
{
return $this->getValue();
}

/**
* @return string
*/
public function __toString(): string
{
$value = $this->getValue();
if (is_string($value)) {
return $value;
}
return parent::__toString();
}
}
26 changes: 26 additions & 0 deletions src/JeckelLabAdvancedTypesBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 13/11/2019
*/

namespace JeckelLab\AdvancedTypes;

use JeckelLab\AdvancedTypes\DependencyInjection\AdvancedTypesExtension;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
* Class JeckelLabCommandDispatcherBundle
*/
class JeckelLabAdvancedTypesBundle extends Bundle
{
/**
* @return AdvancedTypesExtension|ExtensionInterface|null
*/
public function getContainerExtension()
{
return new AdvancedTypesExtension();
}
}
8 changes: 8 additions & 0 deletions src/Resources/config/services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false
JeckelLab\AdvancedTypes\Twig\DurationExtension:
autowire: true
tags: [ 'twig.runtime', 'twig.extension' ]
62 changes: 62 additions & 0 deletions src/Twig/DurationExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 13/12/2019
*/

namespace JeckelLab\AdvancedTypes\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

/**
* Class DurationExtension
* @package App\Twig
*/
class DurationExtension extends AbstractExtension
{
/** @var int */
protected $dayDuration = 86400; // 60*60*24 ==> 1 day

/**
* DurationExtension constructor.
* @param int|null $dayDuration
*/
public function __construct($dayDuration = null)
{
if (null !== $dayDuration & is_numeric($dayDuration)) {
$this->dayDuration = (int) $dayDuration;
}
}

/**
* @return iterable
*/
public function getFilters(): iterable
{
return [
new TwigFilter('duration', [$this, 'formatDuration'])
];
}

/**
* @param int $duration
* @return string
*/
public function formatDuration(int $duration): string
{
$minutes = floor($duration / 60) % 60;
$hours = floor(($duration % $this->dayDuration) / 3600) ;
$days = floor($duration / $this->dayDuration);

switch (true) {
case $days > 0:
return sprintf('%dd %d:%02d', $days, $hours, $minutes);
case ($hours > 0 || $minutes > 0):
return sprintf('%d:%02d', $hours, $minutes);
default:
return '<1mn';
}
}
}
19 changes: 0 additions & 19 deletions src/TypeException.php

This file was deleted.

73 changes: 73 additions & 0 deletions src/ValueObject/Color.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 20/11/2019
*/

namespace JeckelLab\AdvancedTypes\ValueObject;

use JeckelLab\AdvancedTypes\ValueObject\Exception\InvalidArgumentException;

/**
* Class Color
* @package ValueObject
*/
class Color
{
/**
* @var string
*/
protected $color;

/**
* Color constructor.
* @param string $color
*/
protected function __construct(string $color)
{
$this->color = $color;
}

/**
* @return string
*/
public function getHex(): string
{
return $this->color;
}

/**
* @param string $color
* @return bool
*/
public static function isValidHex(string $color): bool
{
// Remove leading # if present
if (strpos($color, '#') === 0) {
$color = substr($color, 1);
}
return (ctype_xdigit($color) && strlen($color) === 6);
}

/**
* @param string $color
* @return Color
* @throws InvalidArgumentException
*/
public static function byHex(string $color): Color
{
if (! self::isValidHex($color)) {
throw new InvalidArgumentException(sprintf('Color %s is not a valid hex color', $color));
}
return new self($color);
}

/**
* @return string
*/
public function __toString(): string
{
return $this->color;
}
}
17 changes: 17 additions & 0 deletions src/ValueObject/Exception/InvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 20/11/2019
*/

namespace JeckelLab\AdvancedTypes\ValueObject\Exception;

/**
* Class InvalidArgumentException
* @package ValueObject\Exception
*/
class InvalidArgumentException extends \InvalidArgumentException implements ValueObjectExceptionInterface
{

}
19 changes: 19 additions & 0 deletions src/ValueObject/Exception/ValueObjectExceptionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 20/11/2019
*/

namespace JeckelLab\AdvancedTypes\ValueObject\Exception;

use Throwable;

/**
* Interface ValueObjectExceptionInterface
* @package ValueObject\Exception
*/
interface ValueObjectExceptionInterface extends Throwable
{

}
110 changes: 110 additions & 0 deletions src/ValueObject/TimeDuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/**
* @author Julien Mercier-Rojas <julien@jeckel-lab.fr>
* Created at : 18/11/2019
*/

namespace JeckelLab\AdvancedTypes\ValueObject;

use RuntimeException;

/**
* Class TimeDuration
*/
class TimeDuration
{
/**
* @var int
*/
protected $duration;

/**
* TimeDuration constructor.
* @param int $duration
*/
public function __construct(int $duration)
{
$this->duration = $duration;
}

/**
* @return int
*/
public function getValue(): int
{
return $this->duration;
}

/**
* @param string|null $format
* @return string
* @throw RuntimeException
*/
public function format(?string $format = null): string
{
if (null === $format) {
$format = $this->getOptimalFormat();
}
$pattern = ['/%h/', '/%m/', '/%s/', '/%M/', '/%S/'];
$hours = floor($this->duration / 3600);
$minutes = floor(($this->duration % 3600) / 60);
$seconds = $this->duration % 60;

$values = [
$hours,
$minutes,
$seconds,
sprintf('%02d', $minutes),
sprintf('%02d', $seconds)
];

$result = preg_replace($pattern, $values, $format);
if (is_string($result)) {
return $result;
}
throw new RuntimeException('Invalid format for time duration '.$format);
}

/**
* @return string
*/
protected function getOptimalFormat(): string
{
if ($this->duration > 3600) {
return '%h:%M:%S';
}
if ($this->duration > 60) {
return '%m:%S';
}
return '%s';
}

/**
* @param int $duration
* @return $this
*/
public function add(int $duration): self
{
$this->duration += $duration;
return $this;
}

/**
* @param TimeDuration $duration
* @return $this
*/
public function addDuration(TimeDuration $duration): self
{
$this->add($duration->getValue());
return $this;
}

/**
* @return string
*/
public function __toString(): string
{
return $this->format();
}
}

0 comments on commit 424972d

Please sign in to comment.