From cd5fea491441d4135f8984cf382502ea0fb15662 Mon Sep 17 00:00:00 2001 From: kloz Date: Tue, 16 May 2023 19:20:31 +0200 Subject: [PATCH 01/26] [VSF2-177] Taxon repository decoration --- src/Doctrine/Repository/TaxonRepository.php | 158 +++++++++++++++++- .../Repository/TaxonRepositoryInterface.php | 3 +- src/Resources/services/doctrine_orm.xml | 4 + .../Application/config/packages/_sylius.yaml | 6 - 4 files changed, 160 insertions(+), 11 deletions(-) diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index 1d3cfab3..7813164e 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -11,23 +11,175 @@ namespace BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository; use Doctrine\ORM\QueryBuilder; -use Sylius\Bundle\TaxonomyBundle\Doctrine\ORM\TaxonRepository as BaseTaxonRepository; +use Gedmo\Tree\Entity\Repository\NestedTreeRepository; +use Sylius\Bundle\ResourceBundle\Doctrine\ORM\ResourceRepositoryTrait; use Sylius\Component\Taxonomy\Model\TaxonInterface; +use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface as BaseTaxonRepositoryInterface; -final class TaxonRepository extends BaseTaxonRepository implements TaxonRepositoryInterface +final class TaxonRepository extends NestedTreeRepository implements TaxonRepositoryInterface, BaseTaxonRepositoryInterface { + use ResourceRepositoryTrait; + + public function __construct(private BaseTaxonRepositoryInterface $decoratedRepository) + { + parent::__construct($this->decoratedRepository->_em, $decoratedRepository->_class); + } + public function createChildrenByChannelMenuTaxonQueryBuilder( ?TaxonInterface $menuTaxon = null, ?string $locale = null, ): QueryBuilder { + $qb = $this->childrenQueryBuilder($menuTaxon, false, 'position', 'asc'); + $alias = $qb->getRootAliases()[0] ?? 'node'; + + return $this->addTranslations($qb, $alias, $locale) + ->andWhere($alias . '.enabled = :enabled') + ->setParameter('enabled', true); + } + + public function findChildren(string $parentCode, ?string $locale = null): array + { + return $this->createTranslationBasedQueryBuilder($locale) + ->addSelect('child') + ->innerJoin('o.parent', 'parent') + ->leftJoin('o.children', 'child') + ->andWhere('parent.code = :parentCode') + ->addOrderBy('o.position') + ->setParameter('parentCode', $parentCode) + ->getQuery() + ->getResult() + ; + } + + public function findChildrenByChannelMenuTaxon(?TaxonInterface $menuTaxon = null, ?string $locale = null): array + { + $hydrationQuery = $this->createTranslationBasedQueryBuilder($locale) + ->addSelect('o') + ->addSelect('oc') + ->leftJoin('o.children', 'oc') + ; + + if (null !== $menuTaxon) { + $hydrationQuery + ->andWhere('o.root = :root') + ->setParameter('root', $menuTaxon) + ; + } + + $hydrationQuery->getQuery()->getResult(); + return $this->createTranslationBasedQueryBuilder($locale) ->addSelect('child') ->innerJoin('o.parent', 'parent') ->leftJoin('o.children', 'child') - ->andWhere('o.enabled = true') + ->andWhere('o.enabled = :enabled') ->andWhere('parent.code = :parentCode') ->addOrderBy('o.position') ->setParameter('parentCode', ($menuTaxon !== null) ? $menuTaxon->getCode() : 'category') + ->setParameter('enabled', true) + ->getQuery() + ->getResult() + ; + } + + public function findRootNodes(): array + { + return $this->createQueryBuilder('o') + ->andWhere('o.parent IS NULL') + ->addOrderBy('o.position') + ->getQuery() + ->getResult() + ; + } + + public function findHydratedRootNodes(): array + { + $this->createQueryBuilder('o') + ->select(['o', 'oc', 'ot']) + ->leftJoin('o.children', 'oc') + ->leftJoin('o.translations', 'ot') + ->getQuery() + ->getResult() ; + + return $this->findRootNodes(); + } + + public function findOneBySlug(string $slug, string $locale): ?TaxonInterface + { + return $this->createQueryBuilder('o') + ->addSelect('translation') + ->innerJoin('o.translations', 'translation') + ->andWhere('o.enabled = :enabled') + ->andWhere('translation.slug = :slug') + ->andWhere('translation.locale = :locale') + ->setParameter('slug', $slug) + ->setParameter('locale', $locale) + ->setParameter('enabled', true) + ->getQuery() + ->getOneOrNullResult() + ; + } + + public function findByName(string $name, string $locale): array + { + return $this->createQueryBuilder('o') + ->addSelect('translation') + ->innerJoin('o.translations', 'translation') + ->andWhere('translation.name = :name') + ->andWhere('translation.locale = :locale') + ->setParameter('name', $name) + ->setParameter('locale', $locale) + ->getQuery() + ->getResult() + ; + } + + public function findByNamePart(string $phrase, ?string $locale = null, ?int $limit = null): array + { + /** @var TaxonInterface[] $results */ + $results = $this->createTranslationBasedQueryBuilder($locale) + ->andWhere('translation.name LIKE :name') + ->setParameter('name', '%' . $phrase . '%') + ->setMaxResults($limit) + ->getQuery() + ->getResult() + ; + + foreach ($results as $result) { + $result->setFallbackLocale(array_key_first($result->getTranslations()->toArray())); + } + + return $results; + } + + public function createListQueryBuilder(): QueryBuilder + { + return $this->createQueryBuilder('o')->leftJoin('o.translations', 'translation'); + } + + private function createTranslationBasedQueryBuilder(?string $locale): QueryBuilder + { + $alias = 'o'; + $queryBuilder = $this->createQueryBuilder($alias); + + return $this->addTranslations($queryBuilder, $alias, $locale); + } + + private function addTranslations(QueryBuilder $queryBuilder, string $alias, ?string $locale): QueryBuilder + { + $queryBuilder + ->addSelect('translation') + ->leftJoin($alias . '.translations', 'translation') + ; + + if (null !== $locale) { + $queryBuilder + ->andWhere('translation.locale = :locale') + ->setParameter('locale', $locale) + ; + } + + return $queryBuilder; } } diff --git a/src/Doctrine/Repository/TaxonRepositoryInterface.php b/src/Doctrine/Repository/TaxonRepositoryInterface.php index 9fae14a6..195b861b 100755 --- a/src/Doctrine/Repository/TaxonRepositoryInterface.php +++ b/src/Doctrine/Repository/TaxonRepositoryInterface.php @@ -11,10 +11,9 @@ namespace BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository; use Doctrine\ORM\QueryBuilder; -use Sylius\Component\Resource\Repository\RepositoryInterface; use Sylius\Component\Taxonomy\Model\TaxonInterface; -interface TaxonRepositoryInterface extends RepositoryInterface +interface TaxonRepositoryInterface { public function createChildrenByChannelMenuTaxonQueryBuilder( ?TaxonInterface $menuTaxon = null, diff --git a/src/Resources/services/doctrine_orm.xml b/src/Resources/services/doctrine_orm.xml index 001c1b6b..53849a30 100644 --- a/src/Resources/services/doctrine_orm.xml +++ b/src/Resources/services/doctrine_orm.xml @@ -30,5 +30,9 @@ We are hiring developers from all over the world. Join us and start your new, ex + + + diff --git a/tests/Application/config/packages/_sylius.yaml b/tests/Application/config/packages/_sylius.yaml index a290e029..d4bdff13 100644 --- a/tests/Application/config/packages/_sylius.yaml +++ b/tests/Application/config/packages/_sylius.yaml @@ -17,12 +17,6 @@ sylius_attribute: attribute_value: classes: model: Tests\BitBag\SyliusVueStorefront2Plugin\Entity\ProductAttributeValue - -sylius_taxonomy: - resources: - taxon: - classes: - repository: BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository\TaxonRepository sylius_api: enabled: true From 1a1b6739a819736b07b532a544ca7a5b65dc339d Mon Sep 17 00:00:00 2001 From: kloz Date: Tue, 16 May 2023 19:39:14 +0200 Subject: [PATCH 02/26] [VSF2-177] Extended taxon serialization --- src/Resources/serialization/Taxon.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Resources/serialization/Taxon.xml b/src/Resources/serialization/Taxon.xml index 0940faca..a19f285a 100644 --- a/src/Resources/serialization/Taxon.xml +++ b/src/Resources/serialization/Taxon.xml @@ -29,5 +29,17 @@ We are hiring developers from all over the world. Join us and start your new, ex admin:taxon:read shop:taxon:read + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + From 287f9791a7ca63993af8075c20e5fbd7627589d4 Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 12:14:28 +0200 Subject: [PATCH 03/26] [VSF2-177] Taxon serialization using DTO --- src/DTO/Taxon/TaxonDto.php | 80 +++++++++++++++++++ src/DTO/Taxon/TaxonParentDto.php | 25 ++++++ .../TaxonCollectionDataProvider.php | 46 +++++++++-- src/Resources/serialization/TaxonDto.xml | 45 +++++++++++ .../serialization/TaxonParentDto.xml | 18 +++++ 5 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 src/DTO/Taxon/TaxonDto.php create mode 100644 src/DTO/Taxon/TaxonParentDto.php create mode 100644 src/Resources/serialization/TaxonDto.xml create mode 100644 src/Resources/serialization/TaxonParentDto.xml diff --git a/src/DTO/Taxon/TaxonDto.php b/src/DTO/Taxon/TaxonDto.php new file mode 100644 index 00000000..c6e71bfb --- /dev/null +++ b/src/DTO/Taxon/TaxonDto.php @@ -0,0 +1,80 @@ +id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function getPosition(): ?int + { + return $this->position; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function getEnabled(): ?bool + { + return $this->enabled; + } + + public function getLevel(): ?int + { + return $this->level; + } + + public function getTranslations(): Collection + { + return $this->translations; + } + + public function getParent(): ?TaxonParentDto + { + return $this->parent; + } +} diff --git a/src/DTO/Taxon/TaxonParentDto.php b/src/DTO/Taxon/TaxonParentDto.php new file mode 100644 index 00000000..4e1f0323 --- /dev/null +++ b/src/DTO/Taxon/TaxonParentDto.php @@ -0,0 +1,25 @@ +taxon->getId(); + } +} diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index 0e2ece48..5a263a60 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -18,6 +18,8 @@ use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository\TaxonRepositoryInterface; +use BitBag\SyliusVueStorefront2Plugin\DTO\Taxon\TaxonDto; +use BitBag\SyliusVueStorefront2Plugin\DTO\Taxon\TaxonParentDto; use Sylius\Bundle\ApiBundle\Context\UserContextInterface; use Sylius\Bundle\ApiBundle\Serializer\ContextKeys; use Sylius\Component\Core\Model\ChannelInterface; @@ -66,7 +68,7 @@ public function getCollection(string $resourceClass, string $operationName = nul $channelMenuTaxon = $channelContext->getMenuTaxon(); $user = $this->userContext->getUser(); - if ($this->isUserAllowedToGetAllTaxa($user)) { + if ($this->hasAccessToAllTaxa($user)) { return $this->taxonRepository->findAll(); } @@ -83,21 +85,49 @@ public function getCollection(string $resourceClass, string $operationName = nul } if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) { - return $extension->getResult($queryBuilder); + return $this->parseResult($extension->getResult($queryBuilder)); } } - return $this->paginationExtension->getResult( - $queryBuilder, - $resourceClass, - $operationName, - $context, + return $this->parseResult( + $this->paginationExtension->getResult( + $queryBuilder, + $resourceClass, + $operationName, + $context, + ) ); } - private function isUserAllowedToGetAllTaxa(?UserInterface $user): bool + private function hasAccessToAllTaxa(?UserInterface $user): bool { /** @psalm-suppress DeprecatedClass */ return $user !== null && in_array('ROLE_API_ACCESS', $user->getRoles(), true); } + + private function parseResult(iterable $result): array + { + $data = []; + foreach ($result as $taxon) { + $data[] = $this->parseTaxon($taxon); + } + + return $data; + } + + private function parseTaxon(TaxonInterface $taxon): TaxonDto + { + return new TaxonDto( + $taxon->getId(), + $taxon->getName(), + $taxon->getCode(), + $taxon->getPosition(), + $taxon->getSlug(), + $taxon->getDescription(), + $taxon->isEnabled(), + $taxon->getLevel(), + $taxon->getTranslations(), + $taxon->getParent() ? new TaxonParentDto($taxon->getParent()) : null, + ); + } } diff --git a/src/Resources/serialization/TaxonDto.xml b/src/Resources/serialization/TaxonDto.xml new file mode 100644 index 00000000..f88790e1 --- /dev/null +++ b/src/Resources/serialization/TaxonDto.xml @@ -0,0 +1,45 @@ + + + + + + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + shop:taxon:read + + + diff --git a/src/Resources/serialization/TaxonParentDto.xml b/src/Resources/serialization/TaxonParentDto.xml new file mode 100644 index 00000000..289bc1f8 --- /dev/null +++ b/src/Resources/serialization/TaxonParentDto.xml @@ -0,0 +1,18 @@ + + + + + + + + shop:taxon:read + + + From 5826a6d3086b005fe8f54be7352809e7b5aed9ec Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 14:46:50 +0200 Subject: [PATCH 04/26] Revert "[VSF2-177] Taxon serialization using DTO" This reverts commit 287f9791a7ca63993af8075c20e5fbd7627589d4. --- src/DTO/Taxon/TaxonDto.php | 80 ------------------- src/DTO/Taxon/TaxonParentDto.php | 25 ------ .../TaxonCollectionDataProvider.php | 46 ++--------- src/Resources/serialization/TaxonDto.xml | 45 ----------- .../serialization/TaxonParentDto.xml | 18 ----- 5 files changed, 8 insertions(+), 206 deletions(-) delete mode 100644 src/DTO/Taxon/TaxonDto.php delete mode 100644 src/DTO/Taxon/TaxonParentDto.php delete mode 100644 src/Resources/serialization/TaxonDto.xml delete mode 100644 src/Resources/serialization/TaxonParentDto.xml diff --git a/src/DTO/Taxon/TaxonDto.php b/src/DTO/Taxon/TaxonDto.php deleted file mode 100644 index c6e71bfb..00000000 --- a/src/DTO/Taxon/TaxonDto.php +++ /dev/null @@ -1,80 +0,0 @@ -id; - } - - public function getName(): ?string - { - return $this->name; - } - - public function getCode(): ?string - { - return $this->code; - } - - public function getPosition(): ?int - { - return $this->position; - } - - public function getSlug(): ?string - { - return $this->slug; - } - - public function getDescription(): ?string - { - return $this->description; - } - - public function getEnabled(): ?bool - { - return $this->enabled; - } - - public function getLevel(): ?int - { - return $this->level; - } - - public function getTranslations(): Collection - { - return $this->translations; - } - - public function getParent(): ?TaxonParentDto - { - return $this->parent; - } -} diff --git a/src/DTO/Taxon/TaxonParentDto.php b/src/DTO/Taxon/TaxonParentDto.php deleted file mode 100644 index 4e1f0323..00000000 --- a/src/DTO/Taxon/TaxonParentDto.php +++ /dev/null @@ -1,25 +0,0 @@ -taxon->getId(); - } -} diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index 5a263a60..0e2ece48 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -18,8 +18,6 @@ use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository\TaxonRepositoryInterface; -use BitBag\SyliusVueStorefront2Plugin\DTO\Taxon\TaxonDto; -use BitBag\SyliusVueStorefront2Plugin\DTO\Taxon\TaxonParentDto; use Sylius\Bundle\ApiBundle\Context\UserContextInterface; use Sylius\Bundle\ApiBundle\Serializer\ContextKeys; use Sylius\Component\Core\Model\ChannelInterface; @@ -68,7 +66,7 @@ public function getCollection(string $resourceClass, string $operationName = nul $channelMenuTaxon = $channelContext->getMenuTaxon(); $user = $this->userContext->getUser(); - if ($this->hasAccessToAllTaxa($user)) { + if ($this->isUserAllowedToGetAllTaxa($user)) { return $this->taxonRepository->findAll(); } @@ -85,49 +83,21 @@ public function getCollection(string $resourceClass, string $operationName = nul } if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) { - return $this->parseResult($extension->getResult($queryBuilder)); + return $extension->getResult($queryBuilder); } } - return $this->parseResult( - $this->paginationExtension->getResult( - $queryBuilder, - $resourceClass, - $operationName, - $context, - ) + return $this->paginationExtension->getResult( + $queryBuilder, + $resourceClass, + $operationName, + $context, ); } - private function hasAccessToAllTaxa(?UserInterface $user): bool + private function isUserAllowedToGetAllTaxa(?UserInterface $user): bool { /** @psalm-suppress DeprecatedClass */ return $user !== null && in_array('ROLE_API_ACCESS', $user->getRoles(), true); } - - private function parseResult(iterable $result): array - { - $data = []; - foreach ($result as $taxon) { - $data[] = $this->parseTaxon($taxon); - } - - return $data; - } - - private function parseTaxon(TaxonInterface $taxon): TaxonDto - { - return new TaxonDto( - $taxon->getId(), - $taxon->getName(), - $taxon->getCode(), - $taxon->getPosition(), - $taxon->getSlug(), - $taxon->getDescription(), - $taxon->isEnabled(), - $taxon->getLevel(), - $taxon->getTranslations(), - $taxon->getParent() ? new TaxonParentDto($taxon->getParent()) : null, - ); - } } diff --git a/src/Resources/serialization/TaxonDto.xml b/src/Resources/serialization/TaxonDto.xml deleted file mode 100644 index f88790e1..00000000 --- a/src/Resources/serialization/TaxonDto.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - shop:taxon:read - - - diff --git a/src/Resources/serialization/TaxonParentDto.xml b/src/Resources/serialization/TaxonParentDto.xml deleted file mode 100644 index 289bc1f8..00000000 --- a/src/Resources/serialization/TaxonParentDto.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - shop:taxon:read - - - From 50591ad36d5ef1061297d98146a80e2a757105e9 Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 16:24:27 +0200 Subject: [PATCH 05/26] [VSF2-177] Taxon serializer --- .../TaxonCollectionDataProvider.php | 4 +- src/Resources/services/serializers.xml | 5 +++ src/Serializer/TaxonNormalizer.php | 42 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/Serializer/TaxonNormalizer.php diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index 0e2ece48..e3d31d9e 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -66,7 +66,7 @@ public function getCollection(string $resourceClass, string $operationName = nul $channelMenuTaxon = $channelContext->getMenuTaxon(); $user = $this->userContext->getUser(); - if ($this->isUserAllowedToGetAllTaxa($user)) { + if ($this->hasAccessToAllTaxa($user)) { return $this->taxonRepository->findAll(); } @@ -95,7 +95,7 @@ public function getCollection(string $resourceClass, string $operationName = nul ); } - private function isUserAllowedToGetAllTaxa(?UserInterface $user): bool + private function hasAccessToAllTaxa(?UserInterface $user): bool { /** @psalm-suppress DeprecatedClass */ return $user !== null && in_array('ROLE_API_ACCESS', $user->getRoles(), true); diff --git a/src/Resources/services/serializers.xml b/src/Resources/services/serializers.xml index 46625573..0768f559 100644 --- a/src/Resources/services/serializers.xml +++ b/src/Resources/services/serializers.xml @@ -16,5 +16,10 @@ We are hiring developers from all over the world. Join us and start your new, ex + + + + diff --git a/src/Serializer/TaxonNormalizer.php b/src/Serializer/TaxonNormalizer.php new file mode 100644 index 00000000..77beaf61 --- /dev/null +++ b/src/Serializer/TaxonNormalizer.php @@ -0,0 +1,42 @@ + $object->getId(), + 'name' => $object->getName(), + 'code' => $object->getCode(), + 'position' => $object->getPosition(), + 'slug' => $object->getSlug(), + 'description' => $object->getDescription(), + 'parent' => $object->getParent() + ? ['id' => $object->getParent()->getId()] + : null, + 'enabled' => $object->isEnabled(), + 'level' => $object->getLevel(), + ]; + } + + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof TaxonInterface; + } +} From 3ccd91cca4ca48963369aaf81faf8e0364488d62 Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 16:26:09 +0200 Subject: [PATCH 06/26] [VSF2-177] Taxon decorated repository - methods fix --- src/Doctrine/Repository/TaxonRepository.php | 105 ++------------------ 1 file changed, 8 insertions(+), 97 deletions(-) diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index 7813164e..892227c5 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -39,131 +39,42 @@ public function createChildrenByChannelMenuTaxonQueryBuilder( public function findChildren(string $parentCode, ?string $locale = null): array { - return $this->createTranslationBasedQueryBuilder($locale) - ->addSelect('child') - ->innerJoin('o.parent', 'parent') - ->leftJoin('o.children', 'child') - ->andWhere('parent.code = :parentCode') - ->addOrderBy('o.position') - ->setParameter('parentCode', $parentCode) - ->getQuery() - ->getResult() - ; + return $this->decoratedRepository->findChildren($parentCode, $locale); } public function findChildrenByChannelMenuTaxon(?TaxonInterface $menuTaxon = null, ?string $locale = null): array { - $hydrationQuery = $this->createTranslationBasedQueryBuilder($locale) - ->addSelect('o') - ->addSelect('oc') - ->leftJoin('o.children', 'oc') - ; - - if (null !== $menuTaxon) { - $hydrationQuery - ->andWhere('o.root = :root') - ->setParameter('root', $menuTaxon) - ; - } - - $hydrationQuery->getQuery()->getResult(); - - return $this->createTranslationBasedQueryBuilder($locale) - ->addSelect('child') - ->innerJoin('o.parent', 'parent') - ->leftJoin('o.children', 'child') - ->andWhere('o.enabled = :enabled') - ->andWhere('parent.code = :parentCode') - ->addOrderBy('o.position') - ->setParameter('parentCode', ($menuTaxon !== null) ? $menuTaxon->getCode() : 'category') - ->setParameter('enabled', true) - ->getQuery() - ->getResult() - ; + return $this->decoratedRepository->findChildrenByChannelMenuTaxon($menuTaxon, $locale); } public function findRootNodes(): array { - return $this->createQueryBuilder('o') - ->andWhere('o.parent IS NULL') - ->addOrderBy('o.position') - ->getQuery() - ->getResult() - ; + return $this->decoratedRepository->findRootNodes(); } public function findHydratedRootNodes(): array { - $this->createQueryBuilder('o') - ->select(['o', 'oc', 'ot']) - ->leftJoin('o.children', 'oc') - ->leftJoin('o.translations', 'ot') - ->getQuery() - ->getResult() - ; - - return $this->findRootNodes(); + return $this->decoratedRepository->findHydratedRootNodes(); } public function findOneBySlug(string $slug, string $locale): ?TaxonInterface { - return $this->createQueryBuilder('o') - ->addSelect('translation') - ->innerJoin('o.translations', 'translation') - ->andWhere('o.enabled = :enabled') - ->andWhere('translation.slug = :slug') - ->andWhere('translation.locale = :locale') - ->setParameter('slug', $slug) - ->setParameter('locale', $locale) - ->setParameter('enabled', true) - ->getQuery() - ->getOneOrNullResult() - ; + return $this->decoratedRepository->findOneBySlug($slug, $locale); } public function findByName(string $name, string $locale): array { - return $this->createQueryBuilder('o') - ->addSelect('translation') - ->innerJoin('o.translations', 'translation') - ->andWhere('translation.name = :name') - ->andWhere('translation.locale = :locale') - ->setParameter('name', $name) - ->setParameter('locale', $locale) - ->getQuery() - ->getResult() - ; + return $this->decoratedRepository->findByName($name, $locale); } public function findByNamePart(string $phrase, ?string $locale = null, ?int $limit = null): array { - /** @var TaxonInterface[] $results */ - $results = $this->createTranslationBasedQueryBuilder($locale) - ->andWhere('translation.name LIKE :name') - ->setParameter('name', '%' . $phrase . '%') - ->setMaxResults($limit) - ->getQuery() - ->getResult() - ; - - foreach ($results as $result) { - $result->setFallbackLocale(array_key_first($result->getTranslations()->toArray())); - } - - return $results; + return $this->decoratedRepository->findByNamePart($phrase, $locale, $limit); } public function createListQueryBuilder(): QueryBuilder { - return $this->createQueryBuilder('o')->leftJoin('o.translations', 'translation'); - } - - private function createTranslationBasedQueryBuilder(?string $locale): QueryBuilder - { - $alias = 'o'; - $queryBuilder = $this->createQueryBuilder($alias); - - return $this->addTranslations($queryBuilder, $alias, $locale); + return $this->decoratedRepository->createQueryBuilder('o'); } private function addTranslations(QueryBuilder $queryBuilder, string $alias, ?string $locale): QueryBuilder From 3ae0175264f6fb9c1960a569b6e6e729fa6a5435 Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 16:58:30 +0200 Subject: [PATCH 07/26] [VSF2-177] Taxon translations partial fix --- .../TaxonCollectionDataProvider.php | 4 +--- src/Doctrine/Repository/TaxonRepository.php | 20 +------------------ src/Serializer/TaxonNormalizer.php | 11 +++++++--- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index e3d31d9e..59074eaf 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -70,9 +70,7 @@ public function getCollection(string $resourceClass, string $operationName = nul return $this->taxonRepository->findAll(); } - $queryBuilder = $this->taxonRepository->createChildrenByChannelMenuTaxonQueryBuilder( - $channelMenuTaxon, - ); + $queryBuilder = $this->taxonRepository->createChildrenByChannelMenuTaxonQueryBuilder($channelMenuTaxon); /** @var QueryCollectionExtensionInterface $extension */ foreach ($this->collectionExtensions as $extension) { diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index 892227c5..afe64d98 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -32,8 +32,7 @@ public function createChildrenByChannelMenuTaxonQueryBuilder( $qb = $this->childrenQueryBuilder($menuTaxon, false, 'position', 'asc'); $alias = $qb->getRootAliases()[0] ?? 'node'; - return $this->addTranslations($qb, $alias, $locale) - ->andWhere($alias . '.enabled = :enabled') + return $qb->andWhere($alias . '.enabled = :enabled') ->setParameter('enabled', true); } @@ -76,21 +75,4 @@ public function createListQueryBuilder(): QueryBuilder { return $this->decoratedRepository->createQueryBuilder('o'); } - - private function addTranslations(QueryBuilder $queryBuilder, string $alias, ?string $locale): QueryBuilder - { - $queryBuilder - ->addSelect('translation') - ->leftJoin($alias . '.translations', 'translation') - ; - - if (null !== $locale) { - $queryBuilder - ->andWhere('translation.locale = :locale') - ->setParameter('locale', $locale) - ; - } - - return $queryBuilder; - } } diff --git a/src/Serializer/TaxonNormalizer.php b/src/Serializer/TaxonNormalizer.php index 77beaf61..331a0a95 100644 --- a/src/Serializer/TaxonNormalizer.php +++ b/src/Serializer/TaxonNormalizer.php @@ -10,6 +10,7 @@ namespace BitBag\SyliusVueStorefront2Plugin\Serializer; +use Sylius\Bundle\ApiBundle\Serializer\ContextKeys; use Sylius\Component\Core\Model\TaxonInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Webmozart\Assert\Assert; @@ -20,13 +21,17 @@ public function normalize($object, $format = null, array $context = []): array { Assert::isInstanceOf($object, TaxonInterface::class); + Assert::keyExists($context, ContextKeys::LOCALE_CODE); + $locale = $context[ContextKeys::LOCALE_CODE]; + $translation = $object->getTranslation($locale); + return [ 'id' => $object->getId(), - 'name' => $object->getName(), + 'name' => $translation->getName(), 'code' => $object->getCode(), 'position' => $object->getPosition(), - 'slug' => $object->getSlug(), - 'description' => $object->getDescription(), + 'slug' => $translation->getSlug(), + 'description' => $translation->getDescription(), 'parent' => $object->getParent() ? ['id' => $object->getParent()->getId()] : null, From 9008cecd4d598fbc76669ab2ac6ce077aaaa688b Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 17:15:14 +0200 Subject: [PATCH 08/26] [VSF2-177] PHPStan fixes --- src/Doctrine/Repository/TaxonRepository.php | 2 +- src/Doctrine/Repository/TaxonRepositoryInterface.php | 3 ++- src/Serializer/TaxonNormalizer.php | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index afe64d98..d714d7eb 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -16,7 +16,7 @@ use Sylius\Component\Taxonomy\Model\TaxonInterface; use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface as BaseTaxonRepositoryInterface; -final class TaxonRepository extends NestedTreeRepository implements TaxonRepositoryInterface, BaseTaxonRepositoryInterface +final class TaxonRepository extends NestedTreeRepository implements TaxonRepositoryInterface { use ResourceRepositoryTrait; diff --git a/src/Doctrine/Repository/TaxonRepositoryInterface.php b/src/Doctrine/Repository/TaxonRepositoryInterface.php index 195b861b..7f2ca832 100755 --- a/src/Doctrine/Repository/TaxonRepositoryInterface.php +++ b/src/Doctrine/Repository/TaxonRepositoryInterface.php @@ -12,8 +12,9 @@ use Doctrine\ORM\QueryBuilder; use Sylius\Component\Taxonomy\Model\TaxonInterface; +use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface as BaseTaxonRepositoryInterface; -interface TaxonRepositoryInterface +interface TaxonRepositoryInterface extends BaseTaxonRepositoryInterface { public function createChildrenByChannelMenuTaxonQueryBuilder( ?TaxonInterface $menuTaxon = null, diff --git a/src/Serializer/TaxonNormalizer.php b/src/Serializer/TaxonNormalizer.php index 331a0a95..3cf4392b 100644 --- a/src/Serializer/TaxonNormalizer.php +++ b/src/Serializer/TaxonNormalizer.php @@ -32,8 +32,8 @@ public function normalize($object, $format = null, array $context = []): array 'position' => $object->getPosition(), 'slug' => $translation->getSlug(), 'description' => $translation->getDescription(), - 'parent' => $object->getParent() - ? ['id' => $object->getParent()->getId()] + 'parent' => is_object($object->getParent()) + ? ['id' => $object->getParent()?->getId()] : null, 'enabled' => $object->isEnabled(), 'level' => $object->getLevel(), From 677b20dfc8ba0ee255374ae9273474cb835329a0 Mon Sep 17 00:00:00 2001 From: kloz Date: Wed, 17 May 2023 17:27:44 +0200 Subject: [PATCH 09/26] [VSF2-177] PHPStan fixes cd --- src/Doctrine/Repository/TaxonRepository.php | 6 ++++-- src/Serializer/TaxonNormalizer.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index d714d7eb..dc9ce009 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -10,6 +10,7 @@ namespace BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Gedmo\Tree\Entity\Repository\NestedTreeRepository; use Sylius\Bundle\ResourceBundle\Doctrine\ORM\ResourceRepositoryTrait; @@ -22,7 +23,8 @@ final class TaxonRepository extends NestedTreeRepository implements TaxonReposit public function __construct(private BaseTaxonRepositoryInterface $decoratedRepository) { - parent::__construct($this->decoratedRepository->_em, $decoratedRepository->_class); + assert($decoratedRepository instanceof EntityRepository); + parent::__construct($decoratedRepository->getEntityManager(), $decoratedRepository->getClassMetadata()); } public function createChildrenByChannelMenuTaxonQueryBuilder( @@ -73,6 +75,6 @@ public function findByNamePart(string $phrase, ?string $locale = null, ?int $lim public function createListQueryBuilder(): QueryBuilder { - return $this->decoratedRepository->createQueryBuilder('o'); + return $this->decoratedRepository->createListQueryBuilder(); } } diff --git a/src/Serializer/TaxonNormalizer.php b/src/Serializer/TaxonNormalizer.php index 3cf4392b..f998763f 100644 --- a/src/Serializer/TaxonNormalizer.php +++ b/src/Serializer/TaxonNormalizer.php @@ -33,7 +33,7 @@ public function normalize($object, $format = null, array $context = []): array 'slug' => $translation->getSlug(), 'description' => $translation->getDescription(), 'parent' => is_object($object->getParent()) - ? ['id' => $object->getParent()?->getId()] + ? ['id' => $object->getParent()->getId()] : null, 'enabled' => $object->isEnabled(), 'level' => $object->getLevel(), From 68f7fa7f5c22b22c0c46093c5afdbb0385173837 Mon Sep 17 00:00:00 2001 From: kloz Date: Thu, 18 May 2023 11:49:52 +0200 Subject: [PATCH 10/26] [VSF2-177] Taxon translations added --- src/Doctrine/Repository/TaxonRepository.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index dc9ce009..ff6c35c8 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -34,6 +34,8 @@ public function createChildrenByChannelMenuTaxonQueryBuilder( $qb = $this->childrenQueryBuilder($menuTaxon, false, 'position', 'asc'); $alias = $qb->getRootAliases()[0] ?? 'node'; + $this->addTranslations($qb, $alias, $locale); + return $qb->andWhere($alias . '.enabled = :enabled') ->setParameter('enabled', true); } @@ -77,4 +79,21 @@ public function createListQueryBuilder(): QueryBuilder { return $this->decoratedRepository->createListQueryBuilder(); } + + private function addTranslations(QueryBuilder $queryBuilder, string $alias, ?string $locale): QueryBuilder + { + $queryBuilder + ->addSelect('translation') + ->leftJoin($alias . '.translations', 'translation') + ; + + if (null !== $locale) { + $queryBuilder + ->andWhere('translation.locale = :locale') + ->setParameter('locale', $locale) + ; + } + + return $queryBuilder; + } } From 07d16ad533c7a31e23e12c2da788ba3cb0d35645 Mon Sep 17 00:00:00 2001 From: kloz Date: Thu, 18 May 2023 12:39:17 +0200 Subject: [PATCH 11/26] [VSF2-177] Taxon normalizer test --- spec/Serializer/TaxonNormalizerSpec.php | 85 +++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 spec/Serializer/TaxonNormalizerSpec.php diff --git a/spec/Serializer/TaxonNormalizerSpec.php b/spec/Serializer/TaxonNormalizerSpec.php new file mode 100644 index 00000000..6002db0b --- /dev/null +++ b/spec/Serializer/TaxonNormalizerSpec.php @@ -0,0 +1,85 @@ +shouldHaveType(TaxonNormalizer::class); + } + + public function it_normalizes_taxon( + TaxonInterface $taxon, + TaxonInterface $taxonParent, + TaxonTranslationInterface $translation, + ): void { + $id = 111; + $parentId = 100; + $name = 'Test taxon name'; + $code = 'test_taxon_code'; + $position = 1; + $slug = 'test_taxon_slug'; + $description = 'Test taxon description'; + $enabled = true; + $level = 1; + + $context = [ + ContextKeys::LOCALE_CODE => 'en_US', + ]; + + $locale = $context[ContextKeys::LOCALE_CODE]; + $taxon->getTranslation($locale)->willReturn($translation); + + $taxon->getId()->willReturn($id); + $translation->getName()->willReturn($name); + $taxon->getName()->willReturn($name); + $taxon->getCode()->willReturn($code); + $taxon->getPosition()->willReturn($position); + $translation->getSlug()->willReturn($slug); + $taxon->getSlug()->willReturn($slug); + $translation->getDescription()->willReturn($description); + $taxon->getDescription()->willReturn($description); + $taxon->getParent()->willReturn($taxonParent); + $taxon->isEnabled()->willReturn($enabled); + $taxon->getLevel()->willReturn($level); + $taxon->getTranslation()->willReturn($translation); + $taxonParent->getId()->willReturn($parentId); + + $result = [ + 'id' => $id, + 'name' => $name, + 'code' => $code, + 'position' => $position, + 'slug' => $slug, + 'description' => $description, + 'parent' => [ + 'id' => $parentId, + ], + 'enabled' => $enabled, + 'level' => $level, + ]; + + $this->normalize($taxon, null, $context)->shouldReturn($result); + } + + public function it_checks_if_it_supports_normalization(Taxon $taxon): void + { + $this->supportsNormalization($taxon)->shouldReturn(true); + } +} From d999eb1d886e8d1b063726ca8d239a8341736474 Mon Sep 17 00:00:00 2001 From: kloz Date: Tue, 23 May 2023 17:48:48 +0200 Subject: [PATCH 12/26] [VSF2-177] Repository test --- .../Repository/AbstractRepositoryTest.php | 43 +++++++++++++ .../Repository/TaxonRepositoryTest.php | 60 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php create mode 100644 tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php diff --git a/tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php b/tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php new file mode 100644 index 00000000..953e2f1d --- /dev/null +++ b/tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php @@ -0,0 +1,43 @@ +getService('doctrine.orm.default_entity_manager'); + $this->entityManager = $em; + + $this->purgeDatabase(); + } + + protected function purgeDatabase(): void + { + $purger = new ORMPurger($this->entityManager); + $purger->purge(); + } + + protected function getService(string $id): ?object + { + return self::$kernel->getContainer() + ->get($id); + } +} diff --git a/tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php b/tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php new file mode 100644 index 00000000..3ed59305 --- /dev/null +++ b/tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php @@ -0,0 +1,60 @@ +createTaxon(); + $childTaxon1 = $this->createTaxon($mainTaxon); + $childTaxon11 = $this->createTaxon($childTaxon1); + $childTaxon111 = $this->createTaxon($childTaxon11); + + $this->entityManager->flush(); + + # Test + /** @var TaxonRepository $repository */ + $repository = $this->getService('sylius.repository.taxon'); + $qb = $repository->createChildrenByChannelMenuTaxonQueryBuilder($mainTaxon); + + $this->assertEquals(3, count($qb->getQuery()->getResult())); + } + + private function createTaxon(?TaxonInterface $parent = null): TaxonInterface + { + $faker = Factory::create(); + + $taxon = $this->getService('sylius.factory.taxon')->createNew(); + $taxon->setCode(strtoupper($faker->words(2, true))); + $taxon->setposition(0); + if ($parent) { + $taxon->setParent($parent); + } + + $translation = new TaxonTranslation(); + $translation->setName(ucfirst($faker->words(2, true))); + $translation->setSlug($faker->slug); + $translation->setLocale('en_US'); + + $taxon->addTranslation($translation); + + $this->entityManager->persist($taxon); + + return $taxon; + } +} From 130f8959b9e4f8998b29389cb78e87e1b7f5370c Mon Sep 17 00:00:00 2001 From: kloz Date: Fri, 26 May 2023 16:39:31 +0200 Subject: [PATCH 13/26] [VSF2-177] Repository test update --- .../TaxonCollectionDataProvider.php | 2 +- src/Doctrine/Repository/TaxonRepository.php | 3 +- .../test_taxon_collection.yml | 25 ++++++++ .../Repository/AbstractRepositoryTest.php | 43 ------------- .../Repository/TaxonRepositoryTest.php | 60 ------------------- .../Doctrine/TaxonRepositoryTest.php | 51 ++++++++++++++++ 6 files changed, 79 insertions(+), 105 deletions(-) create mode 100644 tests/Integration/DataFixtures/ORM/TaxonRepositoryTest/test_taxon_collection.yml delete mode 100644 tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php delete mode 100644 tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php create mode 100644 tests/Integration/Doctrine/TaxonRepositoryTest.php diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index 59074eaf..0637a593 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -67,7 +67,7 @@ public function getCollection(string $resourceClass, string $operationName = nul $user = $this->userContext->getUser(); if ($this->hasAccessToAllTaxa($user)) { - return $this->taxonRepository->findAll(); + $channelMenuTaxon = null; # main taxon will be assumed } $queryBuilder = $this->taxonRepository->createChildrenByChannelMenuTaxonQueryBuilder($channelMenuTaxon); diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index ff6c35c8..57aa82f3 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -31,7 +31,8 @@ public function createChildrenByChannelMenuTaxonQueryBuilder( ?TaxonInterface $menuTaxon = null, ?string $locale = null, ): QueryBuilder { - $qb = $this->childrenQueryBuilder($menuTaxon, false, 'position', 'asc'); + $qb = $this->childrenQueryBuilder($menuTaxon, false, null, null, true); + $alias = $qb->getRootAliases()[0] ?? 'node'; $this->addTranslations($qb, $alias, $locale); diff --git a/tests/Integration/DataFixtures/ORM/TaxonRepositoryTest/test_taxon_collection.yml b/tests/Integration/DataFixtures/ORM/TaxonRepositoryTest/test_taxon_collection.yml new file mode 100644 index 00000000..1a59b046 --- /dev/null +++ b/tests/Integration/DataFixtures/ORM/TaxonRepositoryTest/test_taxon_collection.yml @@ -0,0 +1,25 @@ +Sylius\Component\Core\Model\Taxon: + main: + parent: null + code: "MAIN" + left: 1 + right: 8 + enabled: true + child1: + parent: "@main" + code: "CHILD1" + left: 2 + right: 5 + enabled: true + child11: + parent: "@child1" + code: "CHILD11" + left: 3 + right: 4 + enabled: true + child2: + parent: "@main" + code: "CHILD2" + left: 6 + right: 7 + enabled: true diff --git a/tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php b/tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php deleted file mode 100644 index 953e2f1d..00000000 --- a/tests/Integration/Doctrine/Repository/AbstractRepositoryTest.php +++ /dev/null @@ -1,43 +0,0 @@ -getService('doctrine.orm.default_entity_manager'); - $this->entityManager = $em; - - $this->purgeDatabase(); - } - - protected function purgeDatabase(): void - { - $purger = new ORMPurger($this->entityManager); - $purger->purge(); - } - - protected function getService(string $id): ?object - { - return self::$kernel->getContainer() - ->get($id); - } -} diff --git a/tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php b/tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php deleted file mode 100644 index 3ed59305..00000000 --- a/tests/Integration/Doctrine/Repository/TaxonRepositoryTest.php +++ /dev/null @@ -1,60 +0,0 @@ -createTaxon(); - $childTaxon1 = $this->createTaxon($mainTaxon); - $childTaxon11 = $this->createTaxon($childTaxon1); - $childTaxon111 = $this->createTaxon($childTaxon11); - - $this->entityManager->flush(); - - # Test - /** @var TaxonRepository $repository */ - $repository = $this->getService('sylius.repository.taxon'); - $qb = $repository->createChildrenByChannelMenuTaxonQueryBuilder($mainTaxon); - - $this->assertEquals(3, count($qb->getQuery()->getResult())); - } - - private function createTaxon(?TaxonInterface $parent = null): TaxonInterface - { - $faker = Factory::create(); - - $taxon = $this->getService('sylius.factory.taxon')->createNew(); - $taxon->setCode(strtoupper($faker->words(2, true))); - $taxon->setposition(0); - if ($parent) { - $taxon->setParent($parent); - } - - $translation = new TaxonTranslation(); - $translation->setName(ucfirst($faker->words(2, true))); - $translation->setSlug($faker->slug); - $translation->setLocale('en_US'); - - $taxon->addTranslation($translation); - - $this->entityManager->persist($taxon); - - return $taxon; - } -} diff --git a/tests/Integration/Doctrine/TaxonRepositoryTest.php b/tests/Integration/Doctrine/TaxonRepositoryTest.php new file mode 100644 index 00000000..431aa314 --- /dev/null +++ b/tests/Integration/Doctrine/TaxonRepositoryTest.php @@ -0,0 +1,51 @@ +loadFixturesFromFile('TaxonRepositoryTest/test_taxon_collection.yml'); + + /** @var TaxonRepository $repository */ + $repository = $this->getContainer()->get('sylius.repository.taxon'); + $mainTaxon = $repository->findBy(['parent' => null])[0]; + + $qb = $repository->createChildrenByChannelMenuTaxonQueryBuilder($mainTaxon); + $result = $qb->getQuery()->getResult(); + + $this->assertCount(4, $result); + + /** + * Assert structure: + * main + * child1 + * child11 + * child2 + */ + + $this->assertEquals(0, $result[0]->getLevel()); + $this->assertEquals(0, $result[0]->getPosition()); + + $this->assertEquals(1, $result[1]->getLevel()); + $this->assertEquals(0, $result[1]->getPosition()); + + $this->assertEquals(2, $result[2]->getLevel()); + $this->assertEquals(0, $result[2]->getPosition()); + + $this->assertEquals(1, $result[3]->getLevel()); + $this->assertEquals(1, $result[3]->getPosition()); + } +} From 379e38bdf7b880f92f10c214c046eb111fe2e271 Mon Sep 17 00:00:00 2001 From: kloz Date: Fri, 26 May 2023 16:58:22 +0200 Subject: [PATCH 14/26] [VSF2-177] Phpstan error fix --- src/Doctrine/Repository/TaxonRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index 57aa82f3..cb617a8c 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -31,7 +31,7 @@ public function createChildrenByChannelMenuTaxonQueryBuilder( ?TaxonInterface $menuTaxon = null, ?string $locale = null, ): QueryBuilder { - $qb = $this->childrenQueryBuilder($menuTaxon, false, null, null, true); + $qb = $this->childrenQueryBuilder($menuTaxon, false, null, 'asc', true); $alias = $qb->getRootAliases()[0] ?? 'node'; From 7e31344c01ea3b793889eebb6b9dd26f3092744e Mon Sep 17 00:00:00 2001 From: kloz Date: Fri, 26 May 2023 17:40:34 +0200 Subject: [PATCH 15/26] [VSF2-177] Phpspec error fix --- spec/DataProvider/TaxonCollectionDataProviderSpec.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/DataProvider/TaxonCollectionDataProviderSpec.php b/spec/DataProvider/TaxonCollectionDataProviderSpec.php index bd277fc2..62a971f6 100644 --- a/spec/DataProvider/TaxonCollectionDataProviderSpec.php +++ b/spec/DataProvider/TaxonCollectionDataProviderSpec.php @@ -79,7 +79,7 @@ public function it_gets_all_collection_for_user_with_api_access( $userContext->getUser()->willReturn($user); $roles = ['ROLE_API_ACCESS']; $user->getRoles()->willReturn($roles); - $taxonRepository->findAll()->shouldBeCalled(); + $taxonRepository->createChildrenByChannelMenuTaxonQueryBuilder()->shouldBeCalled(); $this->getCollection('class', 'operation', $context); } From ef31941010a01aca0f76169021a02b4a15ed8016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Kukli=C5=84ski?= Date: Mon, 29 May 2023 21:26:20 +0200 Subject: [PATCH 16/26] Add some integration scenarios for TaxonRepository class --- .../TaxonCollectionDataProviderSpec.php | 2 +- .../TaxonCollectionDataProvider.php | 2 +- src/Doctrine/Repository/TaxonRepository.php | 2 +- .../Repository/TaxonRepositoryInterface.php | 2 +- .../Doctrine/TaxonRepositoryTest.php | 56 +++++++++++++++---- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/spec/DataProvider/TaxonCollectionDataProviderSpec.php b/spec/DataProvider/TaxonCollectionDataProviderSpec.php index 62a971f6..2bf0c660 100644 --- a/spec/DataProvider/TaxonCollectionDataProviderSpec.php +++ b/spec/DataProvider/TaxonCollectionDataProviderSpec.php @@ -79,7 +79,7 @@ public function it_gets_all_collection_for_user_with_api_access( $userContext->getUser()->willReturn($user); $roles = ['ROLE_API_ACCESS']; $user->getRoles()->willReturn($roles); - $taxonRepository->createChildrenByChannelMenuTaxonQueryBuilder()->shouldBeCalled(); + $taxonRepository->createChildrenByParentQueryBuilder()->shouldBeCalled(); $this->getCollection('class', 'operation', $context); } diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index 0637a593..7a644705 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -70,7 +70,7 @@ public function getCollection(string $resourceClass, string $operationName = nul $channelMenuTaxon = null; # main taxon will be assumed } - $queryBuilder = $this->taxonRepository->createChildrenByChannelMenuTaxonQueryBuilder($channelMenuTaxon); + $queryBuilder = $this->taxonRepository->createChildrenByParentQueryBuilder($channelMenuTaxon); /** @var QueryCollectionExtensionInterface $extension */ foreach ($this->collectionExtensions as $extension) { diff --git a/src/Doctrine/Repository/TaxonRepository.php b/src/Doctrine/Repository/TaxonRepository.php index cb617a8c..3f67b83a 100755 --- a/src/Doctrine/Repository/TaxonRepository.php +++ b/src/Doctrine/Repository/TaxonRepository.php @@ -27,7 +27,7 @@ public function __construct(private BaseTaxonRepositoryInterface $decoratedRepos parent::__construct($decoratedRepository->getEntityManager(), $decoratedRepository->getClassMetadata()); } - public function createChildrenByChannelMenuTaxonQueryBuilder( + public function createChildrenByParentQueryBuilder( ?TaxonInterface $menuTaxon = null, ?string $locale = null, ): QueryBuilder { diff --git a/src/Doctrine/Repository/TaxonRepositoryInterface.php b/src/Doctrine/Repository/TaxonRepositoryInterface.php index 7f2ca832..0f2faef1 100755 --- a/src/Doctrine/Repository/TaxonRepositoryInterface.php +++ b/src/Doctrine/Repository/TaxonRepositoryInterface.php @@ -16,7 +16,7 @@ interface TaxonRepositoryInterface extends BaseTaxonRepositoryInterface { - public function createChildrenByChannelMenuTaxonQueryBuilder( + public function createChildrenByParentQueryBuilder( ?TaxonInterface $menuTaxon = null, ?string $locale = null, ): QueryBuilder; diff --git a/tests/Integration/Doctrine/TaxonRepositoryTest.php b/tests/Integration/Doctrine/TaxonRepositoryTest.php index 431aa314..5226d57f 100644 --- a/tests/Integration/Doctrine/TaxonRepositoryTest.php +++ b/tests/Integration/Doctrine/TaxonRepositoryTest.php @@ -15,7 +15,7 @@ final class TaxonRepositoryTest extends JsonApiTestCase { - public function testCreatesChildrenByChannelMenuTaxonQueryBuilder(): void + public function test_creating_children_by_parent_taxon(): void { $this->loadFixturesFromFile('TaxonRepositoryTest/test_taxon_collection.yml'); @@ -23,29 +23,65 @@ public function testCreatesChildrenByChannelMenuTaxonQueryBuilder(): void $repository = $this->getContainer()->get('sylius.repository.taxon'); $mainTaxon = $repository->findBy(['parent' => null])[0]; - $qb = $repository->createChildrenByChannelMenuTaxonQueryBuilder($mainTaxon); + $qb = $repository->createChildrenByParentQueryBuilder($mainTaxon); $result = $qb->getQuery()->getResult(); $this->assertCount(4, $result); - /** - * Assert structure: - * main - * child1 - * child11 - * child2 - */ - + $this->assertEquals('MAIN', $result[0]->getCode()); $this->assertEquals(0, $result[0]->getLevel()); $this->assertEquals(0, $result[0]->getPosition()); + $this->assertEquals('CHILD1', $result[1]->getCode()); $this->assertEquals(1, $result[1]->getLevel()); $this->assertEquals(0, $result[1]->getPosition()); + $this->assertEquals('CHILD11', $result[2]->getCode()); $this->assertEquals(2, $result[2]->getLevel()); $this->assertEquals(0, $result[2]->getPosition()); + $this->assertEquals('CHILD2', $result[3]->getCode()); $this->assertEquals(1, $result[3]->getLevel()); $this->assertEquals(1, $result[3]->getPosition()); } + + public function test_getting_smaller_tree_on_middle_positioned_taxon(): void + { + $this->loadFixturesFromFile('TaxonRepositoryTest/test_taxon_collection.yml'); + + /** @var TaxonRepository $repository */ + $repository = $this->getContainer()->get('sylius.repository.taxon'); + $mainTaxon = $repository->findBy(['code' => 'CHILD1'])[0]; + + $qb = $repository->createChildrenByParentQueryBuilder($mainTaxon); + $result = $qb->getQuery()->getResult(); + + $this->assertCount(2, $result); + + $this->assertEquals('CHILD1', $result[0]->getCode()); + $this->assertEquals(1, $result[0]->getLevel()); + $this->assertEquals(0, $result[0]->getPosition()); + + $this->assertEquals('CHILD11', $result[1]->getCode()); + $this->assertEquals(2, $result[1]->getLevel()); + $this->assertEquals(0, $result[1]->getPosition()); + } + + public function test_getting_no_children_for_leaf_taxon(): void + { + $this->loadFixturesFromFile('TaxonRepositoryTest/test_taxon_collection.yml'); + + /** @var TaxonRepository $repository */ + $repository = $this->getContainer()->get('sylius.repository.taxon'); + $mainTaxon = $repository->findBy(['code' => 'CHILD2'])[0]; + + $qb = $repository->createChildrenByParentQueryBuilder($mainTaxon); + $result = $qb->getQuery()->getResult(); + + $this->assertCount(1, $result); + + $this->assertEquals('CHILD2', $result[0]->getCode()); + $this->assertEquals(1, $result[0]->getLevel()); + $this->assertEquals(1, $result[0]->getPosition()); + } } From 8f282fde1b96b2df41013bb1f15729b7acfcac95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Kukli=C5=84ski?= Date: Mon, 29 May 2023 21:57:58 +0200 Subject: [PATCH 17/26] Remove problematic spec due to final / non-interfaced ApiPlatform dependencies --- .../TaxonCollectionDataProviderSpec.php | 86 ------------------- .../TaxonCollectionDataProvider.php | 2 +- 2 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 spec/DataProvider/TaxonCollectionDataProviderSpec.php diff --git a/spec/DataProvider/TaxonCollectionDataProviderSpec.php b/spec/DataProvider/TaxonCollectionDataProviderSpec.php deleted file mode 100644 index 2bf0c660..00000000 --- a/spec/DataProvider/TaxonCollectionDataProviderSpec.php +++ /dev/null @@ -1,86 +0,0 @@ -getWrappedObject()); - $paginationExtension = new PaginationExtension( - $managerRegistry->getWrappedObject(), - $resourceMetadataFactory->getWrappedObject(), - $pagination, - ); - $collectionExtensions = [ - $queryResultCollectionExtension->getWrappedObject(), - ]; - $this->beConstructedWith( - $taxonRepository, - $paginationExtension, - $userContext, - $queryNameGenerator, - $collectionExtensions, - ); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(TaxonCollectionDataProvider::class); - } - - public function it_checks_if_supports(TaxonInterface $taxon): void - { - $this->supports(get_class($taxon->getWrappedObject()))->shouldReturn(true); - } - - public function it_gets_all_collection_for_user_with_api_access( - ChannelInterface $channel, - TaxonInterface $channelMenuTaxon, - UserContextInterface $userContext, - ShopUserInterface $user, - TaxonRepositoryInterface $taxonRepository, - ) { - $context = [ - ContextKeys::CHANNEL => $channel, - ]; - $channelContext = $context[ContextKeys::CHANNEL]; - $channelContext->getMenuTaxon()->willReturn($channelMenuTaxon); - - $userContext->getUser()->willReturn($user); - $roles = ['ROLE_API_ACCESS']; - $user->getRoles()->willReturn($roles); - $taxonRepository->createChildrenByParentQueryBuilder()->shouldBeCalled(); - - $this->getCollection('class', 'operation', $context); - } -} diff --git a/src/DataProvider/TaxonCollectionDataProvider.php b/src/DataProvider/TaxonCollectionDataProvider.php index 7a644705..30ad3ad3 100644 --- a/src/DataProvider/TaxonCollectionDataProvider.php +++ b/src/DataProvider/TaxonCollectionDataProvider.php @@ -67,7 +67,7 @@ public function getCollection(string $resourceClass, string $operationName = nul $user = $this->userContext->getUser(); if ($this->hasAccessToAllTaxa($user)) { - $channelMenuTaxon = null; # main taxon will be assumed + $channelMenuTaxon = null; } $queryBuilder = $this->taxonRepository->createChildrenByParentQueryBuilder($channelMenuTaxon); From d0d520d18b3b4c0841399ad2ef2e59b42e5813e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Fri, 2 Jun 2023 08:51:38 +0200 Subject: [PATCH 18/26] VSF2-203 Remember me functionality fix --- spec/Factory/ShopUserTokenFactorySpec.php | 2 +- spec/Resolver/Mutation/LoginResolverSpec.php | 8 ++++++-- src/Factory/ShopUserTokenFactory.php | 17 ++++++++++++++--- src/Factory/ShopUserTokenFactoryInterface.php | 2 +- src/Resolver/Mutation/LoginResolver.php | 3 ++- src/Resources/services/factories.xml | 2 ++ tests/Application/.env | 2 ++ 7 files changed, 28 insertions(+), 8 deletions(-) diff --git a/spec/Factory/ShopUserTokenFactorySpec.php b/spec/Factory/ShopUserTokenFactorySpec.php index 4c9703de..45d76871 100644 --- a/spec/Factory/ShopUserTokenFactorySpec.php +++ b/spec/Factory/ShopUserTokenFactorySpec.php @@ -27,7 +27,7 @@ public function let( JWTTokenManagerInterface $jwtManager, RefreshTokenManagerInterface $refreshJwtManager, ): void { - $this->beConstructedWith($entityManager, $jwtManager, $refreshJwtManager); + $this->beConstructedWith($entityManager, $jwtManager, $refreshJwtManager, '+5 second', '+3 month'); } public function it_is_initializable(): void diff --git a/spec/Resolver/Mutation/LoginResolverSpec.php b/spec/Resolver/Mutation/LoginResolverSpec.php index 3ce4a594..843e9d17 100644 --- a/spec/Resolver/Mutation/LoginResolverSpec.php +++ b/spec/Resolver/Mutation/LoginResolverSpec.php @@ -70,6 +70,7 @@ public function it_is_invokable( 'input' => [ 'username' => 'username', 'password' => 'somepass', + 'rememberMe' => true, ], ], ]; @@ -77,6 +78,7 @@ public function it_is_invokable( $input = $context['args']['input']; $username = (string) $input['username']; $password = (string) $input['password']; + $rememberMe = (bool) $input['rememberMe']; $userRepository->findOneBy(['username' => $username])->willReturn($user); $encoderFactory->getEncoder($user)->willReturn($encoder); @@ -93,7 +95,7 @@ public function it_is_invokable( $encoder->isPasswordValid($userPassword, $password, $userSalt)->shouldBeCalled()->willReturn(true); - $tokenFactory->getRefreshToken($user)->willReturn($refreshToken); + $tokenFactory->getRefreshToken($user, $rememberMe)->willReturn($refreshToken); $tokenFactory->create($user, $refreshToken)->willReturn($shopUserToken); $eventDispatcher->dispatch(Argument::any(), LoginResolver::EVENT_NAME)->shouldBeCalled(); @@ -223,6 +225,7 @@ public function it_doesnt_throw_exception_when_logging_in_unverified_user_but_ch 'input' => [ 'username' => 'username', 'password' => 'somepass', + 'rememberMe' => true, ], ], ]; @@ -230,6 +233,7 @@ public function it_doesnt_throw_exception_when_logging_in_unverified_user_but_ch $input = $context['args']['input']; $username = (string) $input['username']; $password = (string) $input['password']; + $rememberMe = (bool) $input['rememberMe']; $userRepository->findOneBy(['username' => $username])->willReturn($user); $encoderFactory->getEncoder($user)->willReturn($encoder); @@ -246,7 +250,7 @@ public function it_doesnt_throw_exception_when_logging_in_unverified_user_but_ch $encoder->isPasswordValid($userPassword, $password, $userSalt)->shouldBeCalled()->willReturn(true); - $tokenFactory->getRefreshToken($user)->willReturn($refreshToken); + $tokenFactory->getRefreshToken($user, $rememberMe)->willReturn($refreshToken); $tokenFactory->create($user, $refreshToken)->willReturn($shopUserToken); $eventDispatcher->dispatch(Argument::any(), LoginResolver::EVENT_NAME)->shouldBeCalled(); diff --git a/src/Factory/ShopUserTokenFactory.php b/src/Factory/ShopUserTokenFactory.php index 3aea39cd..b91a41f9 100644 --- a/src/Factory/ShopUserTokenFactory.php +++ b/src/Factory/ShopUserTokenFactory.php @@ -26,14 +26,22 @@ class ShopUserTokenFactory implements ShopUserTokenFactoryInterface private EntityManagerInterface $entityManager; + private string $refreshTokenTTL; + + private string $refreshTokenExtendedTTL; + public function __construct( EntityManagerInterface $entityManager, JWTTokenManagerInterface $jwtManager, RefreshTokenManagerInterface $refreshJwtManager, + string $refreshTokenTTL, + string $refreshTokenExtendedTTL, ) { $this->entityManager = $entityManager; $this->jwtManager = $jwtManager; $this->refreshJwtManager = $refreshJwtManager; + $this->refreshTokenTTL = $refreshTokenTTL; + $this->refreshTokenExtendedTTL = $refreshTokenExtendedTTL; } public function create( @@ -50,9 +58,12 @@ public function create( return $shopUserToken; } - public function getRefreshToken(ShopUserInterface $user): RefreshTokenInterface - { - $refreshTokenExpirationDate = new \DateTime('+1 month'); + public function getRefreshToken( + ShopUserInterface $user, + bool $rememberMe = null + ): RefreshTokenInterface { + + $refreshTokenExpirationDate = new \DateTime($rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); $refreshToken = $this->refreshJwtManager->create(); $refreshToken->setRefreshToken(); $refreshToken->setUsername((string) $user->getUsernameCanonical()); diff --git a/src/Factory/ShopUserTokenFactoryInterface.php b/src/Factory/ShopUserTokenFactoryInterface.php index ade7a1d5..4aec49c4 100644 --- a/src/Factory/ShopUserTokenFactoryInterface.php +++ b/src/Factory/ShopUserTokenFactoryInterface.php @@ -18,5 +18,5 @@ interface ShopUserTokenFactoryInterface { public function create(ShopUserInterface $user, RefreshTokenInterface $refreshToken): ShopUserTokenInterface; - public function getRefreshToken(ShopUserInterface $user): RefreshTokenInterface; + public function getRefreshToken(ShopUserInterface $user, bool $rememberMe = null): RefreshTokenInterface; } diff --git a/src/Resolver/Mutation/LoginResolver.php b/src/Resolver/Mutation/LoginResolver.php index 5e07965b..5a3e192a 100644 --- a/src/Resolver/Mutation/LoginResolver.php +++ b/src/Resolver/Mutation/LoginResolver.php @@ -79,6 +79,7 @@ public function __invoke($item, array $context): ?ShopUserTokenInterface $username = (string) $input['username']; $password = (string) $input['password']; + $rememberMe = (bool) $input['rememberMe']; /** @var ShopUserInterface|null $user */ $user = $this->userRepository->findOneBy(['username' => $username]); @@ -99,7 +100,7 @@ public function __invoke($item, array $context): ?ShopUserTokenInterface throw new \Exception('User verification required.'); } if ($encoder->isPasswordValid($userPassword, $password, $userSalt)) { - $refreshToken = $this->tokenFactory->getRefreshToken($user); + $refreshToken = $this->tokenFactory->getRefreshToken($user, $rememberMe); $shopUserToken = $this->tokenFactory->create($user, $refreshToken); $this->applyOrder($input, $user); diff --git a/src/Resources/services/factories.xml b/src/Resources/services/factories.xml index 86af2a24..8948b9b9 100644 --- a/src/Resources/services/factories.xml +++ b/src/Resources/services/factories.xml @@ -18,6 +18,8 @@ We are hiring developers from all over the world. Join us and start your new, ex + %env(APP_REFRESH_TOKEN_TTL)% + %env(APP_REFRESH_TOKEN_EXTENDED_TTL)% diff --git a/tests/Application/.env b/tests/Application/.env index a95d74c5..371a20d7 100644 --- a/tests/Application/.env +++ b/tests/Application/.env @@ -6,6 +6,8 @@ APP_ENV=dev APP_DEBUG=1 APP_SECRET=EDITME +APP_REFRESH_TOKEN_TTL="+5 second" +APP_REFRESH_TOKEN_EXTENDED_TTL="+3 month" ###< symfony/framework-bundle ### ###> doctrine/doctrine-bundle ### From e08204fe002181eed2e6f17724ee5d8fd2c01c8c Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Fri, 2 Jun 2023 09:04:49 +0200 Subject: [PATCH 19/26] VSF2-203 Fix wrong type definitions indicated by phpstan --- src/Factory/ShopUserTokenFactory.php | 2 +- src/Factory/ShopUserTokenFactoryInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Factory/ShopUserTokenFactory.php b/src/Factory/ShopUserTokenFactory.php index b91a41f9..983a3910 100644 --- a/src/Factory/ShopUserTokenFactory.php +++ b/src/Factory/ShopUserTokenFactory.php @@ -60,7 +60,7 @@ public function create( public function getRefreshToken( ShopUserInterface $user, - bool $rememberMe = null + ?bool $rememberMe = null ): RefreshTokenInterface { $refreshTokenExpirationDate = new \DateTime($rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); diff --git a/src/Factory/ShopUserTokenFactoryInterface.php b/src/Factory/ShopUserTokenFactoryInterface.php index 4aec49c4..72d04b5e 100644 --- a/src/Factory/ShopUserTokenFactoryInterface.php +++ b/src/Factory/ShopUserTokenFactoryInterface.php @@ -18,5 +18,5 @@ interface ShopUserTokenFactoryInterface { public function create(ShopUserInterface $user, RefreshTokenInterface $refreshToken): ShopUserTokenInterface; - public function getRefreshToken(ShopUserInterface $user, bool $rememberMe = null): RefreshTokenInterface; + public function getRefreshToken(ShopUserInterface $user, ?bool $rememberMe = null): RefreshTokenInterface; } From eff202cd04f2e318ef9f3414b2387500dbc86ae7 Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Fri, 2 Jun 2023 10:29:22 +0200 Subject: [PATCH 20/26] VSF2-203 Fix wrong ternary operator condition indicated by phpstan --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index de9ba0cf..bb3c7fce 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ ], "license": "MIT", "require": { - "php": "^8.0", + "php": "8.0", "ext-json": "*", "ext-openssl": "*", "bitbag/wishlist-plugin": "^3.0", From 7283f97263abb92c19c4a3764a36a85ab1cca0db Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Fri, 2 Jun 2023 10:31:42 +0200 Subject: [PATCH 21/26] VSF2-203 Fix wrong ternary operator condition indicated by phpstan and bring back composer.json --- composer.json | 2 +- src/Factory/ShopUserTokenFactory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index bb3c7fce..de9ba0cf 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ ], "license": "MIT", "require": { - "php": "8.0", + "php": "^8.0", "ext-json": "*", "ext-openssl": "*", "bitbag/wishlist-plugin": "^3.0", diff --git a/src/Factory/ShopUserTokenFactory.php b/src/Factory/ShopUserTokenFactory.php index 983a3910..9cc018c3 100644 --- a/src/Factory/ShopUserTokenFactory.php +++ b/src/Factory/ShopUserTokenFactory.php @@ -63,7 +63,7 @@ public function getRefreshToken( ?bool $rememberMe = null ): RefreshTokenInterface { - $refreshTokenExpirationDate = new \DateTime($rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); + $refreshTokenExpirationDate = new \DateTime(null !== $rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); $refreshToken = $this->refreshJwtManager->create(); $refreshToken->setRefreshToken(); $refreshToken->setUsername((string) $user->getUsernameCanonical()); From 3616b4c0a51d8e9fdc1f3c8d7f01eafbc93cd133 Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Fri, 2 Jun 2023 11:58:57 +0200 Subject: [PATCH 22/26] VSF2-203 Add missing rememberMe attribute to api resource and fix behat login context --- src/Resources/api_resources/ShopUserToken.xml | 3 +++ tests/Behat/Context/Shop/LoginContext.php | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Resources/api_resources/ShopUserToken.xml b/src/Resources/api_resources/ShopUserToken.xml index de0f503a..8b0c9c1e 100644 --- a/src/Resources/api_resources/ShopUserToken.xml +++ b/src/Resources/api_resources/ShopUserToken.xml @@ -22,6 +22,9 @@ We are hiring developers from all over the world. Join us and start your new, ex String! + + Boolean + String diff --git a/tests/Behat/Context/Shop/LoginContext.php b/tests/Behat/Context/Shop/LoginContext.php index a1598283..1254b2c6 100644 --- a/tests/Behat/Context/Shop/LoginContext.php +++ b/tests/Behat/Context/Shop/LoginContext.php @@ -60,6 +60,7 @@ public function iPrepareLoginOperation(string $email, string $password): void $operation->setVariables([ 'username' => $email, 'password' => $password, + 'rememberMe' => true, ]); $this->sharedStorage->set(GraphqlClient::GRAPHQL_OPERATION, $operation); } From cbe9bf5381cc17541d2c2f9a2e6d9ed905277d50 Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Fri, 2 Jun 2023 14:12:05 +0200 Subject: [PATCH 23/26] VSF2-203 Fix wrong ternary operator condition and rememberMe type as graphql attribute --- src/Factory/ShopUserTokenFactory.php | 2 +- src/Resources/api_resources/ShopUserToken.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Factory/ShopUserTokenFactory.php b/src/Factory/ShopUserTokenFactory.php index 9cc018c3..25754cff 100644 --- a/src/Factory/ShopUserTokenFactory.php +++ b/src/Factory/ShopUserTokenFactory.php @@ -63,7 +63,7 @@ public function getRefreshToken( ?bool $rememberMe = null ): RefreshTokenInterface { - $refreshTokenExpirationDate = new \DateTime(null !== $rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); + $refreshTokenExpirationDate = new \DateTime(true === $rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); $refreshToken = $this->refreshJwtManager->create(); $refreshToken->setRefreshToken(); $refreshToken->setUsername((string) $user->getUsernameCanonical()); diff --git a/src/Resources/api_resources/ShopUserToken.xml b/src/Resources/api_resources/ShopUserToken.xml index 8b0c9c1e..ef465402 100644 --- a/src/Resources/api_resources/ShopUserToken.xml +++ b/src/Resources/api_resources/ShopUserToken.xml @@ -23,7 +23,7 @@ We are hiring developers from all over the world. Join us and start your new, ex String! - Boolean + Boolean! String From 8393d9e664a3cca76123e6b3015aac4ed99c50be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Kukli=C5=84ski?= Date: Sun, 4 Jun 2023 20:32:16 +0200 Subject: [PATCH 24/26] Update tests/Application/.env --- tests/Application/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Application/.env b/tests/Application/.env index 371a20d7..68d018fb 100644 --- a/tests/Application/.env +++ b/tests/Application/.env @@ -6,7 +6,7 @@ APP_ENV=dev APP_DEBUG=1 APP_SECRET=EDITME -APP_REFRESH_TOKEN_TTL="+5 second" +APP_REFRESH_TOKEN_TTL="+1 week" APP_REFRESH_TOKEN_EXTENDED_TTL="+3 month" ###< symfony/framework-bundle ### From 366153d7edcbeee6980b95961431cf1f7eedbbc9 Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Tue, 6 Jun 2023 08:11:01 +0200 Subject: [PATCH 25/26] VSF2-203 Add unification of refresh token ttl's handling --- doc/installation.md | 15 ++++++-- spec/Factory/ShopUserTokenFactorySpec.php | 5 +-- spec/Resolver/Mutation/LoginResolverSpec.php | 2 +- .../Mutation/RefreshTokenResolverSpec.php | 10 +++--- .../BitBagSyliusVueStorefront2Extension.php | 1 - src/DependencyInjection/Configuration.php | 1 - src/Factory/ShopUserTokenFactory.php | 7 ++-- src/Factory/ShopUserTokenFactoryInterface.php | 2 +- src/Model/RefreshToken.php | 35 +++++++++++++++++++ src/Model/RefreshTokenInterface.php | 20 +++++++++++ .../Mutation/RefreshTokenResolver.php | 17 +++++---- src/Resources/config/config.yml | 3 ++ .../doctrine/model/RefreshToken.orm.xml | 21 +++++++++++ src/Resources/services/resolvers.xml | 3 +- tests/Application/.env | 1 + .../packages/bitbag_sylius_graphql.yaml | 1 - .../packages/gesdinet_jwt_refresh_token.yaml | 2 ++ 17 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 src/Model/RefreshToken.php create mode 100644 src/Model/RefreshTokenInterface.php create mode 100644 src/Resources/doctrine/model/RefreshToken.orm.xml create mode 100644 tests/Application/config/packages/gesdinet_jwt_refresh_token.yaml diff --git a/doc/installation.md b/doc/installation.md index 854d665a..c54e0eff 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -62,7 +62,6 @@ ```yml bitbag_sylius_vue_storefront2: - refresh_token_lifespan: 2592000 #that its default value test_endpoint: 'http://127.0.0.1:8080/api/v2/graphql' #that its default value ``` ![Step6](/doc/images/Step6.png) @@ -176,15 +175,25 @@ security: ![Step11](/doc/images/Step11.png) -4Add redirection to your `.env` file: +14. Add redirection to your `.env` file: ```env VSF2_HOST= #your VueStoreFront url address ``` ![Step12](/doc/images/Step12.png) + + +15. Add JWT token and refresh token TTL's to your `.env` file: + +```env +APP_TOKEN_TTL=3600 +APP_REFRESH_TOKEN_TTL="+1 week" +APP_REFRESH_TOKEN_EXTENDED_TTL="+3 month" +``` + -14. After all steps, run this commends in your project directory: +16. After all steps, run this commends in your project directory: ```bash yarn install diff --git a/spec/Factory/ShopUserTokenFactorySpec.php b/spec/Factory/ShopUserTokenFactorySpec.php index 45d76871..812d6ca2 100644 --- a/spec/Factory/ShopUserTokenFactorySpec.php +++ b/spec/Factory/ShopUserTokenFactorySpec.php @@ -11,9 +11,9 @@ namespace spec\BitBag\SyliusVueStorefront2Plugin\Factory; use BitBag\SyliusVueStorefront2Plugin\Factory\ShopUserTokenFactory; +use BitBag\SyliusVueStorefront2Plugin\Model\RefreshTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Model\ShopUserToken; use Doctrine\ORM\EntityManagerInterface; -use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface; use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface; use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; use PhpSpec\ObjectBehavior; @@ -68,6 +68,7 @@ public function it_gets_refresh_token( $username = 'canonical'; $user->getUsernameCanonical()->willReturn($username); $refreshToken->setUsername($username)->shouldBeCalled(); + $refreshToken->setRememberMe(true)->shouldBeCalled(); $refreshToken->setRefreshToken()->shouldBeCalled(); @@ -76,6 +77,6 @@ public function it_gets_refresh_token( $entityManager->persist($refreshToken)->shouldBeCalled(); $entityManager->flush()->shouldBeCalled(); - $this->getRefreshToken($user)->shouldReturn($refreshToken); + $this->getRefreshToken($user, true)->shouldReturn($refreshToken); } } diff --git a/spec/Resolver/Mutation/LoginResolverSpec.php b/spec/Resolver/Mutation/LoginResolverSpec.php index 843e9d17..482c12eb 100644 --- a/spec/Resolver/Mutation/LoginResolverSpec.php +++ b/spec/Resolver/Mutation/LoginResolverSpec.php @@ -11,10 +11,10 @@ namespace spec\BitBag\SyliusVueStorefront2Plugin\Resolver\Mutation; use BitBag\SyliusVueStorefront2Plugin\Factory\ShopUserTokenFactoryInterface; +use BitBag\SyliusVueStorefront2Plugin\Model\RefreshTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Model\ShopUserTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Resolver\Mutation\LoginResolver; use Doctrine\ORM\EntityManagerInterface; -use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; use Sylius\Component\Channel\Context\ChannelContextInterface; diff --git a/spec/Resolver/Mutation/RefreshTokenResolverSpec.php b/spec/Resolver/Mutation/RefreshTokenResolverSpec.php index d4b93f12..bf380d2e 100644 --- a/spec/Resolver/Mutation/RefreshTokenResolverSpec.php +++ b/spec/Resolver/Mutation/RefreshTokenResolverSpec.php @@ -11,12 +11,12 @@ namespace spec\BitBag\SyliusVueStorefront2Plugin\Resolver\Mutation; use BitBag\SyliusVueStorefront2Plugin\Factory\ShopUserTokenFactoryInterface; +use BitBag\SyliusVueStorefront2Plugin\Model\RefreshToken; +use BitBag\SyliusVueStorefront2Plugin\Model\RefreshTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Model\ShopUserTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Resolver\Mutation\RefreshTokenResolver; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectRepository; -use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken; -use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; use Sylius\Component\Core\Model\ShopUserInterface; @@ -34,13 +34,15 @@ public function let( EventDispatcherInterface $eventDispatcher, ): void { $entityManager->getRepository(RefreshToken::class)->willReturn($refreshTokenRepository); - $lifespan = '2592000'; + $refreshTokenTTL = '+1 week'; + $refreshTokenExtendedTTL = '+3 month'; $this->beConstructedWith( $entityManager, $tokenFactory, $userRepository, $eventDispatcher, - $lifespan, + $refreshTokenTTL, + $refreshTokenExtendedTTL, ); } diff --git a/src/DependencyInjection/BitBagSyliusVueStorefront2Extension.php b/src/DependencyInjection/BitBagSyliusVueStorefront2Extension.php index 34300a2e..addd5144 100644 --- a/src/DependencyInjection/BitBagSyliusVueStorefront2Extension.php +++ b/src/DependencyInjection/BitBagSyliusVueStorefront2Extension.php @@ -26,7 +26,6 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load('services.xml'); - $container->setParameter('bitbag_sylius_vue_storefront2.refresh_token_lifespan', $config['refresh_token_lifespan']); $container->setParameter('bitbag_sylius_vue_storefront2.test_endpoint', $config['test_endpoint']); } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 962ac4a5..5582cc19 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -30,7 +30,6 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->addDefaultsIfNotSet() ->children() - ->integerNode('refresh_token_lifespan')->defaultValue(2592000)->end() ->scalarNode('test_endpoint')->defaultValue('http://127.0.0.1:8080/api/v2/graphql')->cannotBeEmpty()->end() ->end() ->end() diff --git a/src/Factory/ShopUserTokenFactory.php b/src/Factory/ShopUserTokenFactory.php index 25754cff..8496ab82 100644 --- a/src/Factory/ShopUserTokenFactory.php +++ b/src/Factory/ShopUserTokenFactory.php @@ -10,10 +10,10 @@ namespace BitBag\SyliusVueStorefront2Plugin\Factory; +use BitBag\SyliusVueStorefront2Plugin\Model\RefreshTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Model\ShopUserToken; use BitBag\SyliusVueStorefront2Plugin\Model\ShopUserTokenInterface; use Doctrine\ORM\EntityManagerInterface; -use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface; use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface; use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; use Sylius\Component\Core\Model\ShopUserInterface; @@ -60,14 +60,15 @@ public function create( public function getRefreshToken( ShopUserInterface $user, - ?bool $rememberMe = null + ?bool $rememberMe = null, ): RefreshTokenInterface { - $refreshTokenExpirationDate = new \DateTime(true === $rememberMe ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); + /** @var RefreshTokenInterface $refreshToken */ $refreshToken = $this->refreshJwtManager->create(); $refreshToken->setRefreshToken(); $refreshToken->setUsername((string) $user->getUsernameCanonical()); $refreshToken->setValid($refreshTokenExpirationDate); + $refreshToken->setRememberMe(true === $rememberMe); $this->entityManager->persist($refreshToken); $this->entityManager->flush(); diff --git a/src/Factory/ShopUserTokenFactoryInterface.php b/src/Factory/ShopUserTokenFactoryInterface.php index 72d04b5e..5c6f85ae 100644 --- a/src/Factory/ShopUserTokenFactoryInterface.php +++ b/src/Factory/ShopUserTokenFactoryInterface.php @@ -10,8 +10,8 @@ namespace BitBag\SyliusVueStorefront2Plugin\Factory; +use BitBag\SyliusVueStorefront2Plugin\Model\RefreshTokenInterface; use BitBag\SyliusVueStorefront2Plugin\Model\ShopUserTokenInterface; -use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface; use Sylius\Component\Core\Model\ShopUserInterface; interface ShopUserTokenFactoryInterface diff --git a/src/Model/RefreshToken.php b/src/Model/RefreshToken.php new file mode 100644 index 00000000..53a0fb02 --- /dev/null +++ b/src/Model/RefreshToken.php @@ -0,0 +1,35 @@ +id; + } + + public function isRememberMe(): bool + { + return $this->rememberMe; + } + + public function setRememberMe(bool $rememberMe): void + { + $this->rememberMe = $rememberMe; + } +} diff --git a/src/Model/RefreshTokenInterface.php b/src/Model/RefreshTokenInterface.php new file mode 100644 index 00000000..09a29f92 --- /dev/null +++ b/src/Model/RefreshTokenInterface.php @@ -0,0 +1,20 @@ + */ private ObjectRepository $refreshTokenRepository; - private string $refreshTokenLifetime; + private string $refreshTokenTTL; + + private string $refreshTokenExtendedTTL; public function __construct( EntityManagerInterface $entityManager, ShopUserTokenFactoryInterface $tokenFactory, UserRepositoryInterface $userRepository, EventDispatcherInterface $eventDispatcher, - string $refreshTokenLifetime, + string $refreshTokenTTL, + string $refreshTokenExtendedTTL, ) { $this->entityManager = $entityManager; $this->shopUserTokenFactory = $tokenFactory; $this->userRepository = $userRepository; $this->refreshTokenRepository = $entityManager->getRepository(RefreshToken::class); $this->eventDispatcher = $eventDispatcher; - $this->refreshTokenLifetime = $refreshTokenLifetime; + $this->refreshTokenTTL = $refreshTokenTTL; + $this->refreshTokenExtendedTTL = $refreshTokenExtendedTTL; } /** @@ -70,6 +74,7 @@ public function __invoke($item, array $context): ?ShopUserTokenInterface Assert::keyExists($input, 'refreshToken'); $refreshTokenString = (string) $input['refreshToken']; + /** @var RefreshTokenInterface $refreshToken */ $refreshToken = $this->refreshTokenRepository->findOneBy(['refreshToken' => $refreshTokenString]); Assert::notNull($refreshToken); @@ -78,7 +83,7 @@ public function __invoke($item, array $context): ?ShopUserTokenInterface /** @var ShopUserInterface $user */ $user = $this->userRepository->findOneBy(['username' => $refreshToken->getUsername()]); - $refreshTokenExpirationDate = new \DateTime(sprintf('+%s seconds', $this->refreshTokenLifetime)); + $refreshTokenExpirationDate = new \DateTime(true === $refreshToken->isRememberMe() ? $this->refreshTokenExtendedTTL : $this->refreshTokenTTL); $refreshToken->setValid($refreshTokenExpirationDate); $this->entityManager->flush(); diff --git a/src/Resources/config/config.yml b/src/Resources/config/config.yml index 96480329..4def0f61 100644 --- a/src/Resources/config/config.yml +++ b/src/Resources/config/config.yml @@ -23,3 +23,6 @@ sylius_fixtures: fixtures: channel_locales: priority: 0 + +lexik_jwt_authentication: + token_ttl: '%env(APP_TOKEN_TTL)%' diff --git a/src/Resources/doctrine/model/RefreshToken.orm.xml b/src/Resources/doctrine/model/RefreshToken.orm.xml new file mode 100644 index 00000000..3e26ca1d --- /dev/null +++ b/src/Resources/doctrine/model/RefreshToken.orm.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/src/Resources/services/resolvers.xml b/src/Resources/services/resolvers.xml index 661fb8c7..9f480639 100644 --- a/src/Resources/services/resolvers.xml +++ b/src/Resources/services/resolvers.xml @@ -37,7 +37,8 @@ We are hiring developers from all over the world. Join us and start your new, ex - %bitbag_sylius_vue_storefront2.refresh_token_lifespan% + %env(APP_REFRESH_TOKEN_TTL)% + %env(APP_REFRESH_TOKEN_EXTENDED_TTL)% diff --git a/tests/Application/.env b/tests/Application/.env index 68d018fb..4040c0ac 100644 --- a/tests/Application/.env +++ b/tests/Application/.env @@ -6,6 +6,7 @@ APP_ENV=dev APP_DEBUG=1 APP_SECRET=EDITME +APP_TOKEN_TTL=3600 APP_REFRESH_TOKEN_TTL="+1 week" APP_REFRESH_TOKEN_EXTENDED_TTL="+3 month" ###< symfony/framework-bundle ### diff --git a/tests/Application/config/packages/bitbag_sylius_graphql.yaml b/tests/Application/config/packages/bitbag_sylius_graphql.yaml index b2c72c09..4c83e87f 100644 --- a/tests/Application/config/packages/bitbag_sylius_graphql.yaml +++ b/tests/Application/config/packages/bitbag_sylius_graphql.yaml @@ -2,5 +2,4 @@ imports: - { resource: "@BitBagSyliusVueStorefront2Plugin/Resources/config/services.xml" } bitbag_sylius_vue_storefront2: - refresh_token_lifespan: 2592000 diff --git a/tests/Application/config/packages/gesdinet_jwt_refresh_token.yaml b/tests/Application/config/packages/gesdinet_jwt_refresh_token.yaml new file mode 100644 index 00000000..5a43e270 --- /dev/null +++ b/tests/Application/config/packages/gesdinet_jwt_refresh_token.yaml @@ -0,0 +1,2 @@ +gesdinet_jwt_refresh_token: + refresh_token_class: BitBag\SyliusVueStorefront2Plugin\Model\RefreshToken From aa61b42374af3de1c68518968847380aa5568639 Mon Sep 17 00:00:00 2001 From: Grzegorz Kozinski Date: Tue, 6 Jun 2023 09:19:48 +0200 Subject: [PATCH 26/26] VSF2-203 Fix type definition indicated by phpstan --- src/Resolver/Mutation/RefreshTokenResolver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Resolver/Mutation/RefreshTokenResolver.php b/src/Resolver/Mutation/RefreshTokenResolver.php index b0043a4f..f62714a6 100644 --- a/src/Resolver/Mutation/RefreshTokenResolver.php +++ b/src/Resolver/Mutation/RefreshTokenResolver.php @@ -74,10 +74,11 @@ public function __invoke($item, array $context): ?ShopUserTokenInterface Assert::keyExists($input, 'refreshToken'); $refreshTokenString = (string) $input['refreshToken']; - /** @var RefreshTokenInterface $refreshToken */ $refreshToken = $this->refreshTokenRepository->findOneBy(['refreshToken' => $refreshTokenString]); Assert::notNull($refreshToken); + + /** @var RefreshTokenInterface $refreshToken */ $this->validateRefreshToken($refreshToken, $refreshTokenString); /** @var ShopUserInterface $user */