[Locales] Create ADR for locales exposing

This commit is contained in:
Adam Kasperczak 2021-07-02 12:19:58 +02:00
parent d516bf1233
commit 90ceff637e
5 changed files with 55 additions and 17 deletions

View file

@ -0,0 +1,26 @@
# Using data provider for getting available locales in active channel for shop user
* Status: proposed
* Date: 2021-07-05
## Context and Problem Statement
Customer should have access only to locales available in their channel
## Considered Options
### Using Doctrine Collection extension
* Good, because consistent with actual approach to modifying responses content
* Good, because works with rest of API extension like pagination
* Bad, because locales don't have relation to channel, so using Doctrine Collection extension is extremely hard.
### Using Data Provider
* Good, because we already have this approach for older resources
* Good, because easy to implement
* Bad, because using data providers omits extra Doctrine extensions like pagination
## Decision Outcome
Chosen option: Using Data Provider
Shops shouldn't have many locales for each channel, so lack of a pagination is smaller problem than creating overcomplicated query in Doctrine Collection extension

View file

@ -1,5 +1,5 @@
@locales @locales
Feature: Getting a available locales in current channel Feature: Getting available locales in the current channel
In order to use a shop in chosen locale In order to use a shop in chosen locale
As a Customer As a Customer
I want to be able to get available locales I want to be able to get available locales

View file

@ -56,9 +56,11 @@ final class LocaleContext implements Context
*/ */
public function theLocaleWithCodeShouldBeAvailable(string $name, string $code): void public function theLocaleWithCodeShouldBeAvailable(string $name, string $code): void
{ {
Assert::true( Assert::true($this->isLocaleWithNameAndCode(
$this->isLocaleWithName($name, $this->responseChecker->getCollection($this->client->getLastResponse())) $this->responseChecker->getCollection($this->client->getLastResponse()),
); $name,
$code
));
} }
/** /**
@ -66,15 +68,17 @@ final class LocaleContext implements Context
*/ */
public function theLocaleWithCodeShouldNotBeAvailable(string $name, string $code): void public function theLocaleWithCodeShouldNotBeAvailable(string $name, string $code): void
{ {
Assert::false( Assert::false($this->isLocaleWithNameAndCode(
$this->isLocaleWithName($name, $this->responseChecker->getCollection($this->client->getLastResponse())) $this->responseChecker->getCollection($this->client->getLastResponse()),
); $name,
$code
));
} }
private function isLocaleWithName(string $name, array $locales): bool private function isLocaleWithNameAndCode(array $locales, string $name, string $code): bool
{ {
foreach ($locales as $locale) { foreach ($locales as $locale) {
if ($locale['name'] === $name) { if ($locale['name'] === $name && $locale['code'] === $code) {
return true; return true;
} }
} }

View file

@ -15,8 +15,10 @@ namespace Sylius\Bundle\ApiBundle\DataProvider;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use Doctrine\Common\Collections\Collection;
use Sylius\Bundle\ApiBundle\Context\UserContextInterface; use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
use Sylius\Bundle\ApiBundle\Serializer\ContextKeys; use Sylius\Bundle\ApiBundle\Serializer\ContextKeys;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Locale\Model\LocaleInterface; use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface; use Sylius\Component\Resource\Repository\RepositoryInterface;
@ -31,8 +33,10 @@ final class LocaleCollectionDataProvider implements CollectionDataProviderInterf
/** @var UserContextInterface */ /** @var UserContextInterface */
private $userContext; private $userContext;
public function __construct(RepositoryInterface $localeRepository, UserContextInterface $userContext) public function __construct(
{ RepositoryInterface $localeRepository,
UserContextInterface $userContext
) {
$this->localeRepository = $localeRepository; $this->localeRepository = $localeRepository;
$this->userContext = $userContext; $this->userContext = $userContext;
} }
@ -44,12 +48,14 @@ final class LocaleCollectionDataProvider implements CollectionDataProviderInterf
public function getCollection(string $resourceClass, string $operationName = null, array $context = []) public function getCollection(string $resourceClass, string $operationName = null, array $context = [])
{ {
Assert::keyExists($context, ContextKeys::CHANNEL);
$user = $this->userContext->getUser(); $user = $this->userContext->getUser();
if ($user !== null && in_array('ROLE_API_ACCESS', $user->getRoles())) {
if ($user instanceof AdminUserInterface && in_array('ROLE_API_ACCESS', $user->getRoles())) {
return $this->localeRepository->findAll(); return $this->localeRepository->findAll();
} }
Assert::keyExists($context, ContextKeys::CHANNEL);
/** @var ChannelInterface $channel */ /** @var ChannelInterface $channel */
$channel = $context[ContextKeys::CHANNEL]; $channel = $context[ContextKeys::CHANNEL];

View file

@ -14,9 +14,11 @@ declare(strict_types=1);
namespace spec\Sylius\Bundle\ApiBundle\DataProvider; namespace spec\Sylius\Bundle\ApiBundle\DataProvider;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use PhpSpec\ObjectBehavior; use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Context\UserContextInterface; use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
use Sylius\Bundle\ApiBundle\Serializer\ContextKeys; use Sylius\Bundle\ApiBundle\Serializer\ContextKeys;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Locale\Model\LocaleInterface; use Sylius\Component\Locale\Model\LocaleInterface;
@ -57,17 +59,17 @@ final class LocaleCollectionDataProviderSpec extends ObjectBehavior
$channel->addLocale($firstLocale); $channel->addLocale($firstLocale);
$channel->addLocale($secondLocale); $channel->addLocale($secondLocale);
$channel->getLocales()->willReturn([$firstLocale, $secondLocale]); $channel->getLocales()->willReturn(new ArrayCollection([$firstLocale->getWrappedObject(), $secondLocale->getWrappedObject()]));
$this $this
->getCollection(LocaleInterface::class, 'get', [ContextKeys::CHANNEL => $channel]) ->getCollection(LocaleInterface::class, 'get', [ContextKeys::CHANNEL => $channel])
->shouldReturn(new ArrayCollection([$firstLocale, $secondLocale])) ->shouldBeLike(new ArrayCollection([$firstLocale->getWrappedObject(), $secondLocale->getWrappedObject()]))
; ;
} }
function it_provides_all_locales_if_user_is_admin( function it_provides_all_locales_if_user_is_admin(
UserContextInterface $userContext, UserContextInterface $userContext,
UserInterface $user, AdminUserInterface $user,
ChannelInterface $channel, ChannelInterface $channel,
LocaleInterface $firstLocale, LocaleInterface $firstLocale,
LocaleInterface $secondLocale, LocaleInterface $secondLocale,