diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..955d754 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,24 @@ +name: PHPStan tests + +on: + push: + branches: [ 4.x ] + pull_request: + branches: [ 4.x ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Validate composer.json + run: composer validate --strict + + - name: Install dependencies + uses: php-actions/composer@v6 + + - name: Run PHPStan tests + run: bin/test-phpstan diff --git a/Application/ForeignKeyTransformerInterface.php b/Application/ForeignKeyTransformerInterface.php deleted file mode 100755 index 0f3b5c4..0000000 --- a/Application/ForeignKeyTransformerInterface.php +++ /dev/null @@ -1,21 +0,0 @@ - $data */ - public function normalize(string $context, string $role = ''); - - /** - * @return void - */ - public function denormalize(array $data, string $context, string $role = ''); - + public function denormalize(array $data, string $context, string $role = ''): void; /** - * @return array + * @return array|string>|string> */ - public static function getPropertyMap(string $context = '', string $role = null); - + public static function getPropertyMap(string $context = '', string $role = null): array; public function getSensitiveFields(): array; - /** - * @return array + * @return array */ - public function toArray($hideSensitiveData = false); + public function toArray(bool $hideSensitiveData = false): array; } diff --git a/Application/Event/CommandEventInterface.php b/Domain/Event/CommandEventInterface.php old mode 100644 new mode 100755 similarity index 65% rename from Application/Event/CommandEventInterface.php rename to Domain/Event/CommandEventInterface.php index c4ad62e..79b9c8e --- a/Application/Event/CommandEventInterface.php +++ b/Domain/Event/CommandEventInterface.php @@ -1,6 +1,6 @@ + */ + public function transformCollection(array $elements); +} diff --git a/Application/Helper/ArrayObjectHelper.php b/Domain/Helper/ArrayObjectHelper.php old mode 100644 new mode 100755 similarity index 94% rename from Application/Helper/ArrayObjectHelper.php rename to Domain/Helper/ArrayObjectHelper.php index 9f6fbe8..1e3c8b9 --- a/Application/Helper/ArrayObjectHelper.php +++ b/Domain/Helper/ArrayObjectHelper.php @@ -1,6 +1,6 @@ getEntityClass(), + (string) $event->getEntityId(), + $event->getOccurredOn(), + $event->getMicrotime() + ); + + $entity->id = $event->getId(); + $entity->setData( + $event->getData() + ); + + $entity->setCommand($command); + + $entity->sanitizeValues(); + $entity->initChangelog(); + + return $entity; + } + + /** + * @param array $data | null + * @return static + */ + public function replaceData($data = null) + { + return $this->setData($data); + } + + /** + * Get id + * @codeCoverageIgnore + * @return string + */ + public function getId(): ?string + { + return $this->id; + } +} diff --git a/Domain/Model/Changelog/ChangelogAbstract.php b/Domain/Model/Changelog/ChangelogAbstract.php new file mode 100644 index 0000000..8106260 --- /dev/null +++ b/Domain/Model/Changelog/ChangelogAbstract.php @@ -0,0 +1,301 @@ +setEntity($entity); + $this->setEntityId($entityId); + $this->setCreatedOn($createdOn); + $this->setMicrotime($microtime); + } + + abstract public function getId(): null|string|int; + + public function __toString(): string + { + return sprintf( + "%s#%s", + "Changelog", + (string) $this->getId() + ); + } + + /** + * @throws \Exception + */ + protected function sanitizeValues(): void + { + } + + /** + * @param string|null $id + */ + public static function createDto($id = null): ChangelogDto + { + return new ChangelogDto($id); + } + + /** + * @internal use EntityTools instead + * @param null|ChangelogInterface $entity + */ + public static function entityToDto(?EntityInterface $entity, int $depth = 0): ?ChangelogDto + { + if (!$entity) { + return null; + } + + Assertion::isInstanceOf($entity, ChangelogInterface::class); + + if ($depth < 1) { + return static::createDto($entity->getId()); + } + + if ($entity instanceof \Doctrine\ORM\Proxy\Proxy && !$entity->__isInitialized()) { + return static::createDto($entity->getId()); + } + + $dto = $entity->toDto($depth - 1); + + return $dto; + } + + /** + * Factory method + * @internal use EntityTools instead + * @param ChangelogDto $dto + */ + public static function fromDto( + DataTransferObjectInterface $dto, + ForeignKeyTransformerInterface $fkTransformer + ): static { + Assertion::isInstanceOf($dto, ChangelogDto::class); + $entity = $dto->getEntity(); + Assertion::notNull($entity, 'getEntity value is null, but non null value was expected.'); + $entityId = $dto->getEntityId(); + Assertion::notNull($entityId, 'getEntityId value is null, but non null value was expected.'); + $createdOn = $dto->getCreatedOn(); + Assertion::notNull($createdOn, 'getCreatedOn value is null, but non null value was expected.'); + $microtime = $dto->getMicrotime(); + Assertion::notNull($microtime, 'getMicrotime value is null, but non null value was expected.'); + $command = $dto->getCommand(); + Assertion::notNull($command, 'getCommand value is null, but non null value was expected.'); + + $self = new static( + $entity, + $entityId, + $createdOn, + $microtime + ); + + $self + ->setData($dto->getData()) + ->setCommand($fkTransformer->transform($command)); + + $self->initChangelog(); + + return $self; + } + + /** + * @internal use EntityTools instead + * @param ChangelogDto $dto + */ + public function updateFromDto( + DataTransferObjectInterface $dto, + ForeignKeyTransformerInterface $fkTransformer + ): static { + Assertion::isInstanceOf($dto, ChangelogDto::class); + + $entity = $dto->getEntity(); + Assertion::notNull($entity, 'getEntity value is null, but non null value was expected.'); + $entityId = $dto->getEntityId(); + Assertion::notNull($entityId, 'getEntityId value is null, but non null value was expected.'); + $createdOn = $dto->getCreatedOn(); + Assertion::notNull($createdOn, 'getCreatedOn value is null, but non null value was expected.'); + $microtime = $dto->getMicrotime(); + Assertion::notNull($microtime, 'getMicrotime value is null, but non null value was expected.'); + $command = $dto->getCommand(); + Assertion::notNull($command, 'getCommand value is null, but non null value was expected.'); + + $this + ->setEntity($entity) + ->setEntityId($entityId) + ->setData($dto->getData()) + ->setCreatedOn($createdOn) + ->setMicrotime($microtime) + ->setCommand($fkTransformer->transform($command)); + + return $this; + } + + /** + * @internal use EntityTools instead + */ + public function toDto(int $depth = 0): ChangelogDto + { + return self::createDto() + ->setEntity(self::getEntity()) + ->setEntityId(self::getEntityId()) + ->setData(self::getData()) + ->setCreatedOn(self::getCreatedOn()) + ->setMicrotime(self::getMicrotime()) + ->setCommand(Commandlog::entityToDto(self::getCommand(), $depth)); + } + + protected function __toArray(): array + { + return [ + 'entity' => self::getEntity(), + 'entityId' => self::getEntityId(), + 'data' => self::getData(), + 'createdOn' => self::getCreatedOn(), + 'microtime' => self::getMicrotime(), + 'commandId' => self::getCommand()->getId() + ]; + } + + protected function setEntity(string $entity): static + { + Assertion::maxLength($entity, 150, 'entity value "%s" is too long, it should have no more than %d characters, but has %d characters.'); + + $this->entity = $entity; + + return $this; + } + + public function getEntity(): string + { + return $this->entity; + } + + protected function setEntityId(string $entityId): static + { + Assertion::maxLength($entityId, 36, 'entityId value "%s" is too long, it should have no more than %d characters, but has %d characters.'); + + $this->entityId = $entityId; + + return $this; + } + + public function getEntityId(): string + { + return $this->entityId; + } + + protected function setData(?array $data = null): static + { + $this->data = $data; + + return $this; + } + + public function getData(): ?array + { + return $this->data; + } + + protected function setCreatedOn(string|\DateTimeInterface $createdOn): static + { + + /** @var \Datetime */ + $createdOn = DateTimeHelper::createOrFix( + $createdOn, + null + ); + + if ($this->isInitialized() && $this->createdOn == $createdOn) { + return $this; + } + + $this->createdOn = $createdOn; + + return $this; + } + + public function getCreatedOn(): \DateTime + { + return clone $this->createdOn; + } + + protected function setMicrotime(int $microtime): static + { + $this->microtime = $microtime; + + return $this; + } + + public function getMicrotime(): int + { + return $this->microtime; + } + + protected function setCommand(CommandlogInterface $command): static + { + $this->command = $command; + + return $this; + } + + public function getCommand(): CommandlogInterface + { + return $this->command; + } +} diff --git a/Domain/Model/Changelog/ChangelogDto.php b/Domain/Model/Changelog/ChangelogDto.php new file mode 100644 index 0000000..5fa87ab --- /dev/null +++ b/Domain/Model/Changelog/ChangelogDto.php @@ -0,0 +1,8 @@ +setId($id); + } + + /** + * @inheritdoc + */ + public static function getPropertyMap(string $context = '', string $role = null): array + { + if ($context === self::CONTEXT_COLLECTION) { + return ['id' => 'id']; + } + + return [ + 'entity' => 'entity', + 'entityId' => 'entityId', + 'data' => 'data', + 'createdOn' => 'createdOn', + 'microtime' => 'microtime', + 'id' => 'id', + 'commandId' => 'command' + ]; + } + + /** + * @return array + */ + public function toArray(bool $hideSensitiveData = false): array + { + $response = [ + 'entity' => $this->getEntity(), + 'entityId' => $this->getEntityId(), + 'data' => $this->getData(), + 'createdOn' => $this->getCreatedOn(), + 'microtime' => $this->getMicrotime(), + 'id' => $this->getId(), + 'command' => $this->getCommand() + ]; + + if (!$hideSensitiveData) { + return $response; + } + + foreach ($this->sensitiveFields as $sensitiveField) { + if (!array_key_exists($sensitiveField, $response)) { + throw new \Exception($sensitiveField . ' field was not found'); + } + $response[$sensitiveField] = '*****'; + } + + return $response; + } + + public function setEntity(string $entity): static + { + $this->entity = $entity; + + return $this; + } + + public function getEntity(): ?string + { + return $this->entity; + } + + public function setEntityId(string $entityId): static + { + $this->entityId = $entityId; + + return $this; + } + + public function getEntityId(): ?string + { + return $this->entityId; + } + + public function setData(?array $data): static + { + $this->data = $data; + + return $this; + } + + public function getData(): ?array + { + return $this->data; + } + + public function setCreatedOn(\DateTimeInterface|string $createdOn): static + { + $this->createdOn = $createdOn; + + return $this; + } + + public function getCreatedOn(): \DateTimeInterface|string|null + { + return $this->createdOn; + } + + public function setMicrotime(int $microtime): static + { + $this->microtime = $microtime; + + return $this; + } + + public function getMicrotime(): ?int + { + return $this->microtime; + } + + public function setId($id): static + { + $this->id = $id; + + return $this; + } + + public function getId(): ?string + { + return $this->id; + } + + public function setCommand(?CommandlogDto $command): static + { + $this->command = $command; + + return $this; + } + + public function getCommand(): ?CommandlogDto + { + return $this->command; + } + + public function setCommandId($id): static + { + $value = !is_null($id) + ? new CommandlogDto($id) + : null; + + return $this->setCommand($value); + } + + public function getCommandId() + { + if ($dto = $this->getCommand()) { + return $dto->getId(); + } + + return null; + } +} diff --git a/Domain/Model/Changelog/ChangelogInterface.php b/Domain/Model/Changelog/ChangelogInterface.php new file mode 100644 index 0000000..eb6751c --- /dev/null +++ b/Domain/Model/Changelog/ChangelogInterface.php @@ -0,0 +1,71 @@ + $data | null + * @return static + */ + public function replaceData($data = null); + + /** + * Get id + * @codeCoverageIgnore + * @return string + */ + public function getId(): ?string; + + /** + * @param string|null $id + */ + public static function createDto($id = null): ChangelogDto; + + /** + * @internal use EntityTools instead + * @param null|ChangelogInterface $entity + */ + public static function entityToDto(?EntityInterface $entity, int $depth = 0): ?ChangelogDto; + + /** + * Factory method + * @internal use EntityTools instead + * @param ChangelogDto $dto + */ + public static function fromDto(DataTransferObjectInterface $dto, ForeignKeyTransformerInterface $fkTransformer): static; + + /** + * @internal use EntityTools instead + */ + public function toDto(int $depth = 0): ChangelogDto; + + public function getEntity(): string; + + public function getEntityId(): string; + + public function getData(): ?array; + + public function getCreatedOn(): \DateTime; + + public function getMicrotime(): int; + + public function getCommand(): CommandlogInterface; + + public function isInitialized(): bool; +} diff --git a/Domain/Model/Changelog/ChangelogRepository.php b/Domain/Model/Changelog/ChangelogRepository.php new file mode 100644 index 0000000..27992cb --- /dev/null +++ b/Domain/Model/Changelog/ChangelogRepository.php @@ -0,0 +1,16 @@ + + * @template-extends Selectable + */ +interface ChangelogRepository extends ObjectRepository, Selectable +{ + +} diff --git a/Domain/Model/Changelog/ChangelogTrait.php b/Domain/Model/Changelog/ChangelogTrait.php new file mode 100644 index 0000000..4e7367e --- /dev/null +++ b/Domain/Model/Changelog/ChangelogTrait.php @@ -0,0 +1,82 @@ +sanitizeValues(); + if ($dto->getId()) { + $self->id = $dto->getId(); + $self->initChangelog(); + } + + return $self; + } + + /** + * @internal use EntityTools instead + * @param ChangelogDto $dto + */ + public function updateFromDto( + DataTransferObjectInterface $dto, + ForeignKeyTransformerInterface $fkTransformer + ): static { + parent::updateFromDto($dto, $fkTransformer); + + $this->sanitizeValues(); + + return $this; + } + + /** + * @internal use EntityTools instead + */ + public function toDto(int $depth = 0): ChangelogDto + { + $dto = parent::toDto($depth); + return $dto + ->setId($this->getId()); + } + + protected function __toArray(): array + { + return parent::__toArray() + [ + 'id' => self::getId() + ]; + } +} diff --git a/Domain/Model/ChangelogTrait.php b/Domain/Model/ChangelogTrait.php index ba59729..b99f8ad 100644 --- a/Domain/Model/ChangelogTrait.php +++ b/Domain/Model/ChangelogTrait.php @@ -2,7 +2,7 @@ namespace Ivoz\Core\Domain\Model; -use Ivoz\Core\Application\DataTransferObjectInterface; +use Ivoz\Core\Domain\DataTransferObjectInterface; trait ChangelogTrait { @@ -24,43 +24,36 @@ trait ChangelogTrait abstract public function getId(); abstract protected function __toArray(); - abstract public static function createDto($id = null); /** - * @return bool + * @param string|null $id + */ + abstract public static function createDto($id = null): DataTransferObjectInterface; + + /** + * TRUE on new entities until transaction is closed + * always false for ON_COMMIT lifecycle services */ public function isNew(): bool { return !$this->isPersisted(); } - /** - * @return bool - */ public function isInitialized(): bool { return !empty($this->_initialValues); } - /** - * @return bool - */ public function isPersisted(): bool { return $this->isPersisted; } - /** - * @return void - */ - public function markAsPersisted() + public function markAsPersisted(): void { $this->isPersisted = true; } - /** - * @return bool - */ public function hasBeenDeleted(): bool { $id = $this->getId(); @@ -74,11 +67,7 @@ public function hasBeenDeleted(): bool return $hasInitialValue; } - /** - * @return void - * @throws \Exception - */ - public function initChangelog() + public function initChangelog(): void { $values = $this->__toArray(); if (!$this->getId()) { @@ -94,11 +83,9 @@ public function initChangelog() } /** - * @param string $dbFieldName - * @return bool * @throws \Exception */ - public function hasChanged($dbFieldName): bool + public function hasChanged(string $dbFieldName): bool { if (!array_key_exists($dbFieldName, $this->_initialValues)) { throw new \Exception($dbFieldName . ' field was not found'); @@ -109,11 +96,10 @@ public function hasChanged($dbFieldName): bool } /** - * @param string $dbFieldName - * @return mixed + * @return mixed|array * @throws \Exception */ - public function getInitialValue($dbFieldName) + public function getInitialValue(string $dbFieldName): mixed { if (!array_key_exists($dbFieldName, $this->_initialValues)) { throw new \Exception($dbFieldName . ' field was not found'); @@ -123,9 +109,9 @@ public function getInitialValue($dbFieldName) } /** - * @return array + * @return array */ - protected function getChangeSet() + protected function getChangeSet(): array { $changes = []; $currentValues = $this->__toArray(); diff --git a/Domain/Model/Commandlog/Commandlog.php b/Domain/Model/Commandlog/Commandlog.php new file mode 100644 index 0000000..514db74 --- /dev/null +++ b/Domain/Model/Commandlog/Commandlog.php @@ -0,0 +1,51 @@ +id; + } + + /** + * @param \Ivoz\Core\Domain\Event\CommandEventInterface $event + * @return self + */ + public static function fromEvent(CommandEventInterface $event) + { + $entity = new static( + $event->getRequestId(), + $event->getService(), + $event->getOccurredOn(), + $event->getMicrotime() + ); + + $entity->id = $event->getId(); + $entity->setAgent( + $event->getAgent() + ); + $entity->setMethod( + $event->getMethod() + ); + $entity->setArguments( + $event->getArguments() + ); + + $entity->sanitizeValues(); + $entity->initChangelog(); + + return $entity; + } +} diff --git a/Domain/Model/Commandlog/CommandlogAbstract.php b/Domain/Model/Commandlog/CommandlogAbstract.php new file mode 100644 index 0000000..fa8b49e --- /dev/null +++ b/Domain/Model/Commandlog/CommandlogAbstract.php @@ -0,0 +1,318 @@ +setRequestId($requestId); + $this->setClass($class); + $this->setCreatedOn($createdOn); + $this->setMicrotime($microtime); + } + + abstract public function getId(): null|string|int; + + public function __toString(): string + { + return sprintf( + "%s#%s", + "Commandlog", + (string) $this->getId() + ); + } + + /** + * @throws \Exception + */ + protected function sanitizeValues(): void + { + } + + /** + * @param string|null $id + */ + public static function createDto($id = null): CommandlogDto + { + return new CommandlogDto($id); + } + + /** + * @internal use EntityTools instead + * @param null|CommandlogInterface $entity + */ + public static function entityToDto(?EntityInterface $entity, int $depth = 0): ?CommandlogDto + { + if (!$entity) { + return null; + } + + Assertion::isInstanceOf($entity, CommandlogInterface::class); + + if ($depth < 1) { + return static::createDto($entity->getId()); + } + + if ($entity instanceof \Doctrine\ORM\Proxy\Proxy && !$entity->__isInitialized()) { + return static::createDto($entity->getId()); + } + + $dto = $entity->toDto($depth - 1); + + return $dto; + } + + /** + * Factory method + * @internal use EntityTools instead + * @param CommandlogDto $dto + */ + public static function fromDto( + DataTransferObjectInterface $dto, + ForeignKeyTransformerInterface $fkTransformer + ): static { + Assertion::isInstanceOf($dto, CommandlogDto::class); + $requestId = $dto->getRequestId(); + Assertion::notNull($requestId, 'getRequestId value is null, but non null value was expected.'); + $class = $dto->getClass(); + Assertion::notNull($class, 'getClass value is null, but non null value was expected.'); + $createdOn = $dto->getCreatedOn(); + Assertion::notNull($createdOn, 'getCreatedOn value is null, but non null value was expected.'); + $microtime = $dto->getMicrotime(); + Assertion::notNull($microtime, 'getMicrotime value is null, but non null value was expected.'); + + $self = new static( + $requestId, + $class, + $createdOn, + $microtime + ); + + $self + ->setMethod($dto->getMethod()) + ->setArguments($dto->getArguments()) + ->setAgent($dto->getAgent()); + + $self->initChangelog(); + + return $self; + } + + /** + * @internal use EntityTools instead + * @param CommandlogDto $dto + */ + public function updateFromDto( + DataTransferObjectInterface $dto, + ForeignKeyTransformerInterface $fkTransformer + ): static { + Assertion::isInstanceOf($dto, CommandlogDto::class); + + $requestId = $dto->getRequestId(); + Assertion::notNull($requestId, 'getRequestId value is null, but non null value was expected.'); + $class = $dto->getClass(); + Assertion::notNull($class, 'getClass value is null, but non null value was expected.'); + $createdOn = $dto->getCreatedOn(); + Assertion::notNull($createdOn, 'getCreatedOn value is null, but non null value was expected.'); + $microtime = $dto->getMicrotime(); + Assertion::notNull($microtime, 'getMicrotime value is null, but non null value was expected.'); + + $this + ->setRequestId($requestId) + ->setClass($class) + ->setMethod($dto->getMethod()) + ->setArguments($dto->getArguments()) + ->setAgent($dto->getAgent()) + ->setCreatedOn($createdOn) + ->setMicrotime($microtime); + + return $this; + } + + /** + * @internal use EntityTools instead + */ + public function toDto(int $depth = 0): CommandlogDto + { + return self::createDto() + ->setRequestId(self::getRequestId()) + ->setClass(self::getClass()) + ->setMethod(self::getMethod()) + ->setArguments(self::getArguments()) + ->setAgent(self::getAgent()) + ->setCreatedOn(self::getCreatedOn()) + ->setMicrotime(self::getMicrotime()); + } + + protected function __toArray(): array + { + return [ + 'requestId' => self::getRequestId(), + 'class' => self::getClass(), + 'method' => self::getMethod(), + 'arguments' => self::getArguments(), + 'agent' => self::getAgent(), + 'createdOn' => self::getCreatedOn(), + 'microtime' => self::getMicrotime() + ]; + } + + protected function setRequestId(string $requestId): static + { + $this->requestId = $requestId; + + return $this; + } + + public function getRequestId(): string + { + return $this->requestId; + } + + protected function setClass(string $class): static + { + Assertion::maxLength($class, 50, 'class value "%s" is too long, it should have no more than %d characters, but has %d characters.'); + + $this->class = $class; + + return $this; + } + + public function getClass(): string + { + return $this->class; + } + + protected function setMethod(?string $method = null): static + { + if (!is_null($method)) { + Assertion::maxLength($method, 64, 'method value "%s" is too long, it should have no more than %d characters, but has %d characters.'); + } + + $this->method = $method; + + return $this; + } + + public function getMethod(): ?string + { + return $this->method; + } + + protected function setArguments(?array $arguments = null): static + { + $this->arguments = $arguments; + + return $this; + } + + public function getArguments(): ?array + { + return $this->arguments; + } + + protected function setAgent(?array $agent = null): static + { + $this->agent = $agent; + + return $this; + } + + public function getAgent(): ?array + { + return $this->agent; + } + + protected function setCreatedOn(string|\DateTimeInterface $createdOn): static + { + + /** @var \Datetime */ + $createdOn = DateTimeHelper::createOrFix( + $createdOn, + null + ); + + if ($this->isInitialized() && $this->createdOn == $createdOn) { + return $this; + } + + $this->createdOn = $createdOn; + + return $this; + } + + public function getCreatedOn(): \DateTime + { + return clone $this->createdOn; + } + + protected function setMicrotime(int $microtime): static + { + $this->microtime = $microtime; + + return $this; + } + + public function getMicrotime(): int + { + return $this->microtime; + } +} diff --git a/Domain/Model/Commandlog/CommandlogDto.php b/Domain/Model/Commandlog/CommandlogDto.php new file mode 100644 index 0000000..9ad8e71 --- /dev/null +++ b/Domain/Model/Commandlog/CommandlogDto.php @@ -0,0 +1,8 @@ +setId($id); + } + + /** + * @inheritdoc + */ + public static function getPropertyMap(string $context = '', string $role = null): array + { + if ($context === self::CONTEXT_COLLECTION) { + return ['id' => 'id']; + } + + return [ + 'requestId' => 'requestId', + 'class' => 'class', + 'method' => 'method', + 'arguments' => 'arguments', + 'agent' => 'agent', + 'createdOn' => 'createdOn', + 'microtime' => 'microtime', + 'id' => 'id' + ]; + } + + /** + * @return array + */ + public function toArray(bool $hideSensitiveData = false): array + { + $response = [ + 'requestId' => $this->getRequestId(), + 'class' => $this->getClass(), + 'method' => $this->getMethod(), + 'arguments' => $this->getArguments(), + 'agent' => $this->getAgent(), + 'createdOn' => $this->getCreatedOn(), + 'microtime' => $this->getMicrotime(), + 'id' => $this->getId() + ]; + + if (!$hideSensitiveData) { + return $response; + } + + foreach ($this->sensitiveFields as $sensitiveField) { + if (!array_key_exists($sensitiveField, $response)) { + throw new \Exception($sensitiveField . ' field was not found'); + } + $response[$sensitiveField] = '*****'; + } + + return $response; + } + + public function setRequestId(string $requestId): static + { + $this->requestId = $requestId; + + return $this; + } + + public function getRequestId(): ?string + { + return $this->requestId; + } + + public function setClass(string $class): static + { + $this->class = $class; + + return $this; + } + + public function getClass(): ?string + { + return $this->class; + } + + public function setMethod(?string $method): static + { + $this->method = $method; + + return $this; + } + + public function getMethod(): ?string + { + return $this->method; + } + + public function setArguments(?array $arguments): static + { + $this->arguments = $arguments; + + return $this; + } + + public function getArguments(): ?array + { + return $this->arguments; + } + + public function setAgent(?array $agent): static + { + $this->agent = $agent; + + return $this; + } + + public function getAgent(): ?array + { + return $this->agent; + } + + public function setCreatedOn(\DateTimeInterface|string $createdOn): static + { + $this->createdOn = $createdOn; + + return $this; + } + + public function getCreatedOn(): \DateTimeInterface|string|null + { + return $this->createdOn; + } + + public function setMicrotime(int $microtime): static + { + $this->microtime = $microtime; + + return $this; + } + + public function getMicrotime(): ?int + { + return $this->microtime; + } + + public function setId($id): static + { + $this->id = $id; + + return $this; + } + + public function getId(): ?string + { + return $this->id; + } +} diff --git a/Domain/Model/Commandlog/CommandlogInterface.php b/Domain/Model/Commandlog/CommandlogInterface.php new file mode 100644 index 0000000..33f8d0a --- /dev/null +++ b/Domain/Model/Commandlog/CommandlogInterface.php @@ -0,0 +1,66 @@ + + * @template-extends Selectable + */ +interface CommandlogRepository extends ObjectRepository, Selectable +{ + +} diff --git a/Domain/Model/Commandlog/CommandlogTrait.php b/Domain/Model/Commandlog/CommandlogTrait.php new file mode 100644 index 0000000..b11ecb0 --- /dev/null +++ b/Domain/Model/Commandlog/CommandlogTrait.php @@ -0,0 +1,82 @@ +sanitizeValues(); + if ($dto->getId()) { + $self->id = $dto->getId(); + $self->initChangelog(); + } + + return $self; + } + + /** + * @internal use EntityTools instead + * @param CommandlogDto $dto + */ + public function updateFromDto( + DataTransferObjectInterface $dto, + ForeignKeyTransformerInterface $fkTransformer + ): static { + parent::updateFromDto($dto, $fkTransformer); + + $this->sanitizeValues(); + + return $this; + } + + /** + * @internal use EntityTools instead + */ + public function toDto(int $depth = 0): CommandlogDto + { + $dto = parent::toDto($depth); + return $dto + ->setId($this->getId()); + } + + protected function __toArray(): array + { + return parent::__toArray() + [ + 'id' => self::getId() + ]; + } +} diff --git a/Application/Model/DtoNormalizer.php b/Domain/Model/DtoNormalizer.php old mode 100644 new mode 100755 similarity index 65% rename from Application/Model/DtoNormalizer.php rename to Domain/Model/DtoNormalizer.php index bccafb7..caff3db --- a/Application/Model/DtoNormalizer.php +++ b/Domain/Model/DtoNormalizer.php @@ -1,6 +1,6 @@ toArray(true); $contextProperties = static::getPropertyMap($context, $role); $response = array_filter( $response, - function ($value, $key) use ($contextProperties) { - - $isEmbedded = is_array($value) || is_object($value); - + function ($key) use ($contextProperties) { return in_array($key, $contextProperties, true) - || (!$isEmbedded && in_array($value, $contextProperties, true)) - || ($isEmbedded && array_key_exists($key, $contextProperties)); + || array_key_exists($key, $contextProperties); }, - ARRAY_FILTER_USE_BOTH + ARRAY_FILTER_USE_KEY + ); + + foreach ($response as $key => $val) { + + $isEmbedded = is_array($val); + if (!$isEmbedded) { + continue; + } + + if (!isset($contextProperties[$key])) { + continue; + } + + $validSubKeys = $contextProperties[$key]; + if (!is_array($validSubKeys)) { + throw new \RuntimeException($key . ' context properties were expected to be array'); + } + + $response[$key] = $this->filterResponseSubProperties( + $val, + $validSubKeys + ); + } + + return $response; + } + + private function filterResponseSubProperties(array $values, array $validSubKeys): array + { + if (empty($validSubKeys)) { + return []; + } + + if (is_array($validSubKeys[0])) { + $response = []; + foreach($values as $k => $value) { + $response[$k] = $this->filterResponseSubProperties( + $value, + $validSubKeys[0] + ); + } + + return $response; + } + + return array_filter( + $values, + function ($key) use ($validSubKeys) { + return in_array($key, $validSubKeys); + }, + ARRAY_FILTER_USE_KEY ); foreach ($response as $key => $val) { @@ -99,7 +146,7 @@ function ($key) use ($validSubKeys) { /** * @inheritdoc */ - public function denormalize(array $data, string $context, string $role = '') + public function denormalize(array $data, string $context, string $role = ''): void { $contextProperties = static::getPropertyMap($context, $role); @@ -117,7 +164,12 @@ protected function setByContext(array $contextProperties, array $data) foreach ($contextProperties as $key => $value) { if (is_array($value)) { foreach ($value as $property) { - if (!isset($data[$key]) || !array_key_exists($property, $data[$key])) { + if ( + !isset($data[$key]) + || !is_string($property) + || !is_array($data[$key]) + || !array_key_exists($property, $data[$key]) + ) { continue; } diff --git a/Domain/Model/EntityInterface.php b/Domain/Model/EntityInterface.php index a1cfa50..3466c60 100755 --- a/Domain/Model/EntityInterface.php +++ b/Domain/Model/EntityInterface.php @@ -2,8 +2,8 @@ namespace Ivoz\Core\Domain\Model; -use Ivoz\Core\Application\DataTransferObjectInterface; -use Ivoz\Core\Application\ForeignKeyTransformerInterface; +use Ivoz\Core\Domain\DataTransferObjectInterface; +use Ivoz\Core\Domain\ForeignKeyTransformerInterface; /** * Entity interface @@ -13,100 +13,65 @@ interface EntityInterface { /** - * @return mixed + * @return string|int|null */ public function getId(); - /** - * @return bool - */ - public function isNew(); + public function isNew(): bool; - /** - * @return bool - */ - public function isPersisted(); + public function isPersisted(): bool; - /** - * @return void - */ - public function markAsPersisted(); + public function markAsPersisted(): void; - /** - * @return bool - */ - public function hasBeenDeleted(); + public function hasBeenDeleted(): bool; - /** - * @return string - */ - public function __toString(); + public function __toString(): string; - /** - * @return void - * @throws \Exception - */ - public function initChangelog(); + public function isInitialized(): bool; + + public function initChangelog(): void; /** - * @param string $fieldName - * @return boolean * @throws \Exception */ - public function hasChanged($fieldName); + public function hasChanged(string $fieldName): bool; /** * @return string[] */ - public function getChangedFields(); + public function getChangedFields(): array; /** - * @param string $fieldName - * @return mixed * @throws \Exception */ - public function getInitialValue($fieldName); + public function getInitialValue(string $fieldName): mixed; - /** - * @param mixed|null $id - * @return EntityInterface - */ - public static function createDto($id = null); + public static function createDto(int|string|null $id = null): DataTransferObjectInterface; /** - * @param int $depth - * @param EntityInterface|null $entity - * @return DataTransferObjectInterface|null * @todo move this into dto::fromEntity */ - public static function entityToDto(EntityInterface $entity = null, $depth = 0); + public static function entityToDto(?EntityInterface $entity, int $depth = 0): ?DataTransferObjectInterface; /** * Factory method - * @param DataTransferObjectInterface $dto - * @param \Ivoz\Core\Application\ForeignKeyTransformerInterface $fkTransformer */ public static function fromDto( DataTransferObjectInterface $dto, ForeignKeyTransformerInterface $fkTransformer - ); + ): EntityInterface; /** * @internal use EntityTools instead - * @param DataTransferObjectInterface $dto - * @param \Ivoz\Core\Application\ForeignKeyTransformerInterface $fkTransformer - * @return self */ public function updateFromDto( DataTransferObjectInterface $dto, ForeignKeyTransformerInterface $fkTransformer - ); + ): static; /** * DTO casting - * @param int $depth - * @return DataTransferObjectInterface * @todo move this into dto::fromEntity */ - public function toDto($depth = 0); + public function toDto(int $depth = 0): DataTransferObjectInterface; } diff --git a/Domain/Model/Helper/DateTimeHelper.php b/Domain/Model/Helper/DateTimeHelper.php index 3ec28c5..bc7b054 100755 --- a/Domain/Model/Helper/DateTimeHelper.php +++ b/Domain/Model/Helper/DateTimeHelper.php @@ -58,7 +58,7 @@ public static function createOrFix($value = null, $defaultValue = null) return self::createFromString($value); } - throw new \Exception('Unkown format'); + throw new \Exception('Unknown format'); } /** @@ -195,7 +195,7 @@ protected static function createFromString(string $value) protected static function getCurrentUtcDateTime(): \DateTime { return new \DateTime( - null, + 'now', new \DateTimeZone('UTC') ); } diff --git a/Domain/Model/Mailer/Attachment.php b/Domain/Model/Mailer/Attachment.php new file mode 100644 index 0000000..d303b64 --- /dev/null +++ b/Domain/Model/Mailer/Attachment.php @@ -0,0 +1,55 @@ +type = $type; + } + + public function getType(): string + { + return $this->type; + } + + public function getFile(): string + { + return $this->filePath; + } + + public function getFilename(): ?string + { + return $this->filename; + } + + public function getMimetype(): ?string + { + return $this->mimetype; + } + + +} diff --git a/Domain/Model/Mailer/Message.php b/Domain/Model/Mailer/Message.php index ce13820..6061814 100644 --- a/Domain/Model/Mailer/Message.php +++ b/Domain/Model/Mailer/Message.php @@ -2,6 +2,9 @@ namespace Ivoz\Core\Domain\Model\Mailer; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Address; + class Message { /** @@ -34,48 +37,71 @@ class Message */ protected $toAddress; - protected $attachment; + /** + * @var Attachment[] + */ + protected $attachments = []; /** - * @return \Swift_Message + * @internal */ - public function toSwiftMessage() + public function toEmail(): Email { - $message = new \Swift_Message(); - $message - ->setBody($this->getBody(), $this->getBodyType()) - ->setSubject($this->getSubject()) - ->setFrom($this->getFromAddress(), $this->getFromName()) - ->setTo($this->getToAddress()); + $message = new Email(); + + if ($this->getBodyType() === 'text/plain') { + $message + ->text($this->getBody()); + } else { + $message + ->html($this->getBody()); + } - if ($this->attachment) { - $message->attach($this->attachment); + $message + ->subject($this->getSubject()) + ->from( + new Address( + $this->getFromAddress(), + $this->getFromName() + ) + ) + ->to($this->getToAddress()); + + foreach ($this->attachments as $attachment) { + + if ($attachment->getType() === Attachment::TYPE_FILEPATH) { + $message->attachFromPath( + $attachment->getFile(), + $attachment->getFilename(), + $attachment->getMimetype(), + ); + } else { + + $stream = fopen('php://memory', 'rw+'); + fwrite($stream, $attachment->getFile()); + rewind($stream); + + $message->attach( + $stream, + $attachment->getFilename(), + $attachment->getMimetype(), + ); + } } return $message; } - /** - * @return string - */ public function getBody(): string { return $this->body; } - /** - * @return string - */ public function getBodyType(): string { return $this->bodyType; } - /** - * @param string $body - * @param string $bodyType - * @return Message - */ public function setBody(string $body, string $bodyType = 'text/plain'): Message { $this->body = $body; @@ -83,86 +109,72 @@ public function setBody(string $body, string $bodyType = 'text/plain'): Message return $this; } - /** - * @return string - */ public function getSubject(): string { return $this->subject; } - /** - * @param string $subject - * @return Message - */ public function setSubject(string $subject): Message { $this->subject = $subject; return $this; } - /** - * @return string - */ public function getFromAddress(): string { return $this->fromAddress; } - /** - * @param string $fromAddress - * @return Message - */ public function setFromAddress(string $fromAddress): Message { $this->fromAddress = $fromAddress; return $this; } - /** - * @return string - */ public function getFromName(): string { return $this->fromName; } - /** - * @param string $fromName - * @return Message - */ public function setFromName(string $fromName): Message { $this->fromName = $fromName; return $this; } - /** - * @return string - */ public function getToAddress(): string { return $this->toAddress; } - /** - * @param string $toAddress - * @return Message - */ public function setToAddress(string $toAddress): Message { $this->toAddress = $toAddress; return $this; } - /** - * @return void - */ - public function setAttachment($file, $filename, $mimetype) + public function setAttachment($file, $filename, $mimetype, $type = Attachment::TYPE_FILEPATH): Message { - $this->attachment = \Swift_Attachment::fromPath($file, $mimetype); - $this->attachment->setFilename( - $filename + $this->attachments = []; + $this->addAttachment( + $file, + $filename, + $mimetype, + $type ); + + return $this; + } + + public function addAttachment($file, $filename, $mimetype, $type = Attachment::TYPE_FILEPATH): Message + { + $this->attachments[] = new Attachment( + $file, + $filename, + $mimetype, + $type + ); + + return $this; } } diff --git a/Domain/Model/SelfManagedInterface.php b/Domain/Model/SelfManagedInterface.php new file mode 100755 index 0000000..f79a927 --- /dev/null +++ b/Domain/Model/SelfManagedInterface.php @@ -0,0 +1,10 @@ +tmpFiles[$fldName] = $file; } /** - * @param \Ivoz\Core\Domain\Service\TempFile $file - * * @throws \Exception - * * @return void */ public function removeTmpFile(TempFile $file) @@ -48,7 +42,7 @@ public function getTempFiles() } /** - * @var string $fldName + * @param string $fldName * @return null | \Ivoz\Core\Domain\Service\TempFile */ public function getTempFileByFieldName($fldName) diff --git a/Application/MutexInterface.php b/Domain/MutexInterface.php similarity index 87% rename from Application/MutexInterface.php rename to Domain/MutexInterface.php index 561c77f..fd1e956 100755 --- a/Application/MutexInterface.php +++ b/Domain/MutexInterface.php @@ -1,6 +1,6 @@ services[$event][] = $service; } diff --git a/Application/Service/CommonStoragePathResolver.php b/Domain/Service/CommonStoragePathResolver.php old mode 100644 new mode 100755 similarity index 98% rename from Application/Service/CommonStoragePathResolver.php rename to Domain/Service/CommonStoragePathResolver.php index 08fce56..1a23871 --- a/Application/Service/CommonStoragePathResolver.php +++ b/Domain/Service/CommonStoragePathResolver.php @@ -1,6 +1,6 @@ em = $entityManager; } diff --git a/Domain/Service/Repository/RepositoryInterface.php b/Domain/Service/Repository/RepositoryInterface.php new file mode 100644 index 0000000..e30bf8f --- /dev/null +++ b/Domain/Service/Repository/RepositoryInterface.php @@ -0,0 +1,41 @@ + + * @extends Selectable + */ +interface RepositoryInterface extends ObjectRepository, Selectable +{ + /** + * @param DtoT $dto + * @param EntityT|null $entity + * @return EntityT + */ + public function persistDto(DataTransferObjectInterface $dto, EntityInterface $entity = null, $dispatchImmediately = false): EntityInterface; + + /** + * @param EntityT $entity + */ + public function persist(EntityInterface $entity, bool $dispatchImmediately = false): void; + + /** + * @param EntityT $entity + */ + public function remove(EntityInterface $entity): void; + + /** + * @param EntityT[] $entities + */ + public function removeFromArray(array $entities): void; + + public function dispatchQueued(): void; +} \ No newline at end of file diff --git a/Application/Service/StoragePathResolverCollection.php b/Domain/Service/StoragePathResolverCollection.php old mode 100644 new mode 100755 similarity index 95% rename from Application/Service/StoragePathResolverCollection.php rename to Domain/Service/StoragePathResolverCollection.php index ecdd4c4..fb5758d --- a/Application/Service/StoragePathResolverCollection.php +++ b/Domain/Service/StoragePathResolverCollection.php @@ -1,6 +1,6 @@ em = $em; + public function __construct( + private EntityManagerInterface $em + ) { } /** * @param mixed $element * @param bool $persist + * + * @return ?EntityInterface */ public function transform($element, $persist = true) { @@ -66,15 +63,11 @@ public function transform($element, $persist = true) } /** - * @param array | null $elements - * @return ArrayCollection | null + * @param array $elements + * @return ArrayCollection */ - public function transformCollection(array $elements = null) + public function transformCollection(array $elements) { - if (is_null($elements)) { - return null; - } - if (empty($elements)) { return new ArrayCollection(); } diff --git a/Infrastructure/Domain/Service/Cgrates/FakeCgrRpcClient.php b/Infrastructure/Domain/Service/Cgrates/FakeCgrRpcClient.php index 1d6954e..2b63d22 100644 --- a/Infrastructure/Domain/Service/Cgrates/FakeCgrRpcClient.php +++ b/Infrastructure/Domain/Service/Cgrates/FakeCgrRpcClient.php @@ -6,10 +6,10 @@ use Graze\GuzzleHttp\JsonRpc\Message\Request; use Graze\GuzzleHttp\JsonRpc\Message\RequestInterface; use Graze\GuzzleHttp\JsonRpc\Message\Response; +use GuzzleHttp\Promise\Promise; class FakeCgrRpcClient implements ClientInterface { - private $fixedResponse = '{"error": null}'; public function __construct( @@ -20,6 +20,7 @@ public function __construct( public function notification($method, array $params = null) { + return $this->createRequest(); } public function request($id, $method, array $params = null) @@ -28,7 +29,7 @@ public function request($id, $method, array $params = null) 'POST', '/uri', [], - '[]' + $this->fixedResponse ); } @@ -43,13 +44,32 @@ public function send(RequestInterface $request) public function sendAsync(RequestInterface $request) { + return $this->createPromise(); } public function sendAll(array $requests) { + return [$this->createRequest()]; } public function sendAllAsync(array $requests) { + return $this->createPromise(); + } + + + private function createRequest() + { + return new Request( + 'POST', + '/uri', + [], + '[]' + ); + } + + private function createPromise() + { + return new Promise(); } } diff --git a/Infrastructure/Domain/Service/DoctrineEntityPersister.php b/Infrastructure/Domain/Service/DoctrineEntityPersister.php index b839206..8e79541 100755 --- a/Infrastructure/Domain/Service/DoctrineEntityPersister.php +++ b/Infrastructure/Domain/Service/DoctrineEntityPersister.php @@ -5,10 +5,10 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\UnitOfWork; -use Ivoz\Core\Application\DataTransferObjectInterface; -use Ivoz\Core\Application\Helper\EntityClassHelper; -use Ivoz\Core\Application\Service\CreateEntityFromDto; -use Ivoz\Core\Application\Service\UpdateEntityFromDto; +use Ivoz\Core\Domain\DataTransferObjectInterface; +use Ivoz\Core\Domain\Helper\EntityClassHelper; +use Ivoz\Core\Domain\Service\CreateEntityFromDto; +use Ivoz\Core\Domain\Service\UpdateEntityFromDto; use Ivoz\Core\Domain\Model\EntityInterface; use Ivoz\Core\Domain\Service\EntityPersisterInterface; use Ivoz\Core\Infrastructure\Persistence\Doctrine\Events as CustomEvents; @@ -161,6 +161,7 @@ public function persist(EntityInterface $entity, $dispatchImmediately = false) } if (in_array($state, $singleComputationValidStates)) { + /** @phpstan-ignore-next-line */ $this->em->flush($entity); return; } @@ -330,7 +331,7 @@ private function getDependantEntities(EntityInterface $entity): array foreach ($associations as $field => $association) { $isDeleteCascade = isset($association['joinColumns']) - && $association['joinColumns'][0]['onDelete'] === 'cascade'; + && strtolower($association['joinColumns'][0]['onDelete']) === 'cascade'; if (!$isDeleteCascade) { continue; diff --git a/Infrastructure/Domain/Service/DoctrineQueryRunner.php b/Infrastructure/Domain/Service/DoctrineQueryRunner.php index ed490c6..226acaa 100755 --- a/Infrastructure/Domain/Service/DoctrineQueryRunner.php +++ b/Infrastructure/Domain/Service/DoctrineQueryRunner.php @@ -37,11 +37,7 @@ public function __construct( } /** - * @param string $entityName - * @param AbstractQuery $query * @return int affected rows - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Doctrine\DBAL\DBALException */ public function execute(string $entityName, AbstractQuery $query) { @@ -62,7 +58,11 @@ public function execute(string $entityName, AbstractQuery $query) } $retries = self::DEADLOCK_RETRIES; - while (0 < $retries--) { + /** @phpstan-ignore-next-line */ + while (0 < $retries) { + + $retries -= 1; + $this ->em ->getConnection() @@ -117,10 +117,7 @@ public function execute(string $entityName, AbstractQuery $query) } /** - * @param AbstractQuery $query - * @param EntityEventInterface $event * @return int $affectedRows - * @throws \Doctrine\DBAL\DBALException */ private function runQueryAndReturnAffectedRows(AbstractQuery $query, EntityEventInterface $event) { @@ -170,6 +167,7 @@ private function prepareChangelogEvent(string $entityName, AbstractQuery $query) /** @var \Closure $dqlParamResolver */ $dqlParamResolver = function () use (&$sqlParams, &$types) { + /** @var AbstractQuery $this */ assert( $this instanceof DqlQuery, new \Exception('dqlParamResolver context must be instance of ' . DqlQuery::class) @@ -177,6 +175,7 @@ private function prepareChangelogEvent(string $entityName, AbstractQuery $query) $parser = new Parser($this); $paramMappings = $parser->parse()->getParameterMappings(); + /** @phpstan-ignore-next-line */ list($params, $paramTypes) = $this->processParameterMappings($paramMappings); $sqlParams = $params; diff --git a/Infrastructure/Domain/Service/Gearman/Client/AbstractXmlRpcRequest.php b/Infrastructure/Domain/Service/Gearman/Client/AbstractXmlRpcRequest.php deleted file mode 100755 index c350888..0000000 --- a/Infrastructure/Domain/Service/Gearman/Client/AbstractXmlRpcRequest.php +++ /dev/null @@ -1,48 +0,0 @@ -xmlrpc = $xmlrpc; - $this->xmlrpc->setRpcEntity($rpcEntity); - $this->xmlrpc->setRpcPort($rpcPort); - - $this->enabled = $enabled; - } - - /** - * @return void - */ - public function send(string $rpcMethod) - { - if (!$this->enabled) { - return; - } - - $this->xmlrpc->setRpcMethod($rpcMethod); - $this->xmlrpc->send(); - } -} diff --git a/Infrastructure/Domain/Service/Gearman/Client/FakeXmlRpcTrunksRequest.php b/Infrastructure/Domain/Service/Gearman/Client/FakeXmlRpcTrunksRequest.php deleted file mode 100644 index 9ec1b92..0000000 --- a/Infrastructure/Domain/Service/Gearman/Client/FakeXmlRpcTrunksRequest.php +++ /dev/null @@ -1,10 +0,0 @@ -manager = $manager; - $this->logger = $logger; - $this->settings = $settings; - } - - /** - * @param string $methodName - * - * @return void - */ - public function setMethod($methodName) - { - $this->method = $methodName; - } - - /** - * @return array - */ - public function __sleep() - { - return $this->mainVariables; - } - - /** - * Send Gearman Job request to server - * - * @return void - */ - public function send() - { - $this->manager::setOptions($this->settings); - - try { - $gearmandClient = $this->manager::getClient(); - $gearmandClient->doBackground( - $this->method, - igbinary_serialize($this) - ); - - if ($gearmandClient->returnCode() != GEARMAN_SUCCESS) { - throw new \Exception('Gearmand return code error'); - } - } catch (\DomainException $e) { - $this->logger->error($e->getMessage()); - - throw new \Exception( - 'Gearmand: Unable to add background job to the queue', - 0, - $e - ); - } - } -} diff --git a/Infrastructure/Domain/Service/Gearman/Jobs/Xmlrpc.php b/Infrastructure/Domain/Service/Gearman/Jobs/Xmlrpc.php deleted file mode 100644 index 8b29f2b..0000000 --- a/Infrastructure/Domain/Service/Gearman/Jobs/Xmlrpc.php +++ /dev/null @@ -1,105 +0,0 @@ -method = $method; - parent::__construct($manager, $logger, $settings); - } - - /** - * @param string $rpcEntity - * @return $this - */ - public function setRpcEntity($rpcEntity) - { - $this->rpcEntity = $rpcEntity; - return $this; - } - - /** - * @return string - */ - public function getRpcEntity() - { - return $this->rpcEntity; - } - - /** - * @param string $rpcPort - * @return $this - */ - public function setRpcPort($rpcPort) - { - $this->rpcPort = $rpcPort; - return $this; - } - - /** - * @return string - */ - public function getRpcPort() - { - return $this->rpcPort; - } - - /** - * @param string $rpcMethod - * @return $this - */ - public function setRpcMethod($rpcMethod) - { - $this->rpcMethod = $rpcMethod; - return $this; - } - - /** - * @return string - */ - public function getRpcMethod() - { - return $this->rpcMethod; - } -} diff --git a/Infrastructure/Domain/Service/Gearman/Manager.php b/Infrastructure/Domain/Service/Gearman/Manager.php deleted file mode 100644 index 12493a1..0000000 --- a/Infrastructure/Domain/Service/Gearman/Manager.php +++ /dev/null @@ -1,96 +0,0 @@ -addServers($servers); - if (isset(self::$_options['client']) && - isset(self::$_options['client']['timeout'])) { - $gmclient->setTimeout(self::$_options['client']['timeout']); - } - - return $gmclient; - } - - /** - * Creates a GearmanWorker instance - * - * @return \GearmanWorker - */ - public static function getWorker() - { - $worker = new \GearmanWorker(); - $servers = self::getServers(); - - $worker->addServers($servers); - - return $worker; - } - - /** - * Given a worker name, it checks if it can be loaded. If it's possible, - * it creates and returns a new instance. - * - * @param string $workerName - * @param string $logFile - * @return \GearmanWorker - */ - public static function runWorker($workerName, $logFile = null) - { - $workerName .= 'Worker'; - $workerFile = self::WORKER_PATH . '/' . $workerName . '.php'; - - if (!file_exists($workerFile)) { - throw new \InvalidArgumentException( - "Worker not found: {$workerFile}" - ); - } - - require $workerFile; - - if (!class_exists($workerName)) { - throw new \InvalidArgumentException( - "class {$workerName} not found in {$workerFile}" - ); - } - - return new $workerName($logFile); - } -} diff --git a/Infrastructure/Domain/Service/Lifecycle/CommandPersister.php b/Infrastructure/Domain/Service/Lifecycle/CommandPersister.php index 4c3f61d..f0e0d1a 100755 --- a/Infrastructure/Domain/Service/Lifecycle/CommandPersister.php +++ b/Infrastructure/Domain/Service/Lifecycle/CommandPersister.php @@ -2,33 +2,25 @@ namespace Ivoz\Core\Infrastructure\Domain\Service\Lifecycle; -use Ivoz\Core\Application\Event\CommandWasExecuted; -use Ivoz\Core\Application\Service\CommandEventSubscriber; +use Ivoz\Core\Domain\Event\CommandWasExecuted; +use Ivoz\Core\Domain\Service\CommandEventSubscriber; +use Ivoz\Core\Domain\Event\EntityEventInterface; use Ivoz\Core\Domain\Service\EntityEventSubscriber; use Ivoz\Core\Domain\Service\EntityPersisterInterface; -use Ivoz\Provider\Domain\Model\Changelog\Changelog; -use Ivoz\Provider\Domain\Model\Commandlog\Commandlog; +use Ivoz\Core\Domain\Model\Changelog\Changelog; +use Ivoz\Core\Domain\Model\Commandlog\Commandlog; use Psr\Log\LoggerInterface; class CommandPersister { - protected $commandEventSubscriber; - protected $entityEventSubscriber; - protected $entityPersister; - protected $logger; - protected $latestCommandlog; public function __construct( - CommandEventSubscriber $commandEventSubscriber, - EntityEventSubscriber $entityEventSubscriber, - EntityPersisterInterface $entityPersister, - LoggerInterface $logger + private CommandEventSubscriber $commandEventSubscriber, + private EntityEventSubscriber $entityEventSubscriber, + private EntityPersisterInterface $entityPersister, + private LoggerInterface $logger ) { - $this->commandEventSubscriber = $commandEventSubscriber; - $this->entityEventSubscriber = $entityEventSubscriber; - $this->entityPersister = $entityPersister; - $this->logger = $logger; } /** @@ -36,6 +28,7 @@ public function __construct( */ public function persistEvents() { + /** @var EntityEventInterface[] $entityEvents */ $entityEvents = $this ->entityEventSubscriber ->getEvents(); @@ -145,7 +138,7 @@ public function persistEvents() private function registerFallbackCommand(): CommandWasExecuted { $command = new CommandWasExecuted( - 0, + '0', 'Unregistered', 'Unregistered', [], diff --git a/Infrastructure/Domain/Service/Lifecycle/DoctrineEventSubscriber.php b/Infrastructure/Domain/Service/Lifecycle/DoctrineEventSubscriber.php index e20213d..f9a17e9 100755 --- a/Infrastructure/Domain/Service/Lifecycle/DoctrineEventSubscriber.php +++ b/Infrastructure/Domain/Service/Lifecycle/DoctrineEventSubscriber.php @@ -3,27 +3,21 @@ namespace Ivoz\Core\Infrastructure\Domain\Service\Lifecycle; use Doctrine\Common\EventSubscriber; -use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs; +use Doctrine\DBAL\Events as DbalEvents; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Event\OnFlushEventArgs; -use Doctrine\ORM\Event\PreUpdateEventArgs; +use Doctrine\ORM\Event\{OnFlushEventArgs, PreUpdateEventArgs,}; use Doctrine\ORM\Events; use Doctrine\ORM\PersistentCollection; -use Ivoz\Core\Application\Helper\EntityClassHelper; -use Ivoz\Core\Application\Helper\LifecycleServiceHelper; -use Ivoz\Core\Domain\Event\EntityWasCreated; -use Ivoz\Core\Domain\Event\EntityWasDeleted; -use Ivoz\Core\Domain\Event\EntityWasUpdated; -use Ivoz\Core\Domain\Model\EntityInterface; -use Ivoz\Core\Domain\Model\LoggableEntityInterface; -use Ivoz\Core\Domain\Model\LoggerEntityInterface; -use Ivoz\Core\Domain\Service\CommonLifecycleServiceCollection; -use Ivoz\Core\Domain\Service\DomainEventPublisher; -use Ivoz\Core\Domain\Service\LifecycleEventHandlerInterface; -use Ivoz\Core\Domain\Service\LifecycleServiceCollectionInterface; -use Ivoz\Core\Infrastructure\Persistence\Doctrine\Events as CustomEvents; -use Ivoz\Core\Infrastructure\Persistence\Doctrine\OnCommitEventArgs; -use Ivoz\Core\Infrastructure\Persistence\Doctrine\OnErrorEventArgs; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Doctrine\ORM\Tools\ToolEvents; +use Doctrine\Persistence\Event\LifecycleEventArgs; +use Ivoz\Core\Domain\Helper\{EntityClassHelper, LifecycleServiceHelper,}; +use Ivoz\Core\Domain\Event\{EntityWasCreated, EntityWasDeleted, EntityWasUpdated,}; +use Ivoz\Core\Domain\Model\{EntityInterface, LoggableEntityInterface, LoggerEntityInterface,}; +use Ivoz\Core\Domain\Service\{CommonLifecycleServiceCollection, DomainEventPublisher,}; +use Ivoz\Core\Domain\Service\{LifecycleEventHandlerInterface, LifecycleServiceCollectionInterface}; +use Ivoz\Core\Infrastructure\Persistence\Doctrine\{Events as CustomEvents, OnCommitEventArgs, OnErrorEventArgs}; use Symfony\Component\DependencyInjection\ContainerInterface; class DoctrineEventSubscriber implements EventSubscriber @@ -35,6 +29,7 @@ class DoctrineEventSubscriber implements EventSubscriber protected $eventPublisher; protected $commandPersister; protected $forcedEntityChangeLog; + protected $schemaManager; /** * @var EntityInterface[] @@ -53,6 +48,7 @@ public function __construct( $this->eventPublisher = $eventPublisher; $this->commandPersister = $commandPersister; $this->forcedEntityChangeLog = $forcedEntityChangeLog; + $this->schemaManager = $em->getConnection()->createSchemaManager(); } public function getSubscribedEvents() @@ -73,6 +69,9 @@ public function getSubscribedEvents() CustomEvents::onCommit, CustomEvents::onError, CustomEvents::onHydratorComplete, + + DbalEvents::onSchemaColumnDefinition, + ToolEvents::postGenerateSchema, ]; } @@ -83,7 +82,40 @@ public function onHydratorComplete(LifecycleEventArgs $args) return; } - $object->initChangelog(); + if (!$object->isInitialized()) { + $object->initChangelog(); + } + } + + public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args) + { + (function () use ($args) { + $tableColumn = $args->getTableColumn(); + unset($tableColumn['CharacterSet']); + unset($tableColumn['Collation']); + + $column = $this->_getPortableTableColumnDefinition( + $tableColumn + ); + $args->setColumn($column); + $args->preventDefault(); + })->call($this->schemaManager); + + return $args; + } + + public function postGenerateSchema(GenerateSchemaEventArgs $args) + { + $schema = $args->getSchema(); + foreach ($schema->getTables() as $table) { + foreach ($table->getColumns() as $column) { + $platformOptions = $column->getPlatformOptions(); + unset($platformOptions['version']); + $column->setPlatformOptions($platformOptions); + } + } + + return $args; } /** @@ -167,6 +199,18 @@ public function onFlush(OnFlushEventArgs $eventArgs) $this->flushedEntities[] = $entity; } } + + $uniqueObjectIds = array_unique( + array_map('spl_object_id', $this->flushedEntities) + ); + + foreach ($this->flushedEntities as $key => $entity) { + if (array_key_exists($key, $uniqueObjectIds)) { + continue; + } + + unset($this->flushedEntities[$key]); + } } /** @@ -193,7 +237,10 @@ public function onCommit(OnCommitEventArgs $args) } foreach ($this->flushedEntities as $entity) { - if (!$entity->__isInitialized__) { + if ( + !property_exists($entity, '__isInitialized__') + || !$entity->__isInitialized__ + ) { continue; } $entity->initChangelog(); diff --git a/Infrastructure/Domain/Service/Mailer/Client.php b/Infrastructure/Domain/Service/Mailer/Client.php index 0bd1299..120bc39 100644 --- a/Infrastructure/Domain/Service/Mailer/Client.php +++ b/Infrastructure/Domain/Service/Mailer/Client.php @@ -4,38 +4,24 @@ use Ivoz\Core\Domain\Model\Mailer\Message; use Ivoz\Core\Domain\Service\MailerClientInterface; +use Symfony\Component\Mailer\MailerInterface; class Client implements MailerClientInterface { - /** - * @var \Swift_Mailer - */ - protected $mailer; - - /** - * Sender constructor. - * @param \Swift_Mailer $mailer - */ - public function __construct(\Swift_Mailer $mailer) - { - $this->mailer = $mailer; + public function __construct( + private MailerInterface $mailer + ) { } /** * @param Message $message * @return void */ - public function send(Message $message) + public function send(Message $message): void { - $transport = $this->mailer->getTransport(); - if (!$transport->ping()) { - $transport->stop(); - $transport->start(); - } - $this->mailer ->send( - $message->toSwiftMessage() + $message->toEmail() ); } } diff --git a/Infrastructure/Domain/Service/Mailer/FakeClient.php b/Infrastructure/Domain/Service/Mailer/FakeClient.php index ca03f78..0b25f84 100644 --- a/Infrastructure/Domain/Service/Mailer/FakeClient.php +++ b/Infrastructure/Domain/Service/Mailer/FakeClient.php @@ -7,11 +7,7 @@ class FakeClient implements MailerClientInterface { - /** - * @param Message $message - * @return void - */ - public function send(Message $message) + public function send(Message $message): void { } } diff --git a/Infrastructure/Persistence/Doctrine/ChangelogDoctrineRepository.php b/Infrastructure/Persistence/Doctrine/ChangelogDoctrineRepository.php new file mode 100755 index 0000000..041fa6b --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/ChangelogDoctrineRepository.php @@ -0,0 +1,22 @@ + $hints + * + * @return IterableResult|array */ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) { @@ -36,7 +47,7 @@ public function hydrateRow() ); } - private function mapToDto(array $rows) + private function mapToDto(array $rows): array { $aliasMap = $this->_rsm->getAliasMap(); diff --git a/Infrastructure/Persistence/Doctrine/Hydration/ObjectHydrator.php b/Infrastructure/Persistence/Doctrine/Hydration/ObjectHydrator.php index 6a0afa1..9dbd78f 100644 --- a/Infrastructure/Persistence/Doctrine/Hydration/ObjectHydrator.php +++ b/Infrastructure/Persistence/Doctrine/Hydration/ObjectHydrator.php @@ -4,13 +4,15 @@ use Doctrine\ORM\Internal\Hydration\ObjectHydrator as DoctrineObjectHydrator; use Ivoz\Core\Infrastructure\Persistence\Doctrine\Events as IvozEvents; -use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Doctrine\Persistence\Event\LifecycleEventArgs; use Doctrine\ORM\Events; class ObjectHydrator extends DoctrineObjectHydrator { protected $loadedEntities = []; + private static int $recursionLevel = 0; + /** * {@inheritdoc} */ @@ -22,6 +24,7 @@ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) $this ); + self::$recursionLevel++; $response = parent::hydrateAll(...func_get_args()); $evm->removeEventListener( @@ -34,6 +37,7 @@ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) && is_object($response[0]); if ($mustTriggerEvents) { + /** @var string $reponseClass */ $reponseClass = get_class($response[0]); $foreignEntities = array_filter( $this->loadedEntities, @@ -54,6 +58,14 @@ function ($entity) use ($reponseClass) { return $response; } + protected function cleanup() + { + self::$recursionLevel--; + if (self::$recursionLevel === 0) { + parent::cleanup(); + } + } + public function hydrateRow() { $response = parent::hydrateRow(); diff --git a/Infrastructure/Persistence/Doctrine/Hydration/SimpleObjectHydrator.php b/Infrastructure/Persistence/Doctrine/Hydration/SimpleObjectHydrator.php index e6206a6..a9ccc11 100644 --- a/Infrastructure/Persistence/Doctrine/Hydration/SimpleObjectHydrator.php +++ b/Infrastructure/Persistence/Doctrine/Hydration/SimpleObjectHydrator.php @@ -4,7 +4,7 @@ use Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator as DoctrineSimpleObjectHydrator; use Ivoz\Core\Infrastructure\Persistence\Doctrine\Events as IvozEvents; -use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Doctrine\Persistence\Event\LifecycleEventArgs; class SimpleObjectHydrator extends DoctrineSimpleObjectHydrator { diff --git a/Infrastructure/Persistence/Doctrine/LoggableMigration.php b/Infrastructure/Persistence/Doctrine/LoggableMigration.php index 682b832..c0c849c 100644 --- a/Infrastructure/Persistence/Doctrine/LoggableMigration.php +++ b/Infrastructure/Persistence/Doctrine/LoggableMigration.php @@ -2,24 +2,24 @@ namespace Ivoz\Core\Infrastructure\Persistence\Doctrine; -use Doctrine\DBAL\Migrations\AbstractMigration; +use Doctrine\Migrations\AbstractMigration;; use Doctrine\DBAL\Schema\Schema; -use Ivoz\Core\Application\Event\CommandWasExecuted; +use Ivoz\Core\Domain\Event\CommandWasExecuted; use Ivoz\Core\Domain\Event\EntityWasUpdated; -use Ivoz\Provider\Domain\Model\Changelog\Changelog; -use Ivoz\Provider\Domain\Model\Commandlog\Commandlog; +use Ivoz\Core\Domain\Model\Changelog\Changelog; +use Ivoz\Core\Domain\Model\Commandlog\Commandlog; abstract class LoggableMigration extends AbstractMigration { private $queries = []; - public function postUp(Schema $schema) + public function postUp(Schema $schema): void { $this->logChangesAndHandleErrors('up'); parent::postUp(...func_get_args()); } - public function postDown(Schema $schema) + public function postDown(Schema $schema): void { $this->logChangesAndHandleErrors('down'); parent::postDown(...func_get_args()); @@ -40,7 +40,7 @@ private function logChangesAndHandleErrors(string $direction) private function logChanges(string $direction) { $event = new CommandWasExecuted( - 0, + '0', get_class($this), $direction, [], @@ -60,7 +60,7 @@ private function logChanges(string $direction) foreach ($this->queries as $query) { $event = new EntityWasUpdated( - 'unkown', + 'unknown', 0, [ 'query' => $query, @@ -122,7 +122,7 @@ function ($value) { return $commandQuery; } - protected function addSql($sql, array $params = [], array $types = []) + protected function addSql($sql, array $params = [], array $types = []): void { $this->queries[] = $sql; parent::addSql(...func_get_args()); diff --git a/Infrastructure/Persistence/Doctrine/Mapping/Changelog.Changelog.orm.xml b/Infrastructure/Persistence/Doctrine/Mapping/Changelog.Changelog.orm.xml new file mode 100644 index 0000000..407279f --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/Mapping/Changelog.Changelog.orm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/Infrastructure/Persistence/Doctrine/Mapping/Changelog.ChangelogAbstract.orm.xml b/Infrastructure/Persistence/Doctrine/Mapping/Changelog.ChangelogAbstract.orm.xml new file mode 100644 index 0000000..2facc90 --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/Mapping/Changelog.ChangelogAbstract.orm.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Infrastructure/Persistence/Doctrine/Mapping/Commandlog.Commandlog.orm.xml b/Infrastructure/Persistence/Doctrine/Mapping/Commandlog.Commandlog.orm.xml new file mode 100644 index 0000000..09e64e4 --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/Mapping/Commandlog.Commandlog.orm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/Infrastructure/Persistence/Doctrine/Mapping/Commandlog.CommandlogAbstract.orm.xml b/Infrastructure/Persistence/Doctrine/Mapping/Commandlog.CommandlogAbstract.orm.xml new file mode 100644 index 0000000..260c5a8 --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/Mapping/Commandlog.CommandlogAbstract.orm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Infrastructure/Persistence/Doctrine/Model/DBAL/Types/UtcDateTimeType.php b/Infrastructure/Persistence/Doctrine/Model/DBAL/Types/UtcDateTimeType.php index cc3045d..e0136a4 100755 --- a/Infrastructure/Persistence/Doctrine/Model/DBAL/Types/UtcDateTimeType.php +++ b/Infrastructure/Persistence/Doctrine/Model/DBAL/Types/UtcDateTimeType.php @@ -10,8 +10,17 @@ class UtcDateTimeType extends DateTimeType { static private $utc; + /** + * {@inheritdoc} + * + * @return mixed + */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { + if (is_string($value)) { + return $value; + } + if ($value instanceof \DateTime) { $value->setTimezone( new \DateTimeZone('UTC') @@ -46,4 +55,4 @@ public function convertToPHPValue($value, AbstractPlatform $platform) return $converted; } -} +} \ No newline at end of file diff --git a/Infrastructure/Persistence/Doctrine/Model/Helper/CriteriaHelper.php b/Infrastructure/Persistence/Doctrine/Model/Helper/CriteriaHelper.php index 7544212..228f603 100755 --- a/Infrastructure/Persistence/Doctrine/Model/Helper/CriteriaHelper.php +++ b/Infrastructure/Persistence/Doctrine/Model/Helper/CriteriaHelper.php @@ -120,7 +120,7 @@ private static function arrayToExpressions(array $conditions) /** * @param string $field * @param string $operator - * @param null $value + * @param null|mixed $value * @return Comparison */ private static function createExpression(string $field, string $operator, $value = null) @@ -281,10 +281,10 @@ public static function append(string $operator, Criteria $baseCriteria, Criteria { $operator = strtoupper($operator); if (!in_array($operator, [CompositeExpression::TYPE_OR, CompositeExpression::TYPE_AND])) { - throw new \InvalidArgumentException('Unkown operator ' . $operator); + throw new \InvalidArgumentException('Unknown operator ' . $operator); } - /** @var CompositeExpression $baseExpression */ + /** @var CompositeExpression $expression */ $expression = self::simplifyCompositeExpression( $baseCriteria->getWhereExpression() ); @@ -306,8 +306,12 @@ public static function append(string $operator, Criteria $baseCriteria, Criteria return new Criteria($expression); } - private static function simplifyCompositeExpression(CompositeExpression $expression): CompositeExpression + private static function simplifyCompositeExpression(Expression $expression): Expression { + if (! $expression instanceof CompositeExpression) { + return $expression; + } + $expressionList = $expression->getExpressionList(); $firstExpression = current($expressionList); $isAndCondition = $expression->getType() === CompositeExpression::TYPE_AND; diff --git a/Infrastructure/Persistence/Doctrine/ORM/EntityManager.php b/Infrastructure/Persistence/Doctrine/ORM/EntityManager.php index 17b504d..17b65a6 100755 --- a/Infrastructure/Persistence/Doctrine/ORM/EntityManager.php +++ b/Infrastructure/Persistence/Doctrine/ORM/EntityManager.php @@ -6,13 +6,18 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\ORM\Configuration; +use Doctrine\ORM\Decorator\EntityManagerDecorator; use Doctrine\ORM\EntityManager as DoctrineEntityManager; +use Doctrine\ORM\NativeQuery; use Doctrine\ORM\ORMException; +use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query as DqlQuery; use Ivoz\Core\Infrastructure\Persistence\Doctrine\Hydration\ObjectHydrator; use Ivoz\Core\Infrastructure\Persistence\Doctrine\Hydration\SimpleObjectHydrator; use Ivoz\Core\Infrastructure\Persistence\Doctrine\Hydration\DtoHydrator; -class EntityManager extends DoctrineEntityManager implements ToggleableBufferedQueryInterface +class EntityManager extends EntityManagerDecorator implements ToggleableBufferedQueryInterface { public function enableBufferedQuery() { @@ -27,12 +32,14 @@ public function disableBufferedQuery() private function setBufferedQuery(bool $enabled = true) { // https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php - (function () use ($enabled) { - $this->connect(); - $driverName = $this->_conn->getAttribute(\PDO::ATTR_DRIVER_NAME); + + /** @var \PDO $connection */ + $connection = $this->getNativeConnection(); + $driverName = $connection->getAttribute(\PDO::ATTR_DRIVER_NAME); + if ($driverName === 'mysql') { - $this->_conn->setAttribute( + $connection->setAttribute( \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $enabled ); @@ -65,7 +72,30 @@ public static function create($conn, Configuration $config, EventManager $eventM throw new \InvalidArgumentException("Invalid argument: " . $conn); } - return new static($conn, $config, $conn->getEventManager()); + $emRef = new \ReflectionClass( + DoctrineEntityManager::class + ); + /** @var DoctrineEntityManager $em */ + $em = $emRef->newInstanceWithoutConstructor(); + $eventManager = $conn->getEventManager(); + (function () use ($conn, $config, $eventManager) { + $this->__construct($conn, $config, $eventManager); + })->call($em); + + $instance = new self($em); + (function () use ($instance) { + $this->em = $instance; + })->call($instance->getUnitOfWork()); + + return $instance; + } + + /** + * {@inheritDoc} + */ + public function getHydrator($hydrationMode) + { + return $this->newHydrator($hydrationMode); } /** @@ -87,4 +117,39 @@ public function newHydrator($hydrationMode) return parent::newHydrator(...func_get_args()); } } -} + + /** + * {@inheritDoc} + */ + public function createQuery($dql = '') + { + $query = new DqlQuery($this); + + if (! empty($dql)) { + $query->setDQL($dql); + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function createNativeQuery($sql, ResultSetMapping $rsm) + { + $query = new NativeQuery($this); + + $query->setSQL($sql); + $query->setResultSetMapping($rsm); + + return $query; + } + + /** + * {@inheritDoc} + */ + public function createQueryBuilder() + { + return new QueryBuilder($this); + } +} \ No newline at end of file diff --git a/Infrastructure/Persistence/Doctrine/ORM/Mapping/QuoteStrategy.php b/Infrastructure/Persistence/Doctrine/ORM/Mapping/QuoteStrategy.php index 4bc5808..9027cfd 100644 --- a/Infrastructure/Persistence/Doctrine/ORM/Mapping/QuoteStrategy.php +++ b/Infrastructure/Persistence/Doctrine/ORM/Mapping/QuoteStrategy.php @@ -5,17 +5,20 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\DefaultQuoteStrategy; +use Doctrine\ORM\Internal\SQLResultCasing; class QuoteStrategy extends DefaultQuoteStrategy { + use SQLResultCasing; + /** * {@inheritdoc} */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null) { $columnName = parent::getColumnAlias(...func_get_args()); - $columnName = is_numeric($columnName{0}) ? '_' . $columnName : $columnName; + $columnName = is_numeric($columnName[0]) ? '_' . $columnName : $columnName; - return $platform->getSQLResultCasing($columnName); + return $this->getSQLResultCasing($platform, $columnName); } } diff --git a/Infrastructure/Persistence/Doctrine/Repository/DoctrineRepository.php b/Infrastructure/Persistence/Doctrine/Repository/DoctrineRepository.php new file mode 100644 index 0000000..3611b99 --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/Repository/DoctrineRepository.php @@ -0,0 +1,94 @@ + + * @implements RepositoryInterface + */ +class DoctrineRepository extends ServiceEntityRepository implements RepositoryInterface +{ + public function __construct( + ManagerRegistry $registry, + string $entityClass, + private EntityPersisterInterface $entityPersister, + ) { + parent::__construct( + $registry, + $entityClass + ); + } + + /** + * @param DtoT $dto + * @param T|null $entity + * @return T + */ + public function persistDto(DataTransferObjectInterface $dto, EntityInterface $entity = null, $dispatchImmediately = false): EntityInterface + { + return + $this + ->entityPersister + ->persistDto( + $dto, + $entity, + $dispatchImmediately + ); + } + + /** + * @param T $entity + */ + public function persist(EntityInterface $entity, bool $dispatchImmediately = false): void + { + $this + ->entityPersister + ->persist( + $entity, + $dispatchImmediately + ); + } + + /** + * @param T $entity + */ + public function remove(EntityInterface $entity): void + { + $this + ->entityPersister + ->remove( + $entity + ); + } + + /** + * @param T[] $entities + */ + public function removeFromArray(array $entities): void + { + $this + ->entityPersister + ->removeFromArray( + $entities + ); + } + + public function dispatchQueued(): void + { + $this->entityPersister->dispatchQueued(); + } +} \ No newline at end of file diff --git a/Infrastructure/Persistence/Doctrine/Service/DuplicateEntryCommonErrorHandler.php b/Infrastructure/Persistence/Doctrine/Service/DuplicateEntryCommonErrorHandler.php index c2a0d7a..6b566d3 100644 --- a/Infrastructure/Persistence/Doctrine/Service/DuplicateEntryCommonErrorHandler.php +++ b/Infrastructure/Persistence/Doctrine/Service/DuplicateEntryCommonErrorHandler.php @@ -2,7 +2,7 @@ namespace Ivoz\Core\Infrastructure\Persistence\Doctrine\Service; -use Doctrine\DBAL\Driver\PDOException; +use Doctrine\DBAL\Driver\PDO\Exception as PDOException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Ivoz\Core\Domain\Service\CommonPersistErrorHandlerInterface; @@ -34,7 +34,7 @@ public function handle(\Throwable $exception) return; } - $isDuplicatedError = $pdoException->getErrorCode() === self::MYSQL_ERROR_DUPLICATE_ENTRY; + $isDuplicatedError = $pdoException->getCode() === self::MYSQL_ERROR_DUPLICATE_ENTRY; if ($isDuplicatedError) { throw new \DomainException( diff --git a/Infrastructure/Persistence/Doctrine/Service/OnDeleteRestrictCommonErrorHandler.php b/Infrastructure/Persistence/Doctrine/Service/OnDeleteRestrictCommonErrorHandler.php new file mode 100755 index 0000000..0f1ab2b --- /dev/null +++ b/Infrastructure/Persistence/Doctrine/Service/OnDeleteRestrictCommonErrorHandler.php @@ -0,0 +1,39 @@ + self::ON_ERROR_PRIORITY, + ]; + } + + public function handle(\Throwable $exception) + { + if (!$exception instanceof ForeignKeyConstraintViolationException) { + return; + } + + preg_match( + '/\(`[^`]+`\.`([^`]+)`/', + $exception->getMessage(), + $results + ); + $entity = $results[1] ?? 'unknown'; + + + throw new \DomainException( + 'Unable delete this element, due to is being used by '. $entity, + 0, + $exception + ); + } +} diff --git a/Infrastructure/Persistence/Filesystem/ScheduleForRemove.php b/Infrastructure/Persistence/Filesystem/ScheduleForRemove.php index bc93a36..a74fe7d 100644 --- a/Infrastructure/Persistence/Filesystem/ScheduleForRemove.php +++ b/Infrastructure/Persistence/Filesystem/ScheduleForRemove.php @@ -2,7 +2,7 @@ namespace Ivoz\Core\Infrastructure\Persistence\Filesystem; -use Ivoz\Core\Application\Service\StoragePathResolverCollection; +use Ivoz\Core\Domain\Service\StoragePathResolverCollection; use Ivoz\Core\Domain\Model\EntityInterface; use Ivoz\Core\Domain\Service\CommonLifecycleEventHandlerInterface; use Ivoz\Core\Domain\Service\FileContainerInterface; diff --git a/Infrastructure/Persistence/Redis/FakeRedisMasterFactory.php b/Infrastructure/Persistence/Redis/FakeRedisMasterFactory.php index d9c406e..6d1d66b 100644 --- a/Infrastructure/Persistence/Redis/FakeRedisMasterFactory.php +++ b/Infrastructure/Persistence/Redis/FakeRedisMasterFactory.php @@ -13,16 +13,19 @@ public function connect( $timeout = 0.0, $reserved = null, $retryInterval = 0, - $readTimeout = 0.0 - ) {} + $readTimeout = 0.0, + $context = null + ) { + return false; + } - public function lPush($key, $value1) {} + public function lPush($key, ...$value1) { return false; } - public function rPush($key, $value1) {} + public function rPush($key, ...$value1) { return false; } - public function blPop($key, $timeout_or_key, ...$extra_args) {} + public function blPop($key, $timeout_or_key, ...$extra_args) { return []; } - public function scan(&$iterator, $pattern = null, $count = 0) + public function scan(&$iterator, $pattern = null, $count = 0, ...$extra_args) { $iterator = 0; return ['something']; diff --git a/Infrastructure/Persistence/Redis/Lock.php b/Infrastructure/Persistence/Redis/Lock.php index b9f7152..fa536b0 100644 --- a/Infrastructure/Persistence/Redis/Lock.php +++ b/Infrastructure/Persistence/Redis/Lock.php @@ -2,7 +2,7 @@ namespace Ivoz\Core\Infrastructure\Persistence\Redis; -use Ivoz\Core\Application\MutexInterface; +use Ivoz\Core\Domain\MutexInterface; use Psr\Log\LoggerInterface; class Lock implements MutexInterface @@ -11,7 +11,7 @@ class Lock implements MutexInterface private $logger; private $dbIndex; - /** @var \Redis */ + /** @var ?\Redis */ private $redisMaster; private $lockKey; diff --git a/Infrastructure/Persistence/Redis/Sentinel.php b/Infrastructure/Persistence/Redis/Sentinel.php index aa8324e..c708dd1 100644 --- a/Infrastructure/Persistence/Redis/Sentinel.php +++ b/Infrastructure/Persistence/Redis/Sentinel.php @@ -106,7 +106,7 @@ private function getRedisMasterOrThrowException(RedisConf $config): RedisConf $masterName ); - if (empty($masters)) { + if (empty($master)) { throw new \RuntimeException( 'Unable to get redis master' ); diff --git a/Infrastructure/Service/Rest/Client.php b/Infrastructure/Service/Rest/Client.php deleted file mode 100644 index a7ddf76..0000000 --- a/Infrastructure/Service/Rest/Client.php +++ /dev/null @@ -1,139 +0,0 @@ -httpClient = $platformHttpClient; - $this->jwtTokenManager = $jwtTokenManager; - $this->administratorRepository = $administratorRepository; - - $privateAdmin = $this->administratorRepository->getInnerGlobalAdmin(); - $this->jwtToken = $this->jwtTokenManager->create( - $privateAdmin - ); - } - - /** - * @param string $uri - * @param array $options - * @return ResponseInterface - */ - public function get(string $uri, array $options = []) - { - $options = $this->appendAuthHeaders($options); - - return $this->request( - 'GET', - $uri, - $options - ); - } - - /** - * @param string $uri - * @param array $options - * @return ResponseInterface - */ - public function post(string $uri, array $options = []) - { - $options = $this->appendAuthHeaders($options); - - return $this->request( - 'POST', - $uri, - $options - ); - } - - /** - * @param string $uri - * @param array $options - * @return ResponseInterface - */ - public function put(string $uri, array $options = []) - { - $options = $this->appendAuthHeaders($options); - - return $this->request( - 'PUT', - $uri, - $options - ); - } - - /** - * @param string $uri - * @param array $options - * @return ResponseInterface - */ - public function delete(string $uri, array $options = []) - { - $options = $this->appendAuthHeaders($options); - - return $this->request( - 'DELETE', - $uri, - $options - ); - } - - /** - * - * @param string $method HTTP method. - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. - * - * @return ResponseInterface - * @throws \RuntimeException - */ - protected function request($method, $uri = '', array $options) - { - try { - return $this->httpClient->request( - $method, - $uri, - $options - ); - } catch (\Exception $e) { - throw new \RuntimeException( - $e->getMessage(), - $e->getCode(), - $e - ); - } - } - - /** - * @return array - * - * @psalm-return array{"headers":empty, headers:array{Authorization:string}} - */ - private function appendAuthHeaders(array $options): array - { - if (!array_key_exists('headers', $options)) { - $options['headers'] = []; - } - $options['headers']['Authorization'] = 'Bearer ' . $this->jwtToken; - - return $options; - } -} diff --git a/Infrastructure/Service/Rest/FakeClient.php b/Infrastructure/Service/Rest/FakeClient.php deleted file mode 100644 index fd26e33..0000000 --- a/Infrastructure/Service/Rest/FakeClient.php +++ /dev/null @@ -1,47 +0,0 @@ - + * @psalm-return array */ protected function getLifecycleEventHandlerServices(): array { diff --git a/Infrastructure/Symfony/DependencyInjection/Compiler/LifecycleCompiler.php b/Infrastructure/Symfony/DependencyInjection/Compiler/LifecycleCompiler.php index 7346016..50b7392 100644 --- a/Infrastructure/Symfony/DependencyInjection/Compiler/LifecycleCompiler.php +++ b/Infrastructure/Symfony/DependencyInjection/Compiler/LifecycleCompiler.php @@ -144,12 +144,8 @@ protected function getServicesByTag($tag): array { $services = $this->container->findTaggedServiceIds($tag); - /** - * @var Definition $a - * @var Definition $b - */ - uasort($services, function ($a, $b) { - return $a[0]['priority'] > $b[0]['priority']; + uasort($services, function (array $a, array $b) { + return (int) ($a[0]['priority'] > $b[0]['priority']); }); return array_keys($services); diff --git a/Infrastructure/Symfony/DependencyInjection/Compiler/RepositoryCompiler.php b/Infrastructure/Symfony/DependencyInjection/Compiler/RepositoryCompiler.php index d16c333..757404b 100644 --- a/Infrastructure/Symfony/DependencyInjection/Compiler/RepositoryCompiler.php +++ b/Infrastructure/Symfony/DependencyInjection/Compiler/RepositoryCompiler.php @@ -4,7 +4,6 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; class RepositoryCompiler implements CompilerPassInterface { @@ -26,8 +25,6 @@ public function process(ContainerBuilder $container) } /** - * @param Definition[] $services - * * @return void */ protected function setRepositoryAliases(array $services) diff --git a/Infrastructure/Symfony/EventListener/DomainExceptionListener.php b/Infrastructure/Symfony/EventListener/DomainExceptionListener.php index 9501ffc..a050178 100644 --- a/Infrastructure/Symfony/EventListener/DomainExceptionListener.php +++ b/Infrastructure/Symfony/EventListener/DomainExceptionListener.php @@ -4,17 +4,17 @@ use Assert\InvalidArgumentException; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; class DomainExceptionListener { /** - * @param GetResponseForExceptionEvent $event + * @param ExceptionEvent $event * @return void */ - public function onKernelException(GetResponseForExceptionEvent $event) + public function onKernelException(ExceptionEvent $event) { - $exception = $event->getException(); + $exception = $event->getThrowable(); $exceptionClass = get_class($exception); $publicExceptions = [ \DomainException::class, @@ -25,9 +25,13 @@ public function onKernelException(GetResponseForExceptionEvent $event) return; } + $exceptionCode = $exception->getCode() + ? $exception->getCode() + : Response::HTTP_FAILED_DEPENDENCY; + $event->setResponse(new Response( $exception->getMessage(), - $exception->getCode() ?? Response::HTTP_FAILED_DEPENDENCY, + $exceptionCode, [ 'X-Content-Type-Options' => 'nosniff', 'X-Frame-Options' => 'deny', diff --git a/bin/test-phpstan b/bin/test-phpstan new file mode 100755 index 0000000..ba54135 --- /dev/null +++ b/bin/test-phpstan @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +run_test () { + TARGET=$1 + echo "==========================" + echo "ivoz-core" + echo "==========================" + + ./vendor/bin/phpstan analyse \ + --level 5 \ + --ansi \ + --no-progress \ + --autoload-file ./vendor/autoload.php \ + --configuration phpstan.neon \ + ${TARGET} $2 +} + +PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")/../" ; pwd -P ) +pushd $PARENT_PATH + run_test . $1 +popd diff --git a/composer.json b/composer.json index c40a1c0..6be8f68 100644 --- a/composer.json +++ b/composer.json @@ -24,18 +24,42 @@ } }, "require": { - "php": ">=7.0.19", - "beberlei/assert": "^3.3", - "doctrine/orm": "^2.5", + "php": ">=8.0", + "beberlei/assert": "^3.0", + "doctrine/orm": "^2.8", + "doctrine/dbal": "^3.2", + "doctrine/doctrine-migrations-bundle": "^3.0.3", "graze/guzzle-jsonrpc": "3.2.*", "guzzlehttp/guzzle": "^6.3", "lexik/jwt-authentication-bundle": "^2.5", - "ramsey/uuid": "^4.3", - "symfony/expression-language": "3.4.*", - "symfony/finder": "3.4.*", - "symfony/http-foundation": "3.4.*", - "symfony/serializer": "3.4.*" + "ramsey/uuid": "^4.2", + "symfony/expression-language": "^5.1", + "symfony/finder": "^5.1", + "symfony/http-foundation": "^5.1", + "symfony/serializer": "^5.1", + "symfony/mailer": "^5.4", + "symfony/mime": "^5.4" }, "require-dev": { + "phpstan/phpstan": "^1.0" + }, + "conflict": { + "symfony/security-bundle": "^6.0", + "symfony/security-core": "^6.0", + "symfony/security-csrf": "^6.0", + "symfony/security-http": "^6.0", + "symfony/stopwatch": "^6.0", + "symfony/string": "^6.0", + "symfony/twig-bridge": "^6.0", + "symfony/var-exporter": "^6.0", + "symfony/web-link": "^6.0", + "symfony/translation": "^6.0", + "symfony/yaml": "^6.0", + "symfony/cache": "^6.0", + "symfony/event-dispatcher": "^6.0", + "symfony/filesystem": "^6.0", + "symfony/monolog-bridge": "^6.0", + "symfony/options-resolver": "^6.0", + "symfony/password-hasher": "^6.0" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..18b6d86 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + excludePaths: + analyse: + - vendor + ignoreErrors: + - '#Unsafe usage of new static\(\)#' + - '#.*has invalid type Doctrine\\DBAL\\Driver\\ResultStatement#'