diff --git a/.gitignore b/.gitignore index 57872d0..4638799 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /vendor/ +/.phpunit.result.cache +/.idea/ diff --git a/composer.json b/composer.json index 9611131..36d709e 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "type": "project", "autoload": { "psr-4": { - "Tymeshift\\PhpTest\\": "src/" + "Tymeshift\\PhpTest\\": "src/", + "Tests\\": "tests/" } }, "authors": [ @@ -17,6 +18,10 @@ "codeception/codeception": "^4.1", "mockery/mockery": "^1.4", "codeception/module-phpbrowser": "^1.0.0", - "codeception/module-asserts": "^1.3" + "codeception/module-asserts": "^1.3", + "phpstan/phpstan": "^1.10" + }, + "require": { + "ext-json": "*" } } diff --git a/composer.lock b/composer.lock index 9ba07ad..5074cd3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "061898b01c4a0e5f714ffdbc521d1d7e", + "content-hash": "71f065a122ac5f4383333327dbd7ecfc", "packages": [], "packages-dev": [ { @@ -1446,6 +1446,68 @@ }, "time": "2021-09-10T09:02:12+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.10.38", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691", + "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2023-10-06T14:19:14+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.7", @@ -4576,7 +4638,9 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "ext-json": "*" + }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.6.0" } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..9c722bc --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,201 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method TValue of Tymeshift\\\\PhpTest\\\\Interfaces\\\\EntityInterface\\:\\:getId\\(\\)\\.$#" + count: 2 + path: src/Base/BaseCollection.php + + - + message: "#^Call to an undefined method TValue of Tymeshift\\\\PhpTest\\\\Interfaces\\\\EntityInterface\\:\\:toArray\\(\\)\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Call to method build\\(\\) on an unknown class Tymeshift\\\\PhpTest\\\\Base\\\\FactoryInterfaceAlias\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Class Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection has @extends tag, but does not extend any class\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Class Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection implements generic interface Tymeshift\\\\PhpTest\\\\Interfaces\\\\CollectionInterface but does not specify its types\\: TValue, TFactory$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Generic type ArrayAccess\\ in PHPDoc tag @implements does not specify all template types of interface ArrayAccess\\: TKey, TValue$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:add\\(\\) return type has no value type specified in iterable type Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:add\\(\\) return type with generic class Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection does not specify its types\\: TValue, TFactory$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:buildFromArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:buildFromArray\\(\\) return type has no value type specified in iterable type Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:buildFromArray\\(\\) return type with generic class Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection does not specify its types\\: TValue, TFactory$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:createFromArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:createFromArray\\(\\) has parameter \\$factory with generic interface Tymeshift\\\\PhpTest\\\\Interfaces\\\\FactoryInterface but does not specify its types\\: TData, TCollection$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:createFromArray\\(\\) return type has no value type specified in iterable type Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:createFromArray\\(\\) return type with generic class Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection does not specify its types\\: TValue, TFactory$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:filter\\(\\) should return \\$this\\(Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\) but returns static\\(Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\)\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:getAssoc\\(\\) has invalid return type Tymeshift\\\\PhpTest\\\\Base\\\\Collection\\.$#" + count: 2 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:getAssoc\\(\\) should return Tymeshift\\\\PhpTest\\\\Base\\\\Collection but returns static\\(Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\)\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:getIds\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:getIterator\\(\\) return type has no value type specified in iterable type Traversable\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:isEntity\\(\\) has parameter \\$item with no type specified\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:last\\(\\) should return Tymeshift\\\\PhpTest\\\\Interfaces\\\\EntityInterface but returns \\(TValue of Tymeshift\\\\PhpTest\\\\Interfaces\\\\EntityInterface\\)\\|false\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:map\\(\\) should return \\$this\\(Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\) but returns static\\(Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\)\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:pluck\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:toJson\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:toJson\\(\\) has parameter \\$options with no type specified\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Parameter \\#1 \\$key \\(string\\) of method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:offsetUnset\\(\\) should be compatible with parameter \\$offset \\(Tymeshift\\\\PhpTest\\\\Interfaces\\\\EntityInterface\\) of method ArrayAccess\\\\:\\:offsetUnset\\(\\)$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Parameter \\#1 \\$key of function array_key_exists expects int\\|string, mixed given\\.$#" + count: 1 + path: src/Base/BaseCollection.php + + - + message: "#^Parameter \\$factory of method Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\:\\:buildFromArray\\(\\) has invalid type Tymeshift\\\\PhpTest\\\\Base\\\\FactoryInterfaceAlias\\.$#" + count: 2 + path: src/Base/BaseCollection.php + + - + message: "#^Property Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\:\\:\\$items \\(array\\\\) does not accept array\\.$#" + count: 2 + path: src/Base/BaseCollection.php + + - + message: "#^Property Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\\\:\\:\\$items \\(array\\\\) does not accept array\\\\.$#" + count: 2 + path: src/Base/BaseCollection.php + + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 3 + path: src/Base/BaseCollection.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Schedule\\\\ScheduleFactory\\:\\:createCollection\\(\\) should return Tymeshift\\\\PhpTest\\\\Domains\\\\Schedule\\\\ScheduleCollection but returns Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\.$#" + count: 1 + path: src/Domains/Schedule/ScheduleFactory.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Schedule\\\\ScheduleStorage\\:\\:getById\\(\\) should return array\\{id\\?\\: int, start_time\\?\\: int, end_time\\?\\: int, name\\?\\: string\\} but returns array\\.$#" + count: 1 + path: src/Domains/Schedule/ScheduleStorage.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Schedule\\\\ScheduleStorage\\:\\:getByIds\\(\\) should return array\\ but returns array\\.$#" + count: 1 + path: src/Domains/Schedule/ScheduleStorage.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Task\\\\TaskFactory\\:\\:createCollection\\(\\) should return Tymeshift\\\\PhpTest\\\\Domains\\\\Task\\\\TaskCollection but returns Tymeshift\\\\PhpTest\\\\Base\\\\BaseCollection\\.$#" + count: 1 + path: src/Domains/Task/TaskFactory.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Task\\\\TaskStorage\\:\\:getById\\(\\) should return array\\{id\\?\\: int, start_time\\?\\: int, duration\\?\\: int, schedule_id\\?\\: int\\} but returns array\\.$#" + count: 1 + path: src/Domains/Task/TaskStorage.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Task\\\\TaskStorage\\:\\:getByIds\\(\\) should return array\\ but returns array\\.$#" + count: 1 + path: src/Domains/Task/TaskStorage.php + + - + message: "#^Method Tymeshift\\\\PhpTest\\\\Domains\\\\Task\\\\TaskStorage\\:\\:getByScheduleId\\(\\) should return array\\ but returns array\\.$#" + count: 1 + path: src/Domains/Task/TaskStorage.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..e062a33 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,3 @@ +parameters: + level: max + treatPhpDocTypesAsCertain: false diff --git a/src/Base/BaseCollection.php b/src/Base/BaseCollection.php index 2af6a00..2d817c1 100644 --- a/src/Base/BaseCollection.php +++ b/src/Base/BaseCollection.php @@ -13,14 +13,26 @@ use Tymeshift\PhpTest\Interfaces\EntityInterface; use Tymeshift\PhpTest\Interfaces\FactoryInterface; -abstract class BaseCollection implements IteratorAggregate, Countable, ArrayAccess, JsonSerializable, CollectionInterface +/** + * @template TValue of EntityInterface + * @template TFactory of FactoryInterface + * @template-extends CollectionInterface + * @implements ArrayAccess + * @implements IteratorAggregate + */ +abstract class BaseCollection implements + IteratorAggregate, + Countable, + ArrayAccess, + JsonSerializable, + CollectionInterface { - /** @var EntityInterface[] */ + /** @var array */ protected $items = []; /** * BaseCollection constructor. - * @param array $data + * @param array $data */ public function __construct(array $data = []) { @@ -37,9 +49,10 @@ public function __construct(array $data = []) * @param EntityInterface $entity * @return BaseCollection */ - public function add(EntityInterface $entity):CollectionInterface + public function add(EntityInterface $entity): CollectionInterface { $this->items[] = $entity; + return $this; } @@ -66,7 +79,7 @@ public function createFromArray(array $data, FactoryInterface $factory): Collect /** * @return array */ - public function toArray():array + public function toArray(): array { $result = []; @@ -92,7 +105,7 @@ public function toJson($options = 0) public function search($key, $value) { foreach ($this->items as $item) { - if ($item->{'get' . $key}() === $value) { + if ($item->{'get'.$key}() === $value) { return $item; } } @@ -109,11 +122,13 @@ public function search($key, $value) public function remove($key, $value) { foreach ($this->items as $index => $item) { - if ($item->{'get' . $key}() === $value) { + if ($item->{'get'.$key}() === $value) { unset($this->items[$index]); + return true; } } + return null; } @@ -246,7 +261,7 @@ public function jsonSerialize() /** * Execute a callback over each item. * - * @param callable $callback + * @param callable $callback * @return $this */ public function each(callable $callback): self @@ -256,6 +271,7 @@ public function each(callable $callback): self break; } } + return $this; } @@ -289,6 +305,7 @@ public function getById($id): ?EntityInterface return $entity; } } + return null; } @@ -300,8 +317,9 @@ public function pluck(string $property): array { $data = []; foreach ($this->items as $entity) { - $data[] = $entity->{'get' . ucfirst($property)}(); + $data[] = $entity->{'get'.ucfirst($property)}(); } + return $data; } @@ -310,11 +328,11 @@ public function pluck(string $property): array * @return Collection * @throws InvalidCollectionDataProvidedException */ - public function getAssoc(string $property = 'id'):Collection + public function getAssoc(string $property = 'id'): Collection { $items = []; foreach ($this->items as $index => $entity) { - $newKey = $entity->{'get' . ucfirst($property)}(); + $newKey = $entity->{'get'.ucfirst($property)}(); $items[$newKey] = $this->items[$index]; } @@ -330,16 +348,18 @@ public function getIds(): array foreach ($this->items as $entity) { $result[] = $entity->getId(); } + return array_unique($result); } /** * @return EntityInterface */ - public function last():EntityInterface + public function last(): EntityInterface { $lastItem = end($this->items); reset($this->items); + return $lastItem; } -} \ No newline at end of file +} diff --git a/src/Components/DatabaseInterface.php b/src/Components/DatabaseInterface.php index 40e041b..39081d8 100644 --- a/src/Components/DatabaseInterface.php +++ b/src/Components/DatabaseInterface.php @@ -7,8 +7,8 @@ interface DatabaseInterface { /** * @param string $query - * @param array $params - * @return array + * @param array $params + * @return array */ public function query(string $query, array $params):array; -} \ No newline at end of file +} diff --git a/src/Components/HttpClientInterface.php b/src/Components/HttpClientInterface.php index f3ea554..3ee88c8 100644 --- a/src/Components/HttpClientInterface.php +++ b/src/Components/HttpClientInterface.php @@ -1,4 +1,5 @@ */ public function request(string $method, string $uri):array; -} \ No newline at end of file +} diff --git a/src/Domains/Schedule/ScheduleCollection.php b/src/Domains/Schedule/ScheduleCollection.php new file mode 100644 index 0000000..9230f85 --- /dev/null +++ b/src/Domains/Schedule/ScheduleCollection.php @@ -0,0 +1,14 @@ + + */ +final class ScheduleCollection extends BaseCollection +{ + +} diff --git a/src/Domains/Schedule/ScheduleEntity.php b/src/Domains/Schedule/ScheduleEntity.php index b4e7948..af7d73c 100644 --- a/src/Domains/Schedule/ScheduleEntity.php +++ b/src/Domains/Schedule/ScheduleEntity.php @@ -3,105 +3,81 @@ namespace Tymeshift\PhpTest\Domains\Schedule; -use DateTime; use Tymeshift\PhpTest\Interfaces\EntityInterface; -class ScheduleEntity implements EntityInterface +final class ScheduleEntity implements EntityInterface { - /** - * @var int - */ private int $id; - /** - * @var string - */ private string $name; - /** - * @var DateTime - */ - private DateTime $startTime; + private \DateTimeInterface $startTime; - /** - * @var DateTime - */ - private DateTime $endTime; + private \DateTimeInterface $endTime; /** * @var ScheduleItemInterface[] */ private array $items; - /** - * @return int - */ public function getId(): int { return $this->id; } - /** - * @param int $id - * @return ScheduleEntity - */ public function setId(int $id): ScheduleEntity { $this->id = $id; return $this; } - /** - * @return string - */ public function getName(): string { return $this->name; } - /** - * @param string $name - * @return ScheduleEntity - */ public function setName(string $name): ScheduleEntity { $this->name = $name; return $this; } - /** - * @return DateTime - */ - public function getStartTime(): DateTime + public function getStartTime(): \DateTimeInterface { return $this->startTime; } - /** - * @param DateTime $startTime - * @return ScheduleEntity - */ - public function setStartTime(DateTime $startTime): ScheduleEntity + public function setStartTime(\DateTimeInterface $startTime): ScheduleEntity { $this->startTime = $startTime; + return $this; } - /** - * @return DateTime - */ - public function getEndTime(): DateTime + public function getEndTime(): \DateTimeInterface { return $this->endTime; } + public function setEndTime(\DateTimeInterface $endTime): ScheduleEntity + { + $this->endTime = $endTime; + + return $this; + } + /** - * @param DateTime $endTime - * @return ScheduleEntity + * @return ScheduleItemInterface[] */ - public function setEndTime(DateTime $endTime): ScheduleEntity + public function getItems(): array { - $this->endTime = $endTime; + return $this->items; + } + + public function addItem(ScheduleItemInterface $item): self + { + $this->items[] = $item; + return $this; } -} \ No newline at end of file +} diff --git a/src/Domains/Schedule/ScheduleFactory.php b/src/Domains/Schedule/ScheduleFactory.php index 82444af..d237396 100644 --- a/src/Domains/Schedule/ScheduleFactory.php +++ b/src/Domains/Schedule/ScheduleFactory.php @@ -1,16 +1,21 @@ + */ +final class ScheduleFactory implements FactoryInterface { - - public function createEntity(array $data): EntityInterface + public function createEntity(array $data): ScheduleEntity { $entity = new ScheduleEntity(); if (isset($data['id']) && is_int($data['id'])) { @@ -31,4 +36,9 @@ public function createEntity(array $data): EntityInterface return $entity; } -} \ No newline at end of file + + public function createCollection(array $data): ScheduleCollection + { + return (new ScheduleCollection())->createFromArray($data, $this); + } +} diff --git a/src/Domains/Schedule/ScheduleItem.php b/src/Domains/Schedule/ScheduleItem.php new file mode 100644 index 0000000..5bc8e8e --- /dev/null +++ b/src/Domains/Schedule/ScheduleItem.php @@ -0,0 +1,40 @@ +scheduleId = $scheduleId; + $this->startTime = $startTime; + $this->endTime = $endTime; + $this->type = $type; + } + + public function getScheduleId(): int + { + return $this->scheduleId; + } + + public function getStartTime(): int + { + return $this->startTime; + } + + public function getEndTime(): int + { + return $this->endTime; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/src/Domains/Schedule/ScheduleItemInterface.php b/src/Domains/Schedule/ScheduleItemInterface.php index d7cb063..e5f494c 100644 --- a/src/Domains/Schedule/ScheduleItemInterface.php +++ b/src/Domains/Schedule/ScheduleItemInterface.php @@ -5,23 +5,11 @@ interface ScheduleItemInterface { - /** - * @return int - */ - public function getScheduleId():int; + public function getScheduleId(): int; - /** - * @return int - */ - public function getStartTime():int; + public function getStartTime(): int; - /** - * @return int - */ - public function getEndTime():int; + public function getEndTime(): int; - /** - * @return string - */ - public function getType():string; -} \ No newline at end of file + public function getType(): string; +} diff --git a/src/Domains/Schedule/ScheduleRepository.php b/src/Domains/Schedule/ScheduleRepository.php index ac74ec5..af7083d 100644 --- a/src/Domains/Schedule/ScheduleRepository.php +++ b/src/Domains/Schedule/ScheduleRepository.php @@ -1,26 +1,38 @@ storage = $storage; $this->factory = $factory; } - public function getById(int $id):EntityInterface + public function getById(int $id): ScheduleEntity { $data = $this->storage->getById($id); + + if (empty($data)) { + throw new StorageDataMissingException('Schedule not found'); + } + return $this->factory->createEntity($data); } -} \ No newline at end of file + + public function getByIds(array $ids): ScheduleCollection + { + $data = $this->storage->getByIds($ids); + + return $this->factory->createCollection($data); + } +} diff --git a/src/Domains/Schedule/ScheduleRepositoryInterface.php b/src/Domains/Schedule/ScheduleRepositoryInterface.php new file mode 100644 index 0000000..50bb030 --- /dev/null +++ b/src/Domains/Schedule/ScheduleRepositoryInterface.php @@ -0,0 +1,13 @@ + + */ +interface ScheduleRepositoryInterface extends RepositoryInterface +{ +} diff --git a/src/Domains/Schedule/ScheduleService.php b/src/Domains/Schedule/ScheduleService.php new file mode 100644 index 0000000..3582405 --- /dev/null +++ b/src/Domains/Schedule/ScheduleService.php @@ -0,0 +1,36 @@ +scheduleRepository = $scheduleRepository; + $this->taskRepository = $taskRepository; + $this->taskToScheduleItemMapper = $taskToScheduleItemMapper; + } + + public function fillScheduleItems(int $id): ScheduleEntity + { + $schedule = $this->scheduleRepository->getById($id); + $tasks = $this->taskRepository->getByScheduleId($id); + + foreach ($tasks as $task) { + $schedule->addItem($this->taskToScheduleItemMapper->map($task)); + } + + return $schedule; + } +} diff --git a/src/Domains/Schedule/ScheduleServiceInterface.php b/src/Domains/Schedule/ScheduleServiceInterface.php new file mode 100644 index 0000000..a149101 --- /dev/null +++ b/src/Domains/Schedule/ScheduleServiceInterface.php @@ -0,0 +1,9 @@ +db = $database; } + /** + * @return array{ + * id?: int, + * start_time?: int, + * end_time?: int, + * name?: string + * } + */ public function getById(int $id): array { return $this->db->query('SELECT * FROM schedules WHERE id=:id', ["id" => $id]); } + /** + * @param array $ids + * @return array + */ public function getByIds(array $ids): array { return $this->db->query('SELECT * FROM schedules WHERE id in (:ids)', $ids); } -} \ No newline at end of file +} diff --git a/src/Domains/Schedule/ScheduleStorageInterface.php b/src/Domains/Schedule/ScheduleStorageInterface.php new file mode 100644 index 0000000..3dd29cc --- /dev/null +++ b/src/Domains/Schedule/ScheduleStorageInterface.php @@ -0,0 +1,28 @@ + $ids + * @return array + */ + public function getByIds(array $ids): array; +} diff --git a/src/Domains/Schedule/TaskToScheduleItemMapper.php b/src/Domains/Schedule/TaskToScheduleItemMapper.php new file mode 100644 index 0000000..cd90f63 --- /dev/null +++ b/src/Domains/Schedule/TaskToScheduleItemMapper.php @@ -0,0 +1,19 @@ +getScheduleId(), + $task->getStartTime(), + $task->getStartTime() + $task->getDuration(), + get_class($task) + ); + } +} diff --git a/src/Domains/Task/TaskCollection.php b/src/Domains/Task/TaskCollection.php index 04593d3..1765c96 100644 --- a/src/Domains/Task/TaskCollection.php +++ b/src/Domains/Task/TaskCollection.php @@ -1,10 +1,14 @@ + */ +final class TaskCollection extends BaseCollection { -} \ No newline at end of file +} diff --git a/src/Domains/Task/TaskEntity.php b/src/Domains/Task/TaskEntity.php index 4b076fa..0d4a700 100644 --- a/src/Domains/Task/TaskEntity.php +++ b/src/Domains/Task/TaskEntity.php @@ -5,7 +5,61 @@ use Tymeshift\PhpTest\Interfaces\EntityInterface; -class TaskEntity implements EntityInterface +final class TaskEntity implements EntityInterface { + private int $id; -} \ No newline at end of file + private int $scheduleId; + + private int $startTime; + + private int $duration; + + public function getId(): int + { + return $this->id; + } + + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } + + public function getStartTime(): int + { + return $this->startTime; + } + + public function setStartTime(int $startTime): self + { + $this->startTime = $startTime; + + return $this; + } + + public function getScheduleId(): int + { + return $this->scheduleId; + } + + public function setScheduleId(int $scheduleId): self + { + $this->scheduleId = $scheduleId; + + return $this; + } + + public function getDuration(): int + { + return $this->duration; + } + + public function setDuration(int $duration): self + { + $this->duration = $duration; + + return $this; + } +} diff --git a/src/Domains/Task/TaskFactory.php b/src/Domains/Task/TaskFactory.php index 2ccfa22..cdf0794 100644 --- a/src/Domains/Task/TaskFactory.php +++ b/src/Domains/Task/TaskFactory.php @@ -3,24 +3,42 @@ namespace Tymeshift\PhpTest\Domains\Task; -use Tymeshift\PhpTest\Exceptions\InvalidCollectionDataProvidedException; -use Tymeshift\PhpTest\Interfaces\CollectionInterface; -use Tymeshift\PhpTest\Interfaces\EntityInterface; use Tymeshift\PhpTest\Interfaces\FactoryInterface; -class TaskFactory implements FactoryInterface +/** + * @implements FactoryInterface + */ +final class TaskFactory implements FactoryInterface { - - public function createEntity(array $data): EntityInterface + public function createEntity(array $data): TaskEntity { - // TODO: Implement createEntity() method. + $entity = new TaskEntity(); + if (isset($data['id']) && is_int($data['id'])) { + $entity->setId($data['id']); + } + + if (isset($data['start_time']) && is_int($data['start_time'])) { + $entity->setStartTime($data['start_time']); + } + + if (isset($data['duration']) && is_int($data['duration'])) { + $entity->setDuration($data['duration']); + } + + if (isset($data['schedule_id']) && is_int($data['schedule_id'])) { + $entity->setScheduleId($data['schedule_id']); + } + + return $entity; } - /** - * @throws InvalidCollectionDataProvidedException - */ - public function createCollection(array $data):CollectionInterface + public function createCollection(array $data): TaskCollection { return (new TaskCollection())->createFromArray($data, $this); } -} \ No newline at end of file +} diff --git a/src/Domains/Task/TaskRepository.php b/src/Domains/Task/TaskRepository.php index cd56671..0b87c78 100644 --- a/src/Domains/Task/TaskRepository.php +++ b/src/Domains/Task/TaskRepository.php @@ -3,21 +3,14 @@ namespace Tymeshift\PhpTest\Domains\Task; -use Tymeshift\PhpTest\Interfaces\EntityCollection; -use Tymeshift\PhpTest\Interfaces\EntityInterface; -use Tymeshift\PhpTest\Interfaces\RepositoryInterface; +use Tymeshift\PhpTest\Exceptions\InvalidCollectionDataProvidedException; +use Tymeshift\PhpTest\Exceptions\StorageDataMissingException; -class TaskRepository implements RepositoryInterface +final class TaskRepository implements TaskRepositoryInterface { - /** - * @var TaskFactory - */ - private $factory; + private TaskFactory $factory; - /** - * @var TaskStorage - */ - private $storage; + private TaskStorage $storage; public function __construct(TaskStorage $storage, TaskFactory $factory) { @@ -25,18 +18,35 @@ public function __construct(TaskStorage $storage, TaskFactory $factory) $this->storage = $storage; } - public function getById(int $id): EntityInterface + public function getById(int $id): TaskEntity { - // TODO: Implement getById() method. + $data = $this->storage->getById($id); + + if (empty($data)) { + throw new StorageDataMissingException('Task not found'); + } + + return $this->factory->createEntity($data); } - public function getByScheduleId(int $scheduleId):TaskCollection + public function getByScheduleId(int $scheduleId): TaskCollection { + $data = $this->storage->getByScheduleId($scheduleId); + if (empty($data)) { + throw new StorageDataMissingException('Tasks not found for schedule'); + } + return $this->factory->createCollection($data); } + /** + * @param array $ids + * @throws InvalidCollectionDataProvidedException + */ public function getByIds(array $ids): TaskCollection { - // TODO: Implement getByIds() method. + $data = $this->storage->getByIds($ids); + + return $this->factory->createCollection($data); } -} \ No newline at end of file +} diff --git a/src/Domains/Task/TaskRepositoryInterface.php b/src/Domains/Task/TaskRepositoryInterface.php new file mode 100644 index 0000000..7c5971f --- /dev/null +++ b/src/Domains/Task/TaskRepositoryInterface.php @@ -0,0 +1,14 @@ + + */ +interface TaskRepositoryInterface extends RepositoryInterface +{ + public function getByScheduleId(int $scheduleId): TaskCollection; +} diff --git a/src/Domains/Task/TaskStorage.php b/src/Domains/Task/TaskStorage.php index 9488da7..f6e160a 100644 --- a/src/Domains/Task/TaskStorage.php +++ b/src/Domains/Task/TaskStorage.php @@ -7,20 +7,54 @@ class TaskStorage { - private $client; + private HttpClientInterface $client; public function __construct(HttpClientInterface $httpClient) { $this->client = $httpClient; } + /** + * @param int $id + * @return array + */ public function getByScheduleId(int $id): array { - + return $this->client->request('GET', '/api/tasks/schedule/'.$id); } + /** + * @param array $ids + * @return array + */ public function getByIds(array $ids): array { - // TODO: Implement getByIds() method. + $query = http_build_query(['ids' => $ids]); + + return $this->client->request('GET', '/api/tasks?'.$query); + } + + /** + * @param int $id + * @return array{ + * id?: int, + * start_time?: int, + * duration?: int, + * schedule_id?: int + * } + */ + public function getById(int $id): array + { + return $this->client->request('GET', '/api/tasks/'.$id); } -} \ No newline at end of file +} diff --git a/src/Interfaces/CollectionInterface.php b/src/Interfaces/CollectionInterface.php index d7b2840..7b932ba 100644 --- a/src/Interfaces/CollectionInterface.php +++ b/src/Interfaces/CollectionInterface.php @@ -3,26 +3,30 @@ namespace Tymeshift\PhpTest\Interfaces; +/** + * @template TValue of EntityInterface + * @template TFactory of FactoryInterface + */ interface CollectionInterface { /** * Adds item to collection - * @param EntityInterface $entity + * @param TValue $entity * @return $this */ public function add(EntityInterface $entity):self; /** * Creates Collection from array - * @param array $data - * @param FactoryInterface $factory + * @param array $data + * @param TFactory $factory * @return $this */ public function createFromArray(array $data, FactoryInterface $factory):self; /** * Creates array from collection - * @return array + * @return array */ public function toArray():array; } diff --git a/src/Interfaces/FactoryInterface.php b/src/Interfaces/FactoryInterface.php index 7b48676..5567dad 100644 --- a/src/Interfaces/FactoryInterface.php +++ b/src/Interfaces/FactoryInterface.php @@ -3,7 +3,24 @@ namespace Tymeshift\PhpTest\Interfaces; +use Tymeshift\PhpTest\Exceptions\InvalidCollectionDataProvidedException; + +/** + * @template TData of array + * @template TCollection of CollectionInterface + */ interface FactoryInterface { - public function createEntity(array $data):EntityInterface; -} \ No newline at end of file + /** + * @param TData $data + */ + public function createEntity(array $data): EntityInterface; + + + /** + * @param array $data + * @return TCollection + * @throws InvalidCollectionDataProvidedException + */ + public function createCollection(array $data): CollectionInterface; +} diff --git a/src/Interfaces/RepositoryInterface.php b/src/Interfaces/RepositoryInterface.php index 29f33c3..33722d0 100644 --- a/src/Interfaces/RepositoryInterface.php +++ b/src/Interfaces/RepositoryInterface.php @@ -3,9 +3,25 @@ namespace Tymeshift\PhpTest\Interfaces; +use Tymeshift\PhpTest\Exceptions\InvalidCollectionDataProvidedException; + +/** + * @template TEntity of EntityInterface + * @template TCollection of CollectionInterface + */ interface RepositoryInterface { - public function getById(int $id):EntityInterface; + /** + * @return TEntity + */ + public function getById(int $id): EntityInterface; - public function getByIds(array $ids):CollectionInterface; -} \ No newline at end of file + /** + * @param array $ids + * + * @return TCollection + * + * @throws InvalidCollectionDataProvidedException + */ + public function getByIds(array $ids): CollectionInterface; +} diff --git a/tests/ScheduleCest.php b/tests/ScheduleCest.php index 4776fec..a32366c 100644 --- a/tests/ScheduleCest.php +++ b/tests/ScheduleCest.php @@ -1,37 +1,36 @@ scheduleStorageMock = \Mockery::mock(ScheduleStorage::class); - $this->scheduleRepository = new ScheduleRepository($this->scheduleStorageMock, new ScheduleFactory()); + $this->databaseMock = Mockery::mock(DatabaseInterface::class); + $this->scheduleStorage = new ScheduleStorage($this->databaseMock); + $this->scheduleRepository = new ScheduleRepository($this->scheduleStorage, new ScheduleFactory()); } public function _after() { + $this->databaseMock = null; $this->scheduleRepository = null; - $this->scheduleStorageMock = null; + $this->scheduleStorage = null; \Mockery::close(); } @@ -43,10 +42,10 @@ public function testGetByIdSuccess(Example $example, \UnitTester $tester) ['id' => $id, 'start_time' => $startTime, 'end_time' => $endTime, 'name' => $name] = $example; $data = ['id' => $id, 'start_time' => $startTime, 'end_time' => $endTime, 'name' => $name]; - $this->scheduleStorageMock - ->shouldReceive('getById') - ->with($id) - ->andReturn(['id' => $id, 'start_time' => $startTime, 'end_time' => $endTime, 'name' => $name]); + $this->databaseMock + ->shouldReceive('query') + ->with('SELECT * FROM schedules WHERE id=:id', ['id' => $id]) + ->andReturn($data); $entity = $this->scheduleRepository->getById($id); $tester->assertEquals($id, $entity->getId()); @@ -59,9 +58,9 @@ public function testGetByIdSuccess(Example $example, \UnitTester $tester) */ public function testGetByIdFail(\UnitTester $tester) { - $this->scheduleStorageMock - ->shouldReceive('getById') - ->with(4) + $this->databaseMock + ->shouldReceive('query') + ->with('SELECT * FROM schedules WHERE id=:id', ['id' => 4]) ->andReturn([]); $tester->expectThrowable(StorageDataMissingException::class, function () { $this->scheduleRepository->getById(4); @@ -69,7 +68,12 @@ public function testGetByIdFail(\UnitTester $tester) } /** - * @return array[] + * @return array */ protected function scheduleProvider() { @@ -77,4 +81,4 @@ protected function scheduleProvider() ['id' => 1, 'start_time' => 1631232000, 'end_time' => 1631232000 + 86400, 'name' => 'Test'], ]; } -} \ No newline at end of file +} diff --git a/tests/ScheduleServiceCest.php b/tests/ScheduleServiceCest.php new file mode 100644 index 0000000..02b3e8c --- /dev/null +++ b/tests/ScheduleServiceCest.php @@ -0,0 +1,95 @@ +httpClientMock = Mockery::mock(HttpClientInterface::class); + $this->databaseMock = Mockery::mock(DatabaseInterface::class); + $this->scheduleService = new ScheduleService( + new ScheduleRepository( + new ScheduleStorage( + $this->databaseMock + ), + new ScheduleFactory() + ), + new TaskRepository( + new TaskStorage( + $this->httpClientMock + ), + new TaskFactory() + ), + new TaskToScheduleItemMapper() + ); + } + + public function _after(): void + { + $this->httpClientMock = null; + $this->databaseMock = null; + Mockery::close(); + } + + /** + * @dataProvider tasksDataProvider + */ + public function testFillScheduleItemsSuccess(Example $example, UnitTester $tester): void + { + $this->httpClientMock + ->shouldReceive('request') + ->with('GET', '/api/tasks/schedule/1') + ->andReturn([...$example]); + + $this->databaseMock + ->shouldReceive('query') + ->with('SELECT * FROM schedules WHERE id=:id', ["id" => 1]) + ->andReturn(['id' => 1, 'start_time' => 111111, 'end_time' => 22222 + 33333, 'name' => 'XYZ']); + + $schedule = $this->scheduleService->fillScheduleItems(1); + $items = $schedule->getItems(); + $tester->assertInstanceOf(ScheduleEntity::class, $schedule); + foreach ($items as $item) { + $tester->assertInstanceOf(ScheduleItem::class, $item); + $tester->assertEquals($item->getType(), TaskEntity::class); + } + $tester->assertCount(3, $items); + } + + /** + * @return array> + */ + protected function tasksDataProvider(): array + { + return [ + [ + ["id" => 123, "schedule_id" => 1, "start_time" => 0, "duration" => 3600], + ["id" => 431, "schedule_id" => 1, "start_time" => 3600, "duration" => 650], + ["id" => 332, "schedule_id" => 1, "start_time" => 5600, "duration" => 3600], + ], + ]; + } +} diff --git a/tests/TaskCest.php b/tests/TaskCest.php index d4ae159..7ed1e78 100644 --- a/tests/TaskCest.php +++ b/tests/TaskCest.php @@ -4,60 +4,107 @@ namespace Tests; use Codeception\Example; -use Codeception\Test\Unit; -use Mockery\Mock; use Tymeshift\PhpTest\Components\HttpClientInterface; use Tymeshift\PhpTest\Domains\Task\TaskCollection; +use Tymeshift\PhpTest\Domains\Task\TaskEntity; use Tymeshift\PhpTest\Domains\Task\TaskFactory; use Tymeshift\PhpTest\Domains\Task\TaskRepository; use Tymeshift\PhpTest\Domains\Task\TaskStorage; +use UnitTester; -class TaskCest +final class TaskCest { + private ?TaskRepository $taskRepository; - /** - * @var TaskRepository - */ - private $taskRepository; - + private ?HttpClientInterface $httpClientMock; public function _before() { - $httpClientMock = \Mockery::mock(HttpClientInterface::class); - $storage = new TaskStorage($httpClientMock); + $this->httpClientMock = \Mockery::mock(HttpClientInterface::class); + + $storage = new TaskStorage($this->httpClientMock); $this->taskRepository = new TaskRepository($storage, new TaskFactory()); } public function _after() { $this->taskRepository = null; + $this->httpClientMock = null; \Mockery::close(); } /** * @dataProvider tasksDataProvider */ - public function testGetTasks(Example $example, \UnitTester $tester) + public function testGetTasksByScheduleSuccess(Example $example, UnitTester $tester): void { + $this->httpClientMock + ->shouldReceive('request') + ->with('GET', '/api/tasks/schedule/1') + ->andReturn([...$example]); + $tasks = $this->taskRepository->getByScheduleId(1); $tester->assertInstanceOf(TaskCollection::class, $tasks); } - public function testGetTasksFailed(\UnitTester $tester) + /** + * @dataProvider tasksDataProvider + */ + public function testGetTasks(Example $example, \UnitTester $tester) + { + $ids = [123, 431, 332]; + $this->httpClientMock->shouldReceive('request') + ->with('GET', '/api/tasks?'.http_build_query(['ids' => $ids])) + ->andReturn([...$example]); + + $tasks = $this->taskRepository->getByIds($ids); + $tester->assertInstanceOf(TaskCollection::class, $tasks); + } + + /** + * @dataProvider tasksDataProvider + */ + public function testGetTask(Example $example, UnitTester $tester): void { - $tester->expectThrowable(\Exception::class, function (){ - $this->taskRepository->getByScheduleId(4); + $data = [...$example][0]; + + $this->httpClientMock + ->shouldReceive('request') + ->with('GET', '/api/tasks/1') + ->andReturn($data); + + $task = $this->taskRepository->getById(1); + $tester->assertInstanceOf(TaskEntity::class, $task); + } + + public function testGetTaskFailed(UnitTester $tester): void + { + $this->httpClientMock + ->shouldReceive('request') + ->with('GET', '/api/tasks/4') + ->andReturn([]); + + $tester->expectThrowable(\Exception::class, function () { + $this->taskRepository->getById(4); }); } - public function tasksDataProvider() + /** + * @return array> + */ + public function tasksDataProvider(): array { return [ [ ["id" => 123, "schedule_id" => 1, "start_time" => 0, "duration" => 3600], ["id" => 431, "schedule_id" => 1, "start_time" => 3600, "duration" => 650], ["id" => 332, "schedule_id" => 1, "start_time" => 5600, "duration" => 3600], - ] + ], ]; } -} \ No newline at end of file +}