8000 create lazy entity references by ro0NL · Pull Request #230 · msgphp/msgphp · GitHub
[go: up one dir, main page]

Skip to content

create lazy entity references #230

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

8000
Merged
merged 1 commit into from
Nov 10, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions UPGRADE-0.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- Updated `DomainMessageBusInterface::dispatch()` to return `void`
- Updated `DomainCollectionInterface::map()` to return `DomainCollectionInterface`
- Renamed `TreeBuilder` to `TreeBuilderHelper`
- Removed `ChainObjectFactory` and `ClassMappingObjectFactory`
- Added `DomainObjectFactoryInterface::getClass()`

## UserEav

Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"description": "Composer configuration for bulk CI testing of packages",
"prefer-stable": true,
"minimum-stability": "dev",
"require-dev": {
"wikimedia/composer-merge-plugin": "^1.4",
"phpstan/phpstan-shim": "^0.10",
"phpstan/phpstan-phpunit": "^0.10",
"phpunit/phpunit": "^7.4",
"ro0nl/link": "^1.0",
"symfony/config": "@dev",
"symfony/messenger": "@dev",
"symfony/var-dumper": "^4.1",
"symfony/config": "^4.2",
"twig/twig": "^2.5"
},
"autoload-dev": {
Expand Down
21 changes: 13 additions & 8 deletions docs/ddd/factory/entity-aware.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,19 @@ value might be considered empty if it's not capable to calculate one upfront.

### `MsgPhp\Domain\Factory\EntityAwareFactory`

A generic entity factory. It decorates any object factory and uses the [domain identity mapping](../identity-mapping.md)
as well as a known entity to identifier class mapping.
A generic entity factory.

- `__construct(DomainObjectFactoryInterface $factory, DomainIdentityMappingInterface $identityMapping, array $identifierMapping = [])`
- `$factory`: The decorated object factory
- `$identityMapping`: The identity mapping
- `__construct(DomainObjectFactoryInterface $factory, DomainIdentityHelper $identityHelper, array $identifierMapping = [])`
- `$factory`: The decorated [object factory](object.md)
- `$identityHelper`: The [identity helper](../identities.md)
- `$identifierMapping`: The identifier class mapping (`['EntityType' => 'IdType']`)

#### Basic example

```php
<?php

use MsgPhp\Domain\DomainId;
use MsgPhp\Domain\{DomainId, DomainIdentityHelper};
use MsgPhp\Domain\Factory\{DomainObjectFactory, EntityAwareFactory};
use MsgPhp\Domain\Infra\InMemory\DomainIdentityMapping;

Expand All @@ -64,9 +63,9 @@ class MyEntity

$factory = new EntityAwareFactory(
new DomainObjectFactory(),
new DomainIdentityMapping([
new DomainIdentityHelper(new DomainIdentityMapping([
MyEntity::class => 'id',
]),
])),
[
MyEntity::class => DomainId::class,
]
Expand All @@ -83,8 +82,14 @@ $id = $factory->identify(MyEntity::class, 1);
/** @var DomainId $id */
$id = $factory->nextIdentifier(MyEntity::class);
```

!!! note
`EntityAwareFactory::reference()` requires [symfony/var-exporter]

### `MsgPhp\Domain\Infra\Doctrine\EntityAwareFactory`

A Doctrine tailored entity aware factory.

- [Read more](../../infrastructure/doctrine-orm.md#entity-aware-factory)

[symfony/var-exporter]: https://packagist.org/packages/symfony/var-exporter
87 changes: 20 additions & 67 deletions docs/ddd/factory/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ any domain object based on a given class name and context.

Returns a factorized domain object by class name. Optionally a context can be provided for the factory to act upon.

### `getClass(string $class, array $context = []): string`

Returns the actual class name the factory will create and equalizes `get_class($factory->create($class, $context))`.

## Implementations

### `MsgPhp\Domain\Factory\DomainObjectFactory`
Expand All @@ -21,9 +25,12 @@ higher precedence. In case the context key is numeric its value will be provided
Any sub class of `MsgPhp\Domain\DomainIdInterface` or `MsgPhp\Domain\DomainCollectionInterface` will be initialized
using `$class::fromValue()` by default, otherwise initialization happens regularly (i.e. `new $class(...$arguments)`).

A class mapping can be provided and is usually used to map abstracts to concretes.

Nested objects (e.g. `MyObject $myArgument`) can be provided as nested context (thus nested array).

- `setNestedFactory(?DomainObjectFactoryInterface $factory): void`
- `$classMapping`: The class mapping (`['SourceType' => 'TargetType']`)`
- `$factory`: The optional factory to use for nested objects. If not set the current factory will be used instead.

#### Basic example
Expand All @@ -35,7 +42,11 @@ use MsgPhp\Domain\Factory\DomainObjectFactory;

// --- SETUP ---

class Some
interface KnownInterface
{
}

class Some implements KnownInterface
{
public function __construct(int $a, ?int $b, ?int $c)
{
Expand All @@ -44,15 +55,21 @@ class Some

class Subject
{
public function __construct(string $argument, Some $some, Subject $otherSubject = null)
public function __construct(string $argument, KnownInterface $some, Subject $otherSubject = null)
{
}
}

$factory = new DomainObjectFactory();
$factory = new DomainObjectFactory([
6293 KnownInterface::class => Some::class,
]);

// --- USAGE ---

/** @var Some $object */
$object = $factory->create(KnownInterface::class, ['a' => 1]);
$factory->getClass(KnownInterface::class); // "Some"

/** @var Subject $object */
$object = $factory->create(Subject::class, [
'argument' => 'value',
Expand All @@ -63,67 +80,3 @@ $object = $factory->create(Subject::class, [
],
]);
```

### `MsgPhp\Domain\Factory\ChainObjectFactory`

A chain object factory. It holds many object factories and returns a domain object from the first supporting factory.

- `__construct(iterable $factories)`
- `$factories`: Available object factories

#### Basic example

```php
<?php

use MsgPhp\Domain\Factory\{ChainObjectFactory, DomainObjectFactory, DomainObjectFactoryInterface};

// --- SETUP ---

class MyFactory implements DomainObjectFactoryInterface
{
public function create(string $class, array $context = [])
{
// ...
}
}

$factory = new ChainObjectFactory([new MyFactory(), new DomainObjectFactory()]);
```

### `MsgPhp\Domain\Factory\ClassMappingObjectFactory`

A class mapping object factory. It decorates any object factory and resolves the actual class name from a provided
mapping. It's usually used to map abstracts to concretes. In case the class is not mapped it will be used as is.

- `__construct(DomainObjectFactoryInterface $factory, array $mapping)`
- `$factory`: The decorated object factory
- `$mapping`: The class mapping (`['SourceType' => 'TargetType']`)

#### Basic example

```php
<?php

use MsgPhp\Domain\Factory\{ClassMappingObjectFactory, DomainObjectFactory};

// --- SETUP ---

interface KnownInterface
{
}

class Subject implements KnownInterface
{
}

$factory = new ClassMappingObjectFactory(
new DomainObjectFactory(),
[KnownInterface::class => Subject::class]
);

// --- USAGE ---

/** @var Subject $object */
$object = $factory->create(KnownInterface::class);
```
35 changes: 0 additions & 35 deletions src/Domain/Factory/ChainObjectFactory.php

This file was deleted.

25 changes: 0 additions & 25 deletions src/Domain/Factory/ClassMappingObjectFactory.php

This file was deleted.

13 changes: 13 additions & 0 deletions src/Domain/Factory/DomainObjectFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,23 @@
*/
final class DomainObjectFactory implements DomainObjectFactoryInterface
{
private $classMapping;
private $factory;

public function __construct(array $classMapping = [])
{
$this->classMapping = $classMapping;
}

public function setNestedFactory(?DomainObjectFactoryInterface $factory): void
{
$this->factory = $factory;
}

public function create(string $class, array $context = [])
{
$class = $this->getClass($class, $context);

if (is_subclass_of($class, DomainIdInterface::class) || is_subclass_of($class, DomainCollectionInterface::class)) {
return $class::fromValue(...$this->resolveArguments($class, 'fromValue', $context));
}
Expand All @@ -32,6 +40,11 @@ public function create(string $class, array $context = [])
return new $class(...$this->resolveArguments($class, '__construct', $context));
}

public function getClass(string $class, array $context = []): string
{
return $this->classMapping[$class] ?? $class;
}

private function resolveArguments(string $class, string $method, array $context): array
{
$arguments = [];
Expand Down
2 changes: 2 additions & 0 deletions src/Domain/Factory/DomainObjectFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ interface DomainObjectFactoryInterface
* @return object
*/
public function create(string $class, array $context = []);

public function getClass(string $class, array $context = []): string;
}
41 changes: 33 additions & 8 deletions src/Domain/Factory/EntityAwareFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@

namespace MsgPhp\Domain\Factory;

use MsgPhp\Domain\{DomainIdentityMappingInterface, DomainIdInterface};
use MsgPhp\Domain\{DomainIdentityHelper, DomainIdInterface};
use MsgPhp\Domain\Exception\InvalidClassException;
use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
use Symfony\Component\VarExporter\Instantiator;

/**
* @author Roland Franssen <franssen.roland@gmail.com>
*/
final class EntityAwareFactory implements EntityAwareFactoryInterface
{
private $factory;
private $identityMapping;
private $identityHelper;
private $identifierMapping;

public function __construct(DomainObjectFactoryInterface $factory, DomainIdentityMappingInterface $identityMapping, array $identifierMapping = [])
public function __construct(DomainObjectFactoryInterface $factory, DomainIdentityHelper $identityHelper, array $identifierMapping = [])
{
$this->factory = $factory;
$this->identityMapping = $identityMapping;
$this->identityHelper = $identityHelper;
$this->identifierMapping = $identifierMapping;
}

Expand All @@ -28,15 +30,38 @@ public function create(string $class, array $context = [])
return $this->factory->create($class, $context);
}

public function getClass(string $class, array $context = []): string
{
return $this->factory->getClass($class, $context);
}

public function reference(string $class, $id)
{
$idFields = $this->identityMapping->getIdentifierFieldNames($class);
if (!class_exists(Instantiator::class)) {
throw new \LogicException(sprintf('Method "%s()" requires "symfony/var-exporter".', __METHOD__));
}

$class = $this->factory->getClass($class);

if (!\is_array($id)) {
$id = [array_shift($idFields) => $id];
if (!$this->identityHelper->isIdentity($class, $id)) {
throw new \LogicException(sprintf('Invalid identity %s for class "%s".', (string) json_encode($id), $class));
}

return $this->factory->create($class, $id + array_fill_keys($idFields, null));
$properties = [];
foreach ($this->identityHelper->toIdentity($class, $id) as $field => $value) {
if (property_exists($class, $field)) {
$properties[$field] = $value;
continue;
}

$properties[lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $field))))] = $value;
}

try {
return Instantiator::instantiate($class, $properties);
} catch (ClassNotFoundException $e) {
throw InvalidClassException::create($class);
}
}

public function identify(string $class, $value): DomainIdInterface
Expand Down
Loading
0