Let's assume I have 2 objects Product
and Volume
. 1 Product
must have at least 1 Volume
which means: Product
----> 1...*
Volume
.
I want to keep the consistence of Product
at its creation, in other words, I want to block the user to create products without any volumes.
I have a ready working code, but nonetheless I want to know your opinions guys.
Here is the running code: https://3v4l.org/t7JqO#v700
Here is my code:
<?php
declare(strict_types = 1);
namespace App
{
class Product
{
private $id;
private $name;
private $volumes;
/**
* Product constructor.
* @param int $id
* @param string $name
* @param array $volumes
* @throws \InvalidArgumentException
*/
public function __construct(int $id, string $name, array $volumes)
{
$this->id = $id;
$this->name = $name;
if (count($volumes) === 0) {
throw new \InvalidArgumentException('Your product must have 1 product at least.');
}
$this->volumes = $this->prepareVolumes($volumes);
}
private function prepareVolumes(array $volumes)
{
foreach ($volumes as $volume) {
$volume->setProduct($this);
}
return $volumes;
}
}
class Volume
{
protected $id;
protected $product;
protected $description;
public function __construct(string $description)
{
$this->description = $description;
}
public function setProduct(Product $product)
{
$this->product = $product;
}
}
class ProductTest
{
public function __construct()
{
$volume1 = new Volume('part-1');
$volume2 = new Volume('part-2');
$volume3 = new Volume('part-3');
$product = new Product(1, 'BED', [$volume1, $volume2, $volume3]);
$invalidProduct = new Product(2, 'Table', []);
}
}
return new ProductTest();
}
1 Answer 1
At first:
Note: Strict typing is only defined for scalar type declarations, and as such, requires PHP 7.0.0 or later, as scalar type declarations were added in that version.
Secondly:
You are passing in an array of volumes $volumes
to the __construct
function when instantiating an object of class Product
. But there is no confidence that each element of $volumes
is an object of Volume
class. Someone could put into $volumes
array an arbitrary value.
I would recommend applying dependency with Volume
class and pass in Product
constructor only one object of Volume
class (following the initial rule: "Product must have at least 1 Volume"). And then, if necessary, you will be able to add some additional 'volumes' to product object using an auxiliary method addVolume()
.
See the optimized version:
<?php
declare(strict_types = 1);
namespace App
{
class Product
{
private $id;
private $name;
private $volumes = [];
/**
* Product constructor.
* @param int $id
* @param string $name
* @param array $volumes
*/
public function __construct(int $id, string $name, Volume $volume)
{
$this->id = $id;
$this->name = $name;
$volume->setProduct($this);
$this->volumes[] = $volume;
}
public function addVolume(Volume $volume)
{
$volume->setProduct($this);
$this->volumes[] = $volume;
}
}
class Volume
{
protected $id;
protected $product;
protected $description;
public function __construct(string $description)
{
$this->description = $description;
}
public function setProduct(Product $product)
{
$this->product = $product;
}
}
class ProductTest
{
public function __construct()
{
$volume1 = new Volume('part-1');
$volume2 = new Volume('part-2');
$volume3 = new Volume('part-3');
$product = new Product(1, 'BED', $volume1);
$product->addVolume($volume2);
$product->addVolume($volume3);
$invalidProduct = new Product(2, 'Table', []);
}
}
return new ProductTest();
}
-
\$\begingroup\$ Great solution @romanperekhrest. I had already got the point about confidence tip and in my production code I'm using an collection instead of array. Thank you. I owe you a beer. \$\endgroup\$Daniel Lima– Daniel Lima2016年01月29日 11:38:47 +00:00Commented Jan 29, 2016 at 11:38