-
-
Save basherr/4f5d5fef9213587120a96988162d29bb to your computer and use it in GitHub Desktop.
| /// PRODUCT Data type | |
| <?php | |
| namespace App\DataTypes; | |
| /** | |
| * class Product | |
| * | |
| * Represent the Product type for the shopbot | |
| * | |
| * @package App\DataTypes | |
| */ | |
| class Product extends PropertyAccessor | |
| { | |
| /** | |
| * @var string | |
| */ | |
| protected $sku; | |
| /** | |
| * @var string | |
| */ | |
| protected $title; | |
| /** | |
| * @var string | |
| */ | |
| protected $image; | |
| /** | |
| * @var string | |
| */ | |
| protected $link; | |
| } | |
| /// Default Property Accessor class | |
| namespace App\DataTypes; | |
| /** | |
| * class PropertyAccessor | |
| * | |
| * Retrieve/changes any property of the class dynmically without the need for | |
| * setters & getters | |
| * | |
| * To set `foo` property for a class, just call `setFoo` method on object instance | |
| * where `Foo` first character will be converted to lowercase | |
| * | |
| * @package App\DataTypes | |
| */ | |
| abstract class PropertyAccessor | |
| { | |
| /** | |
| * Set any property for the class dynamically | |
| * | |
| * @param string $name | |
| * @param mixed $value | |
| * @return $this | |
| */ | |
| public function __call(string $name, $value) | |
| { | |
| if (substr($name, 0, 3) === 'set') { | |
| return $this->setProperty($name, $value); | |
| } else if (substr($name, 0, 3) === 'get') { | |
| return $this->getProperty($name); | |
| } | |
| } | |
| /** | |
| * Set any property for the class | |
| * | |
| * @param string $name | |
| * @param mixed $value | |
| * @return $this | |
| * @throws \App\Exception\InvalidSetterException | |
| */ | |
| protected function setProperty(string $name, $value) | |
| { | |
| $property = lcfirst(str_replace('set', '', $name)); | |
| if (property_exists($this, $property)) { | |
| $this->$property = array_shift($value); | |
| return $this; | |
| } | |
| throw new \Exception('Invalid Property Setter'); | |
| } | |
| /** | |
| * Retrieve any property for the class dynamically | |
| * | |
| * @param string $name | |
| * @return mixed | |
| * @throws \App\Exception\InvalidGetterException | |
| */ | |
| public function getProperty(string $name) | |
| { | |
| $property = lcfirst(str_replace('get', '', $key)); | |
| if (property_exists($this, $property)) { | |
| return $this->$property; | |
| } | |
| throw new \Exception('Invalid Property Getter'); | |
| } | |
| } | |
This is fine in principle:
PropertyAccessorshould be a trait rather than a base class. Something like\App\Traits\CanAccessProperties.Productshould beabstract& have anabstract public function hydrate($incoming): self.- You should probably just write a general test for this trait so you don't have to re-test it for every class.
- To allow you to easily test expected properties exist create a base/abstract
ApiModelTestCasefor the ApiModel tests to inherit from and then add a method something like:
/**
* Test we can get & set specified properties
*
* @param array $properties an array of properties that should exist
* @param object $instance the instance we are asserting against
* @param string $message optional assertion message
* @return void
*/
protected function assertPublicProperties($properties, $instance, $message = '')
{
$count = 0;
foreach ($properties as $property) {
$set = 'set' . ucfirst($property);
$get = 'get' . ucfirst($property);
$value = 'foo' . ($count++);
$instance->$set($property, $value);
$this->assertEquals($value, $instance->$get(), "$message should be able to set & get $property");
}Now your tests can just say:
public function testPublicProperties()
{
$this->assertPublicProperties(
['sku', 'image', 'title', 'link'],
new Product()
);
}
Add the following tests for your trait testing too & make them pass:
public function testItShouldThrowExceptionWhenGettingInvalidAttribute()
{
$this->expectException(InvalidMethodException::class);
$product = (new Product())
->getFoo();
}
public function testItShouldThrowExceptionWhenSettingInvalidAttribute()
{
$this->expectException(InvalidMethodException::class);
$product = (new Product())
->setFoo();
}@brentkelly Why Product should be abstract? How we will instantiate an abstract class Product? What I believe it should implement a contract to hydrate or extend a base abstract class that contains hydrate method as abstract.
Because Zest isn't going to be the only Connector we have long term. It should be App\ApiModels\AbstractProduct which defines 90% of the class, but then there needs to be a connector implementation for Zest which tells it how to hydrate from a API result.
So \App\Connectors\Zest\ApiModels\Product extends App\ApiModels\AbstractProduct & probably has 1 method:
hydrate($productData)- this receives a raw API product data & hydrates the properties defined inAbstractProduct.
Then in future when we want to connect to e.g. Shopify, we will have a \App\Connectors\Shopify\Product which will extend AbstractProduct, and its hydrate method will take a Shopify product API result & translate it to hydrate the AbstractProduct properties.
@brentkelly Perfect, thanks.
Here's the test for it.