-
Notifications
You must be signed in to change notification settings - Fork 9.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'magento-commerce:2.4-develop' into application-server
- Loading branch information
Showing
20 changed files
with
1,272 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
app/code/Magento/CmsGraphQl/Model/Resolver/Page/ResolverCacheIdentity.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\CmsGraphQl\Model\Resolver\Page; | ||
|
||
use Magento\Cms\Api\Data\PageInterface; | ||
use Magento\Cms\Model\Page; | ||
use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; | ||
|
||
/** | ||
* Identity for resolved CMS page for resolver cache type | ||
*/ | ||
class ResolverCacheIdentity implements IdentityInterface | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private $cacheTag = Page::CACHE_TAG; | ||
|
||
/** | ||
* Get page ID from resolved data | ||
* | ||
* @param array $resolvedData | ||
* @return string[] | ||
*/ | ||
public function getIdentities(array $resolvedData): array | ||
{ | ||
return empty($resolvedData[PageInterface::PAGE_ID]) ? | ||
[] : [sprintf('%s_%s', $this->cacheTag, $resolvedData[PageInterface::PAGE_ID])]; | ||
} | ||
} |
252 changes: 252 additions & 0 deletions
252
app/code/Magento/CmsGraphQl/Test/Integration/Model/Resolver/PageTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\CmsGraphQl\Test\Integration\Model\Resolver; | ||
|
||
use Magento\Cms\Api\Data\PageInterface; | ||
use Magento\Cms\Model\PageRepository; | ||
use Magento\Framework\Api\SearchCriteriaBuilder; | ||
use Magento\Framework\App\Cache\StateInterface as CacheStateInterface; | ||
use Magento\Framework\App\Cache\Type\FrontendPool; | ||
use Magento\Framework\ObjectManagerInterface; | ||
use Magento\GraphQl\Service\GraphQlRequest; | ||
use Magento\GraphQlCache\Model\Cache\Query\Resolver\Result\Type as GraphQlResolverCache; | ||
use Magento\GraphQlCache\Model\Plugin\Query\Resolver\Result\Cache as ResolverResultCachePlugin; | ||
use Magento\TestFramework\Helper\Bootstrap; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* Test GraphQl Resolver cache saves and loads properly | ||
* @magentoAppArea graphql | ||
*/ | ||
class PageTest extends TestCase | ||
{ | ||
/** | ||
* @var ObjectManagerInterface | ||
*/ | ||
private $objectManager; | ||
|
||
/** | ||
* @var GraphQlRequest | ||
*/ | ||
private $graphQlRequest; | ||
|
||
/** | ||
* @var ResolverResultCachePlugin | ||
*/ | ||
private $originalResolverResultCachePlugin; | ||
|
||
/** | ||
* @var SearchCriteriaBuilder | ||
*/ | ||
private $searchCriteriaBuilder; | ||
|
||
/** | ||
* @var PageRepository | ||
*/ | ||
private $pageRepository; | ||
|
||
/** | ||
* @var CacheStateInterface | ||
*/ | ||
private $cacheState; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
private $originalCacheStateEnabledStatus; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->objectManager = $objectManager = Bootstrap::getObjectManager(); | ||
$this->graphQlRequest = $objectManager->create(GraphQlRequest::class); | ||
$this->searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); | ||
$this->pageRepository = $objectManager->get(PageRepository::class); | ||
$this->originalResolverResultCachePlugin = $objectManager->get(ResolverResultCachePlugin::class); | ||
|
||
$this->cacheState = $objectManager->get(CacheStateInterface::class); | ||
$this->originalCacheStateEnabledStatus = $this->cacheState->isEnabled(GraphQlResolverCache::TYPE_IDENTIFIER); | ||
$this->cacheState->setEnabled(GraphQlResolverCache::TYPE_IDENTIFIER, true); | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
$objectManager = $this->objectManager; | ||
|
||
// reset to original resolver plugin | ||
$objectManager->addSharedInstance($this->originalResolverResultCachePlugin, ResolverResultCachePlugin::class); | ||
|
||
// clean graphql resolver cache and reset to original enablement status | ||
$objectManager->get(GraphQlResolverCache::class)->clean(); | ||
$this->cacheState->setEnabled(GraphQlResolverCache::TYPE_IDENTIFIER, $this->originalCacheStateEnabledStatus); | ||
} | ||
|
||
/** | ||
* Test that result can be loaded continuously after saving once when passing the same arguments | ||
* | ||
* @magentoDataFixture Magento/Cms/Fixtures/page_list.php | ||
* @return void | ||
*/ | ||
public function testResultIsLoadedMultipleTimesAfterOnlyBeingSavedOnce() | ||
{ | ||
$objectManager = $this->objectManager; | ||
$page = $this->getPageByTitle('Page with 1column layout'); | ||
|
||
$frontendPool = $objectManager->get(FrontendPool::class); | ||
|
||
$cacheProxy = $this->getMockBuilder(GraphQlResolverCache::class) | ||
->enableProxyingToOriginalMethods() | ||
->setConstructorArgs([ | ||
$frontendPool | ||
]) | ||
->getMock(); | ||
|
||
// assert cache proxy calls load at least once for the same CMS page query | ||
$cacheProxy | ||
->expects($this->atLeastOnce()) | ||
->method('load'); | ||
|
||
// assert save is called at most once for the same CMS page query | ||
$cacheProxy | ||
->expects($this->once()) | ||
->method('save'); | ||
|
||
$resolverPluginWithCacheProxy = $objectManager->create(ResolverResultCachePlugin::class, [ | ||
'graphQlResolverCache' => $cacheProxy, | ||
]); | ||
|
||
// override resolver plugin with plugin instance containing cache proxy class | ||
$objectManager->addSharedInstance($resolverPluginWithCacheProxy, ResolverResultCachePlugin::class); | ||
|
||
$query = $this->getQuery($page->getIdentifier()); | ||
|
||
// send request and assert save is called | ||
$this->graphQlRequest->send($query); | ||
|
||
// send again and assert save is not called (i.e. result is loaded from resolver cache) | ||
$this->graphQlRequest->send($query); | ||
|
||
// send again with whitespace appended and assert save is not called (i.e. result is loaded from resolver cache) | ||
$this->graphQlRequest->send($query . ' '); | ||
|
||
// send again with a different field and assert save is not called (i.e. result is loaded from resolver cache) | ||
$differentQuery = $this->getQuery($page->getIdentifier(), ['meta_title']); | ||
$this->graphQlRequest->send($differentQuery); | ||
} | ||
|
||
/** | ||
* Test that resolver plugin does not call GraphQlResolverCache's save or load methods when it is disabled | ||
* | ||
* @magentoDataFixture Magento/Cms/Fixtures/page_list.php | ||
* @return void | ||
*/ | ||
public function testNeitherSaveNorLoadAreCalledWhenResolverCacheIsDisabled() | ||
{ | ||
$objectManager = $this->objectManager; | ||
$page = $this->getPageByTitle('Page with 1column layout'); | ||
|
||
// disable graphql resolver cache | ||
$this->cacheState->setEnabled(GraphQlResolverCache::TYPE_IDENTIFIER, false); | ||
|
||
$frontendPool = $objectManager->get(FrontendPool::class); | ||
|
||
$cacheProxy = $this->getMockBuilder(GraphQlResolverCache::class) | ||
->enableProxyingToOriginalMethods() | ||
->setConstructorArgs([ | ||
$frontendPool | ||
]) | ||
->getMock(); | ||
|
||
// assert cache proxy never calls load | ||
$cacheProxy | ||
->expects($this->never()) | ||
->method('load'); | ||
|
||
// assert save is also never called | ||
$cacheProxy | ||
->expects($this->never()) | ||
->method('save'); | ||
|
||
$resolverPluginWithCacheProxy = $objectManager->create(ResolverResultCachePlugin::class, [ | ||
'graphQlResolverCache' => $cacheProxy, | ||
]); | ||
|
||
// override resolver plugin with plugin instance containing cache proxy class | ||
$objectManager->addSharedInstance($resolverPluginWithCacheProxy, ResolverResultCachePlugin::class); | ||
|
||
$query = $this->getQuery($page->getIdentifier()); | ||
|
||
// send request multiple times and assert neither save nor load are called | ||
$this->graphQlRequest->send($query); | ||
$this->graphQlRequest->send($query); | ||
} | ||
|
||
public function testSaveIsNeverCalledWhenMissingRequiredArgumentInQuery() | ||
{ | ||
$objectManager = $this->objectManager; | ||
|
||
$frontendPool = $objectManager->get(FrontendPool::class); | ||
|
||
$cacheProxy = $this->getMockBuilder(GraphQlResolverCache::class) | ||
->enableProxyingToOriginalMethods() | ||
->setConstructorArgs([ | ||
$frontendPool | ||
]) | ||
->getMock(); | ||
|
||
// assert cache proxy never calls save | ||
$cacheProxy | ||
->expects($this->never()) | ||
->method('save'); | ||
|
||
$resolverPluginWithCacheProxy = $objectManager->create(ResolverResultCachePlugin::class, [ | ||
'graphQlResolverCache' => $cacheProxy, | ||
]); | ||
|
||
// override resolver plugin with plugin instance containing cache proxy class | ||
$objectManager->addSharedInstance($resolverPluginWithCacheProxy, ResolverResultCachePlugin::class); | ||
|
||
$query = <<<QUERY | ||
{ | ||
cmsPage { | ||
title | ||
} | ||
} | ||
QUERY; | ||
|
||
// send request multiple times and assert save is never called | ||
$this->graphQlRequest->send($query); | ||
$this->graphQlRequest->send($query); | ||
} | ||
|
||
private function getQuery(string $identifier, array $fields = ['title']): string | ||
{ | ||
$fields = implode(PHP_EOL, $fields); | ||
|
||
return <<<QUERY | ||
{ | ||
cmsPage(identifier: "$identifier") { | ||
$fields | ||
} | ||
} | ||
QUERY; | ||
} | ||
|
||
private function getPageByTitle(string $title): PageInterface | ||
{ | ||
$searchCriteria = $this->searchCriteriaBuilder | ||
->addFilter('title', $title) | ||
->create(); | ||
|
||
$pages = $this->pageRepository->getList($searchCriteria)->getItems(); | ||
|
||
/** @var PageInterface $page */ | ||
$page = reset($pages); | ||
|
||
return $page; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0"?> | ||
<!-- | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
--> | ||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> | ||
<type name="Magento\GraphQlCache\Model\Cache\Query\Resolver\Result\TagResolver"> | ||
<arguments> | ||
<argument name="invalidatableObjectTypes" xsi:type="array"> | ||
<item name="Magento\Cms\Api\Data\PageInterface" xsi:type="string"> | ||
Magento\Cms\Api\Data\PageInterface | ||
</item> | ||
</argument> | ||
</arguments> | ||
</type> | ||
</config> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
...e/Magento/GraphQlCache/Model/Cache/Query/Resolver/Result/ResolverIdentityClassLocator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\GraphQlCache\Model\Cache\Query\Resolver\Result; | ||
|
||
use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; | ||
use Magento\Framework\GraphQl\Query\ResolverInterface; | ||
use Magento\GraphQlCache\Model\Resolver\IdentityPool; | ||
|
||
class ResolverIdentityClassLocator | ||
{ | ||
/** | ||
* @var IdentityPool | ||
*/ | ||
private $identityPool; | ||
|
||
/** | ||
* Map of Resolver Class Name => Identity Provider | ||
* | ||
* @var string[] | ||
*/ | ||
private array $cacheableResolverClassNameIdentityMap; | ||
|
||
/** | ||
* @param IdentityPool $identityPool | ||
* @param array $cacheableResolverClassNameIdentityMap | ||
*/ | ||
public function __construct( | ||
IdentityPool $identityPool, | ||
array $cacheableResolverClassNameIdentityMap | ||
) { | ||
$this->identityPool = $identityPool; | ||
$this->cacheableResolverClassNameIdentityMap = $cacheableResolverClassNameIdentityMap; | ||
} | ||
|
||
/** | ||
* Get Identity provider based on $resolver | ||
* | ||
* @param ResolverInterface $resolver | ||
* @return IdentityInterface|null | ||
*/ | ||
public function getIdentityFromResolver(ResolverInterface $resolver): ?IdentityInterface | ||
{ | ||
$matchingIdentityProviderClassName = null; | ||
|
||
foreach ($this->cacheableResolverClassNameIdentityMap as $resolverClassName => $identityProviderClassName) { | ||
if ($resolver instanceof $resolverClassName) { | ||
$matchingIdentityProviderClassName = $identityProviderClassName; | ||
break; | ||
} | ||
} | ||
|
||
if (!$matchingIdentityProviderClassName) { | ||
return null; | ||
} | ||
|
||
return $this->identityPool->get($matchingIdentityProviderClassName); | ||
} | ||
} |
Oops, something went wrong.