Это мой демонстрационный случай:
<?php
declare(strict_types=1);
final class Order
{
/** @var array|OrderPosition[] */
private $orderPositions;
public static function fromArray($orderData)
{
assert(isset($orderData['orderId']));
assert(isset($orderData['positions']));
return
new self(
$orderData['orderId'],
array_map(
function (array $positionData): OrderPosition {
// I would like to put the "future self (Order)" alread here
return OrderPosition::fromArray($positionData);
},
$orderData['positions']
)
);
}
private function __construct(string $orderId, array $orderPositions)
{
$this->orderPositions = $orderPositions;
// what I want to avoid is:
array_walk($orderPositions, function (OrderPosition $position) {
$position->defineOwningOrder($this);
});
}
}
final class OrderPosition
{
/** @var Order */
private $owningOrder;
public static function fromArray($positionData /* + as mentioned I'd like to put the "calling" Order here already...? */)
{
return
new self(
$positionData['productId'],
$positionData['amount']
);
}
private function __construct(string $productId, int $amount)
{
// …
}
/** @internal */
public function defineOwningOrder(Order $order)
{
$this->owningOrder = $order;
}
}
Мне нравится иметь указатель на «родительский» / владеющий элемент Order в моем OrderPosition; Однако, поскольку Order считается Агрегированным Корнем, я хочу, чтобы Order отвечал за создание коллекции OrderPositions.
Как я должен поместить элемент Order в каждую позицию OrderPosition, если при создании еще нет окончательного Order?
Мой нынешний подход заключается в том, чтобы позднее установить его в ctor ордера, но это приведет к изменению OrderPosition, строго говоря.
У вас есть комбинация нескольких дизайнерских решений, которые находятся в конфликте:
Как вы говорите, ваша текущая реализация ставит под угрозу (1), позволяя OrderPosition
чтобы дополнительная ссылка была добавлена позже.
Вы можете решить проблему, если уберете (2). Какова ситуация, когда у вас будет ссылка на OrderPosition
и хотите перейти к Order
к которому он принадлежит? Можно ли перефразировать эту ситуацию как ответственность Order
, удалив круговую ссылку?
Вы можете изменить (3) так, чтобы конструктор взял информацию для создания OrderPositions
, не OrderPositions
самих себя. В вашем примере это было бы тривиально, но если на практике у вас есть несколько разных фабрик, подающих один конструктор, это может стать грязным.
В качестве альтернативы, если вы расслабитесь (4), вы можете передать частично построенный объект в OrderPosition
конструктор / фабрика:
public static function fromArray($orderData)
{
assert(isset($orderData['orderId']));
assert(isset($orderData['positions']));
$instance = new self($orderData['orderId']);
foreach ( $orderData['positions'] as $positionData ) {
$instance->orderPositions[] = OrderPosition::fromArray($positionData, $instance);
}
return $instance;
}
Хотя это все еще выглядит как мутация, это только та же мутация, которую вы делали бы в любом конструкторе — вы создаете начальное состояние объекта.
На языке, который поддерживает перегруженные или именованные конструкторы, fromArray
было бы быть конструктор, и может не использовать какую-либо реализацию с другими конструкторами. В PHP вы можете эмулировать этот шаблон с пустым private function __construct(){}
и статические методы, начинающиеся с $instance = new self;
Других решений пока нет …