diff --git a/src/Entity.php b/src/Entity.php index c4d2876..5310d82 100644 --- a/src/Entity.php +++ b/src/Entity.php @@ -3,121 +3,37 @@ namespace Selami\Entity; -use JsonSerializable; use stdClass; -use Opis\JsonSchema\Validator; -use Selami\Entity\Exception\InvalidArgumentException; -use Selami\Entity\Exception\UnexpectedValueException; use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; +use JsonSerializable; +use Selami\Entity\Exception\UnexpectedValueException; final class Entity implements JsonSerializable { - /** - * @var Model - */ - private $model; - /* - * @var stdClass - */ - private $data; + use ObjectTrait; - public function __construct(Model $model, ?stdClass $data = null) + public function __construct(Model $model, UuidInterface $id, ?stdClass $data = null) { $this->model = $model; $this->data = $data; if ($data === null) { $this->data = new stdClass(); } - $this->checkAndSetId(); - } - - private function checkAndSetId() : void - { - if (!isset($this->data->id)) { - $this->data->id = Uuid::uuid4()->toString(); - } - } - - public function __get($name) - { - return $this->data->{$name}; - } - - public function __set($name, $value) : void - { - $this->data->{$name} = $value; - } - - public function __isset($name) : bool - { - return property_exists($this->data, $name); - } - - public function __unset($name) - { - unset($this->data->{$name}); - } - - public function validate() : bool - { - return $this->validateData($this->data, $this->model->getSchema()); - } - - public function validatePartially(array $requiredFields) : bool - { - $model = $this->model->getModel(); - $model->required = $requiredFields; - $schema = $this->model->getSchema($model); - return $this->validateData($this->data, $schema); - } - - private function validateData($data, $schema) : bool - { - $validation = (new Validator())->schemaValidation($data, $schema); - if (!$validation->isValid()) { - $errors = $validation->getErrors(); - $message = 'Data validation failed.' . PHP_EOL; - foreach ($errors as $error) { - $message .= sprintf( - 'ERROR: %s. %s', - $error->keyword(), - json_encode($error->keywordArgs(), JSON_PRETTY_PRINT) - ) . PHP_EOL; - } - throw new InvalidArgumentException( - $message - ); - } - return true; - } - - - public function equals($rightHandedObject) : bool - { - return (string) $this === (string) $rightHandedObject; - } - - public function jsonSerialize() : string - { - return (string) json_encode($this->data); - } - - public function __toString() - { - return $this->jsonSerialize(); + $this->data->id = $id->toString(); } - public static function createFromJsonFile($filePath) : Entity + public static function createFromJsonFile($filePath, UuidInterface $id) : Entity { if (!file_exists($filePath)) { throw new UnexpectedValueException(sprintf('Model definition file (%s) does not exist!', $filePath)); } $json = file_get_contents($filePath); - return self::createFromJson($json); + return static::createFromJson($json, $id); } - public static function createFromJson($json) : Entity + public static function createFromJson($json, UuidInterface $id) : Entity { - return new self(new Model($json)); + return new static(new Model($json), $id); } } diff --git a/src/Model.php b/src/Model.php index 1c58a22..c113e34 100644 --- a/src/Model.php +++ b/src/Model.php @@ -23,6 +23,7 @@ public function getModel() : \stdClass { return $this->model; } + public function getRequiredFields() : array { return $this->requiredFields; diff --git a/src/ObjectTrait.php b/src/ObjectTrait.php new file mode 100644 index 0000000..15e2dc8 --- /dev/null +++ b/src/ObjectTrait.php @@ -0,0 +1,89 @@ +data->{$name}; + } + + public function __set($name, $value) : void + { + $this->data->{$name} = $value; + } + + public function __isset($name) : bool + { + return property_exists($this->data, $name); + } + + public function __unset($name) + { + unset($this->data->{$name}); + } + + public function validate() : bool + { + return $this->validateData($this->data, $this->model->getSchema()); + } + + public function validatePartially(array $requiredFields) : bool + { + $model = $this->model->getModel(); + $model->required = $requiredFields; + $schema = $this->model->getSchema($model); + return $this->validateData($this->data, $schema); + } + + private function validateData($data, $schema) : bool + { + $validation = (new Validator())->schemaValidation($data, $schema); + if (!$validation->isValid()) { + $errors = $validation->getErrors(); + $message = 'Data validation failed.' . PHP_EOL; + foreach ($errors as $error) { + $message .= sprintf( + 'ERROR: %s. %s', + $error->keyword(), + json_encode($error->keywordArgs(), JSON_PRETTY_PRINT) + ) . PHP_EOL; + } + throw new InvalidArgumentException( + $message + ); + } + return true; + } + + public function equals($rightHandedObject) : bool + { + return (string) $this === (string) $rightHandedObject; + } + + public function jsonSerialize() : stdClass + { + return $this->data; + } + + public function __toString() : string + { + return (string) json_encode($this); + } +} diff --git a/src/ValueObject.php b/src/ValueObject.php new file mode 100644 index 0000000..5c0ec84 --- /dev/null +++ b/src/ValueObject.php @@ -0,0 +1,35 @@ +model = $model; + $this->data = $data; + if ($data === null) { + $this->data = new stdClass(); + } + } + + public static function createFromJsonFile($filePath) : ValueObject + { + if (!file_exists($filePath)) { + throw new UnexpectedValueException(sprintf('Model definition file (%s) does not exist!', $filePath)); + } + $json = file_get_contents($filePath); + return static::createFromJson($json); + } + public static function createFromJson($json) : ValueObject + { + return new static(new Model($json)); + } +} diff --git a/tests/resources/test-schema-value-object.json b/tests/resources/test-schema-value-object.json new file mode 100644 index 0000000..94f88fc --- /dev/null +++ b/tests/resources/test-schema-value-object.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://api.example.com/profile.json#", + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^[a-zA-Z0-9\\-]+(\\s[a-zA-Z0-9\\-]+)*$" + }, + "age": { + "type": "integer", + "minimum": 18, + "maximum": 100 + }, + "email": { + "type": "string", + "maxLength": 128, + "format": "email" + }, + "website": { + "type": ["string", "null"], + "maxLength": 128, + "format": "hostname" + }, + "location": { + "type": "object", + "properties": { + "country": { + "enum": ["US", "CA", "GB"] + }, + "address": { + "type": "string", + "maxLength": 128 + } + }, + "required": ["country", "address"], + "additionalProperties": false + }, + "available_for_hire": { + "type": "boolean" + }, + "interests": { + "type": "array", + "minItems": 3, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "maxLength": 120 + } + }, + "skills": { + "type": "array", + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLenght": 1, + "maxLength": 64 + }, + "value": { + "type": "number", + "minimum": 0, + "maximum": 100, + "multipleOf": 0.25 + } + }, + "required": ["name", "value"], + "additionalProperties": false + } + } + }, + "required": ["name", "age", "email", "location", + "available_for_hire", "interests", "skills"], + "additionalProperties": false +} \ No newline at end of file diff --git a/tests/unit/EntityTest.php b/tests/unit/EntityTest.php index 4cba7d7..32ca520 100644 --- a/tests/unit/EntityTest.php +++ b/tests/unit/EntityTest.php @@ -3,7 +3,6 @@ use Selami\Entity\Entity; use Selami\Entity\Model; use Ramsey\Uuid\Uuid; -use Selami\Entity\Exception\UnexpectedValueException; class EntityTest extends \Codeception\Test\Unit { @@ -25,7 +24,8 @@ protected function _after() */ public function shouldReturnEntityObjectSuccessfully() : void { - $entity = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); + $id = Uuid::uuid4(); + $entity = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json', $id); $entity->name = 'John Doe'; $entity->age = 31; $entity->email = "john@example.com"; @@ -52,7 +52,8 @@ public function shouldReturnEntityObjectSuccessfully() : void */ public function shouldValidatePartiallySuccessfully() : void { - $entity = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); + $id = Uuid::uuid4(); + $entity = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json', $id); $entity->name = 'John Doe'; $entity->age = 31; $requiredFields = ['name', 'age']; @@ -66,25 +67,22 @@ public function shouldValidatePartiallySuccessfully() : void */ public function shouldCompareTwoEntityObjectSuccessfully() : void { - $id = Uuid::uuid4()->toString(); - $entity1 = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); - $entity2 = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); - $entity3 = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); + $id = Uuid::uuid4(); + $entity1 = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json', $id); + $entity2 = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json', $id); + $entity3 = Entity::createFromJsonFile(__DIR__.'/../resources/test-schema.json', $id); $entity1->name = 'Kedibey'; - $entity1->id= $id; $entity1->details = new stdClass(); $entity1->details->age = 11; $entity1->details->type = 'Angora'; $entity2->name = 'Kedibey'; - $entity2->id= $id; $entity2->details = new stdClass(); $entity2->details->age = 11; $entity2->details->type = 'Angora'; $entity3->name = 'Kedibey'; - $entity3->id= $id; $entity3->details = new stdClass(); $entity3->details->age = 11; $entity3->details->type = 'Van'; @@ -102,8 +100,9 @@ public function shouldCompareTwoEntityObjectSuccessfully() : void */ public function shouldFailForRequiredInput() : void { + $id = Uuid::uuid4(); $model = Model::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); - $entity = new Entity($model); + $entity = new Entity($model, $id); $entity->name = 'John Doe'; $entity->age = 31; $entity->email = "john@example.com"; @@ -117,6 +116,7 @@ public function shouldFailForRequiredInput() : void */ public function shouldFailForAModelFileDoesNotExist() : void { - Entity::createFromJsonFile(__DIR__.'/../resources/test-schema-no-file.json'); + $id = Uuid::uuid4(); + Entity::createFromJsonFile(__DIR__.'/../resources/test-schema-no-file.json', $id); } } diff --git a/tests/unit/ValueObjectTest.php b/tests/unit/ValueObjectTest.php new file mode 100644 index 0000000..adde5b6 --- /dev/null +++ b/tests/unit/ValueObjectTest.php @@ -0,0 +1,116 @@ +name = 'John Doe'; + $valueObject->age = 31; + $valueObject->email = "john@example.com"; + $valueObject->website = null; + $valueObject->location = new stdClass(); + $valueObject->location->country = 'US'; + $valueObject->location->address = 'Sesame Street, no. 5'; + $valueObject->available_for_hire = true; + $valueObject->interests = ['oop', 'solid', 'tdd', 'ddd']; + $item =new stdClass(); + $item->name = 'PHP'; + $item->value = 100; + $valueObject->skills = [$item]; + $this->assertTrue($valueObject->validate()); + $arrayFromJson = json_decode($valueObject, true); + $this->assertEquals(31, $arrayFromJson['age']); + $this->assertTrue(isset($valueObject->name)); + unset($valueObject->name); + $this->assertFalse(isset($valueObject->name)); + } + + /** + * @test + */ + public function shouldValidatePartiallySuccessfully() : void + { + $valueObject = ValueObject::createFromJsonFile(__DIR__.'/../resources/test-schema-value-object.json'); + $valueObject->name = 'John Doe'; + $valueObject->age = 31; + $requiredFields = ['name', 'age']; + $this->assertTrue($valueObject->validatePartially($requiredFields)); + $requiredFields = ['name', 'age', 'email']; + $this->expectException(\Selami\Entity\Exception\InvalidArgumentException::class); + $valueObject->validatePartially($requiredFields); + } + /** + * @test + */ + public function shouldCompareTwoValueObjectSuccessfully() : void + { + $valueObject1 = ValueObject::createFromJsonFile(__DIR__.'/../resources/test-schema-value-object.json'); + $valueObject2 = ValueObject::createFromJsonFile(__DIR__.'/../resources/test-schema-value-object.json'); + $valueObject3 = ValueObject::createFromJsonFile(__DIR__.'/../resources/test-schema-value-object.json'); + + $valueObject1->name = 'Kedibey'; + $valueObject1->details = new stdClass(); + $valueObject1->details->age = 11; + $valueObject1->details->type = 'Angora'; + + $valueObject2->name = 'Kedibey'; + $valueObject2->details = new stdClass(); + $valueObject2->details->age = 11; + $valueObject2->details->type = 'Angora'; + + $valueObject3->name = 'Kedibey'; + $valueObject3->details = new stdClass(); + $valueObject3->details->age = 11; + $valueObject3->details->type = 'Van'; + + $this->assertTrue($valueObject1->equals($valueObject2)); + $this->assertFalse($valueObject1->equals($valueObject3)); + } + + + + + /** + * @test + * @expectedException \Selami\Entity\Exception\InvalidArgumentException + */ + public function shouldFailForRequiredInput() : void + { + $model = Model::createFromJsonFile(__DIR__.'/../resources/test-schema.json'); + $valueObject = new ValueObject($model); + $valueObject->name = 'John Doe'; + $valueObject->age = 31; + $valueObject->email = "john@example.com"; + $valueObject->website = null; + $valueObject->validate(); + } + + /** + * @test + * @expectedException \Selami\Entity\Exception\UnexpectedValueException + */ + public function shouldFailForAModelFileDoesNotExist() : void + { + ValueObject::createFromJsonFile(__DIR__.'/../resources/test-schema-no-file.json'); + } +}