diff --git a/config/services.php b/config/services.php index da95c24..0bfeec3 100644 --- a/config/services.php +++ b/config/services.php @@ -17,7 +17,9 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger\RemoveDisabledTagsResponseTagger; use Neusta\Pimcore\HttpCacheBundle\CacheActivator; use Neusta\Pimcore\HttpCacheBundle\DataCollector; +use Neusta\Pimcore\HttpCacheBundle\Element\DependentElementFinder; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use Neusta\Pimcore\HttpCacheBundle\Element\InvalidateElementListener; use Neusta\Pimcore\HttpCacheBundle\Element\TagElementListener; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -73,25 +75,33 @@ $services->set('.neusta_pimcore_http_cache.element.repository', ElementRepository::class); + $services->set('neusta_pimcore_http_cache.elements_config', ElementsConfig::class) + ->factory([ElementsConfig::class, 'fromArray']); + $services->set('neusta_pimcore_http_cache.cache_tag_checker.element.asset', AssetCacheTagChecker::class) ->arg('$repository', service('.neusta_pimcore_http_cache.element.repository')) - ->arg('$config', ['enabled' => false, 'types' => []]); + ->arg('$config', service('neusta_pimcore_http_cache.elements_config')); $services->set('neusta_pimcore_http_cache.cache_tag_checker.element.document', DocumentCacheTagChecker::class) ->arg('$repository', service('.neusta_pimcore_http_cache.element.repository')) - ->arg('$config', ['enabled' => false, 'types' => []]); + ->arg('$config', service('neusta_pimcore_http_cache.elements_config')); $services->set('neusta_pimcore_http_cache.cache_tag_checker.element.object', ObjectCacheTagChecker::class) ->arg('$repository', service('.neusta_pimcore_http_cache.element.repository')) - ->arg('$config', ['enabled' => false, 'types' => [], 'classes' => []]); + ->arg('$config', service('neusta_pimcore_http_cache.elements_config')); $services->set('neusta_pimcore_http_cache.element.tag_listener', TagElementListener::class) ->arg('$responseTagger', service('neusta_pimcore_http_cache.response_tagger')) ->arg('$dispatcher', service('event_dispatcher')); + $services->set('neusta_pimcore_http_cache.element.dependent_element_finder', DependentElementFinder::class) + ->arg('$elementRepository', service('.neusta_pimcore_http_cache.element.repository')) + ->arg('$config', service('neusta_pimcore_http_cache.elements_config')); + $services->set('neusta_pimcore_http_cache.element.invalidate_listener', InvalidateElementListener::class) ->arg('$cacheInvalidator', service('neusta_pimcore_http_cache.cache_invalidator')) - ->arg('$dispatcher', service('event_dispatcher')); + ->arg('$dispatcher', service('event_dispatcher')) + ->arg('$dependentElementFinder', service('neusta_pimcore_http_cache.element.dependent_element_finder')); $services->set('neusta_pimcore_http_cache.data_collector', DataCollector::class) ->arg('$cacheTagCollector', service('.neusta_pimcore_http_cache.collect_tags_response_tagger')) diff --git a/doc/2-configuration.md b/doc/2-configuration.md index c8d1cdc..ec5a007 100644 --- a/doc/2-configuration.md +++ b/doc/2-configuration.md @@ -7,32 +7,84 @@ neusta_pimcore_http_cache: # Enable/disable cache handling for certain element types elements: assets: + # Available types: image, video, audio, document, archive, text, unknown, folder # By default, every type except "folder" is enabled types: archive: false unknown: false - - # Unless you disable assets completely + + # Invalidate dependent elements when an asset changes (disabled by default). + # Note: a dependent element type must also be enabled above for invalidation to take effect. + invalidate_dependent_elements: + enabled: true + types: + objects: # fine-grained: invalidate objects, but with exclusions + enabled: true + types: + folder: false # skip object folders + classes: + MyIgnoredClass: false # skip this class + documents: # fine-grained: invalidate documents, but with exclusions + enabled: true + types: + link: false # skip link documents + + # Or disable assets completely (mutually exclusive with the options above) enabled: false - + documents: + # Available types: page, snippet, link, hardlink, email, folder # By default, every type except "email", "folder" and "hardlink" is enabled types: link: false - - # Unless you disable documents completely + + # Invalidate dependent elements when a document changes (disabled by default). + # Note: a dependent element type must also be enabled above for invalidation to take effect. + invalidate_dependent_elements: + enabled: true + types: + objects: # fine-grained: invalidate objects, but with exclusions + enabled: true + types: + folder: false # skip object folders + classes: + MyIgnoredClass: false # skip this class + + # Or disable documents completely (mutually exclusive with the options above) enabled: false - + objects: + # Available types: object, variant, folder # By default, every type except "folder" is enabled types: variant: false - + # By default, every data object class is enabled classes: MyDataObjectClass: false - # Unless you disable data objects completely + # Invalidate dependent elements when an object changes (disabled by default). + # Note: a dependent element type must also be enabled above for invalidation to take effect. + invalidate_dependent_elements: + enabled: true + types: + objects: # fine-grained: invalidate objects, but with exclusions + enabled: true + types: + folder: false # skip object folders + variant: false # skip variants + classes: + MyIgnoredClass: false # skip this class + documents: # fine-grained: invalidate documents, but with exclusions + enabled: true + types: + link: false # skip link documents + assets: # fine-grained: invalidate assets, but with exclusions + enabled: true + types: + folder: false # skip asset folders + + # Or disable data objects completely (mutually exclusive with the options above) enabled: false # Enable/disable cache handling for custom cache types diff --git a/doc/3-pimcore-elements.md b/doc/3-pimcore-elements.md index 16589c1..c9d9a45 100644 --- a/doc/3-pimcore-elements.md +++ b/doc/3-pimcore-elements.md @@ -5,7 +5,9 @@ types. You can enable or disable cache handling for specific element types and c ### Assets -By default, all asset types except "folder" are enabled. You can disable specific asset types or disable assets +The available asset types are: `image`, `video`, `audio`, `document`, `archive`, `text`, `unknown`, `folder`. + +By default, all asset types except `folder` are enabled. You can disable specific asset types or disable assets completely. #### Disable specific asset types @@ -28,7 +30,10 @@ neusta_pimcore_http_cache: ``` ### Documents -By default, all document types except "email", "folder" and "hardlink" are enabled. You can disable specific document types or disable documents completely. + +The available document types are: `page`, `snippet`, `link`, `hardlink`, `email`, `folder`. + +By default, all document types except `email`, `folder`, and `hardlink` are enabled. You can disable specific document types or disable documents completely. #### Disable specific document types Example configuration to disable the "link" document type: @@ -50,7 +55,10 @@ neusta_pimcore_http_cache: ``` ### Objects -By default, all object types except "folder" are enabled. You can disable specific object types or disable objects completely. Also, you can enable or disable cache handling for specific data object classes. + +The available object types are: `object`, `variant`, `folder`. + +By default, all object types except `folder` are enabled. You can disable specific object types or disable objects completely. Also, you can enable or disable cache handling for specific data object classes. #### Disable specific object types Example configuration to disable the "variant" object type: @@ -81,3 +89,100 @@ neusta_pimcore_http_cache: classes: MyDataObjectClass: false ``` + +## Dependent Element Invalidation + +When a Pimcore element is updated or deleted, other elements that reference it may also serve stale content. +For example, a document that embeds a data object will be outdated as soon as that object changes. + +By default, the bundle only invalidates the cache tag of the element that was directly changed. +Dependent element invalidation — traversing Pimcore's dependency graph to also purge referencing elements — is **disabled by default** and must be opted in via configuration. + +The dependency graph is one level deep: only elements that directly reference the changed element are invalidated, not transitive dependencies. + +> **Note:** For a dependent element type to actually be invalidated, it must also be enabled in the main `elements` configuration. For example, setting `objects.invalidate_dependent_elements.types.documents: true` has no effect if `documents` is disabled — the cache tag will be silently dropped. + +### Enable dependent invalidation for objects + +The most common use case is invalidating documents and other objects that reference a changed data object. +The listed dependent types (`documents`, `objects`) must also be enabled in the `elements` configuration: + +```yaml +neusta_pimcore_http_cache: + elements: + objects: + invalidate_dependent_elements: + enabled: true + types: + documents: true # invalidate documents that reference the object + objects: true # invalidate objects that reference the object + assets: false # leave assets out (default) + documents: true # must be enabled for document invalidation to take effect +``` + +### Enable dependent invalidation for assets + +If an asset (e.g. an image) is referenced by objects or documents, those can be invalidated when the asset changes. +The listed dependent types must also be enabled in the `elements` configuration: + +```yaml +neusta_pimcore_http_cache: + elements: + assets: + invalidate_dependent_elements: + enabled: true + types: + objects: true # invalidate objects that reference the asset + documents: true # invalidate documents that reference the asset + objects: true # must be enabled for object invalidation to take effect + documents: true # must be enabled for document invalidation to take effect +``` + +### Enable dependent invalidation for documents + +If a document is referenced by other elements (e.g. an object with a document relation field), those elements can be invalidated when the document changes. +The listed dependent types must also be enabled in the `elements` configuration: + +```yaml +neusta_pimcore_http_cache: + elements: + documents: + invalidate_dependent_elements: + enabled: true + types: + objects: true # invalidate objects that reference the document + objects: true # must be enabled for object invalidation to take effect +``` + +### Fine-grained control per dependent type + +Each dependent type under `types` accepts either a boolean shorthand or a full configuration +with its own `types` (and `classes` for objects) to exclude specific subtypes or classes: + +```yaml +neusta_pimcore_http_cache: + elements: + objects: + invalidate_dependent_elements: + enabled: true + types: + assets: # fine-grained: invalidate assets, but with exclusions + enabled: true + types: + folder: false # don't cascade to asset folders + documents: # fine-grained: invalidate documents, but with exclusions + enabled: true + types: + link: false # don't cascade to link documents + objects: # fine-grained: invalidate objects, but with exclusions + enabled: true + types: + folder: false # don't cascade to object folders + classes: + MyIgnoredClass: false # don't cascade to this class +``` + +The `types` filter applies to all dependent element types (assets, documents, objects). +The `classes` filter is only available for `objects`. + +Both filters work alongside the global `elements` configuration — both must allow an element for it to be invalidated as a dependent. diff --git a/src/Cache/CacheTagChecker/Element/AssetCacheTagChecker.php b/src/Cache/CacheTagChecker/Element/AssetCacheTagChecker.php index ad28256..00c47dc 100644 --- a/src/Cache/CacheTagChecker/Element/AssetCacheTagChecker.php +++ b/src/Cache/CacheTagChecker/Element/AssetCacheTagChecker.php @@ -6,16 +6,14 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheType\ElementCacheType; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use Neusta\Pimcore\HttpCacheBundle\Element\ElementType; final class AssetCacheTagChecker implements CacheTagChecker { - /** - * @param array{enabled: bool, types: array} $config - */ public function __construct( private readonly ElementRepository $repository, - private readonly array $config, + private readonly ElementsConfig $config, ) { } @@ -24,7 +22,7 @@ public function isEnabled(CacheTag $tag): bool \assert($tag->type instanceof ElementCacheType, \sprintf('Cache type must be an instance of %s', ElementCacheType::class)); \assert(ElementType::Asset === $tag->type->type, \sprintf('Cache type must be "%s"', ElementType::Asset->value)); - if (!$this->config['enabled']) { + if (!$this->config->isEnabled(ElementType::Asset)) { return false; } @@ -32,6 +30,6 @@ public function isEnabled(CacheTag $tag): bool return false; } - return $this->config['types'][$asset->getType()] ?? true; + return $this->config->isTypeEnabled(ElementType::Asset, $asset->getType()); } } diff --git a/src/Cache/CacheTagChecker/Element/DocumentCacheTagChecker.php b/src/Cache/CacheTagChecker/Element/DocumentCacheTagChecker.php index 1d5fe5e..b787fca 100644 --- a/src/Cache/CacheTagChecker/Element/DocumentCacheTagChecker.php +++ b/src/Cache/CacheTagChecker/Element/DocumentCacheTagChecker.php @@ -6,16 +6,14 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheType\ElementCacheType; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use Neusta\Pimcore\HttpCacheBundle\Element\ElementType; final class DocumentCacheTagChecker implements CacheTagChecker { - /** - * @param array{enabled: bool, types: array} $config - */ public function __construct( private readonly ElementRepository $repository, - private readonly array $config, + private readonly ElementsConfig $config, ) { } @@ -24,7 +22,7 @@ public function isEnabled(CacheTag $tag): bool \assert($tag->type instanceof ElementCacheType, \sprintf('Cache type must be an instance of %s', ElementCacheType::class)); \assert(ElementType::Document === $tag->type->type, \sprintf('Cache type must be "%s"', ElementType::Document->value)); - if (!$this->config['enabled']) { + if (!$this->config->isEnabled(ElementType::Document)) { return false; } @@ -32,6 +30,6 @@ public function isEnabled(CacheTag $tag): bool return false; } - return $this->config['types'][$document->getType()] ?? true; + return $this->config->isTypeEnabled(ElementType::Document, $document->getType()); } } diff --git a/src/Cache/CacheTagChecker/Element/ObjectCacheTagChecker.php b/src/Cache/CacheTagChecker/Element/ObjectCacheTagChecker.php index d10af59..4e858ba 100644 --- a/src/Cache/CacheTagChecker/Element/ObjectCacheTagChecker.php +++ b/src/Cache/CacheTagChecker/Element/ObjectCacheTagChecker.php @@ -6,17 +6,15 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheType\ElementCacheType; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use Neusta\Pimcore\HttpCacheBundle\Element\ElementType; use Pimcore\Model\DataObject\Concrete; final class ObjectCacheTagChecker implements CacheTagChecker { - /** - * @param array{enabled: bool, types: array, classes: array} $config - */ public function __construct( private readonly ElementRepository $repository, - private readonly array $config, + private readonly ElementsConfig $config, ) { } @@ -25,7 +23,7 @@ public function isEnabled(CacheTag $tag): bool \assert($tag->type instanceof ElementCacheType, \sprintf('Cache type must be an instance of %s', ElementCacheType::class)); \assert(ElementType::Object === $tag->type->type, \sprintf('Cache type must be "%s"', ElementType::Object->value)); - if (!$this->config['enabled']) { + if (!$this->config->isEnabled(ElementType::Object)) { return false; } @@ -33,7 +31,7 @@ public function isEnabled(CacheTag $tag): bool return false; } - if (!($this->config['types'][$object->getType()] ?? true)) { + if (!$this->config->isTypeEnabled(ElementType::Object, $object->getType())) { return false; } @@ -41,6 +39,6 @@ public function isEnabled(CacheTag $tag): bool return true; } - return $this->config['classes'][$object->getClassName()] ?? true; + return $this->config->isObjectClassEnabled($object->getClassName()); } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 6f93ccb..1fe4bd3 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -2,6 +2,7 @@ namespace Neusta\Pimcore\HttpCacheBundle\DependencyInjection; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -37,6 +38,22 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue(['folder' => false]) ->booleanPrototype()->end() ->end() + ->arrayNode('invalidate_dependent_elements') + ->info('Enable/disable invalidation of dependent elements when an asset is updated or deleted.') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('types') + ->info('Enable/disable invalidation of dependent element types.') + ->addDefaultsIfNotSet() + ->children() + ->append($this->buildDependentTypeNode('assets')) + ->append($this->buildDependentTypeNode('documents')) + ->append($this->buildDependentTypeNode('objects', withClasses: true)) + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->arrayNode('documents') @@ -54,6 +71,22 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue(['email' => false, 'folder' => false, 'hardlink' => false]) ->booleanPrototype()->end() ->end() + ->arrayNode('invalidate_dependent_elements') + ->info('Enable/disable invalidation of dependent elements when a document is updated or deleted.') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('types') + ->info('Enable/disable invalidation of dependent element types.') + ->addDefaultsIfNotSet() + ->children() + ->append($this->buildDependentTypeNode('assets')) + ->append($this->buildDependentTypeNode('documents')) + ->append($this->buildDependentTypeNode('objects', withClasses: true)) + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->arrayNode('objects') @@ -79,6 +112,22 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue([]) ->booleanPrototype()->end() ->end() + ->arrayNode('invalidate_dependent_elements') + ->info('Enable/disable invalidation of dependent elements when an object is updated or deleted.') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('types') + ->info('Enable/disable invalidation of dependent element types.') + ->addDefaultsIfNotSet() + ->children() + ->append($this->buildDependentTypeNode('assets')) + ->append($this->buildDependentTypeNode('documents')) + ->append($this->buildDependentTypeNode('objects', withClasses: true)) + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() @@ -93,4 +142,40 @@ public function getConfigTreeBuilder(): TreeBuilder return $treeBuilder; } + + private function buildDependentTypeNode(string $name, bool $withClasses = false): ArrayNodeDefinition + { + $builder = new TreeBuilder($name); + /** @var ArrayNodeDefinition $node */ + $node = $builder->getRootNode(); + + $node + ->beforeNormalization() + ->ifTrue(fn ($v) => is_bool($v)) + ->then(fn ($v) => ['enabled' => $v]) + ->end() + ->canBeEnabled() + ->addDefaultsIfNotSet(); + + $children = $node->children(); + $children + ->arrayNode('types') + ->normalizeKeys(false) + ->useAttributeAsKey('type') + ->defaultValue([]) + ->booleanPrototype()->end() + ->end(); + + if ($withClasses) { + $children + ->arrayNode('classes') + ->normalizeKeys(false) + ->useAttributeAsKey('class') + ->defaultValue([]) + ->booleanPrototype()->end() + ->end(); + } + + return $node; + } } diff --git a/src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php b/src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php index b1dd629..ed78d6e 100644 --- a/src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php +++ b/src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php @@ -35,10 +35,10 @@ private function registerElements(ContainerBuilder $container, array $config): v $tagListener = $container->getDefinition('neusta_pimcore_http_cache.element.tag_listener'); $invalidateListener = $container->getDefinition('neusta_pimcore_http_cache.element.invalidate_listener'); - if ($config['assets']['enabled']) { - $container->getDefinition('neusta_pimcore_http_cache.cache_tag_checker.element.asset') - ->setArgument('$config', $config['assets']); + $container->getDefinition('neusta_pimcore_http_cache.elements_config') + ->setArgument('$config', $config); + if ($config['assets']['enabled']) { $tagListener ->addTag('kernel.event_listener', ['event' => AssetEvents::POST_LOAD]); @@ -48,9 +48,6 @@ private function registerElements(ContainerBuilder $container, array $config): v } if ($config['documents']['enabled']) { - $container->getDefinition('neusta_pimcore_http_cache.cache_tag_checker.element.document') - ->setArgument('$config', $config['documents']); - $tagListener ->addTag('kernel.event_listener', ['event' => DocumentEvents::POST_LOAD]); @@ -60,9 +57,6 @@ private function registerElements(ContainerBuilder $container, array $config): v } if ($config['objects']['enabled']) { - $container->getDefinition('neusta_pimcore_http_cache.cache_tag_checker.element.object') - ->setArgument('$config', $config['objects']); - $tagListener ->addTag('kernel.event_listener', ['event' => DataObjectEvents::POST_LOAD]); diff --git a/src/Element/DependentElementFinder.php b/src/Element/DependentElementFinder.php new file mode 100644 index 0000000..665bd0d --- /dev/null +++ b/src/Element/DependentElementFinder.php @@ -0,0 +1,56 @@ + + */ + public function findFor(ElementInterface $source): array + { + $sourceType = ElementType::tryFromElement($source); + + if (null === $sourceType || !$this->config->isDependentElementsEnabled($sourceType)) { + return []; + } + + $elements = []; + + foreach ($source->getDependencies()->getRequiredBy() as $required) { + if (!isset($required['id'], $required['type'])) { + continue; + } + + $dependentType = ElementType::tryFrom($required['type']); + + if (null === $dependentType) { + continue; + } + + if (!$this->config->isDependentTypeEnabled($sourceType, $dependentType)) { + continue; + } + + $element = match ($dependentType) { + ElementType::Asset => $this->elementRepository->findAsset((int) $required['id']), + ElementType::Document => $this->elementRepository->findDocument((int) $required['id']), + ElementType::Object => $this->elementRepository->findObject((int) $required['id']), + }; + + if (null !== $element && $this->config->isDependentElementEnabled($sourceType, $element)) { + $elements[] = $element; + } + } + + return $elements; + } +} diff --git a/src/Element/DependentTypeConfig.php b/src/Element/DependentTypeConfig.php new file mode 100644 index 0000000..37b837d --- /dev/null +++ b/src/Element/DependentTypeConfig.php @@ -0,0 +1,44 @@ + */ + private array $types, + /** @var array */ + private array $classes, + ) { + } + + /** @param array|bool $config */ + public static function fromArray(array|bool $config): self + { + if (\is_bool($config)) { + return new self(enabled: $config, types: [], classes: []); + } + + return new self( + enabled: $config['enabled'] ?? false, + types: $config['types'] ?? [], + classes: $config['classes'] ?? [], + ); + } + + public function isEnabled(): bool + { + return $this->enabled; + } + + public function isTypeEnabled(string $type): bool + { + return $this->types[$type] ?? true; + } + + public function isObjectClassEnabled(?string $class): bool + { + return null === $class || ($this->classes[$class] ?? true); + } +} diff --git a/src/Element/ElementType.php b/src/Element/ElementType.php index fb136a5..02c6837 100644 --- a/src/Element/ElementType.php +++ b/src/Element/ElementType.php @@ -15,4 +15,9 @@ public static function fromElement(ElementInterface $element): self { return self::from(Service::getElementType($element) ?? ''); } + + public static function tryFromElement(ElementInterface $element): ?self + { + return self::tryFrom(Service::getElementType($element) ?? ''); + } } diff --git a/src/Element/ElementsConfig.php b/src/Element/ElementsConfig.php new file mode 100644 index 0000000..eb5519d --- /dev/null +++ b/src/Element/ElementsConfig.php @@ -0,0 +1,152 @@ + */ + private array $assetTypes, + private bool $assetDependentElementsEnabled, + private DependentTypeConfig $assetDependentAssetConfig, + private DependentTypeConfig $assetDependentDocumentConfig, + private DependentTypeConfig $assetDependentObjectConfig, + private bool $documentsEnabled, + /** @var array */ + private array $documentTypes, + private bool $documentDependentElementsEnabled, + private DependentTypeConfig $documentDependentAssetConfig, + private DependentTypeConfig $documentDependentDocumentConfig, + private DependentTypeConfig $documentDependentObjectConfig, + private bool $objectsEnabled, + /** @var array */ + private array $objectTypes, + /** @var array */ + private array $objectClasses, + private bool $objectDependentElementsEnabled, + private DependentTypeConfig $objectDependentAssetConfig, + private DependentTypeConfig $objectDependentDocumentConfig, + private DependentTypeConfig $objectDependentObjectConfig, + ) { + } + + /** @param array $config */ + public static function fromArray(array $config): self + { + return new self( + assetsEnabled: $config['assets']['enabled'] ?? false, + assetTypes: $config['assets']['types'] ?? [], + assetDependentElementsEnabled: $config['assets']['invalidate_dependent_elements']['enabled'] ?? false, + assetDependentAssetConfig: DependentTypeConfig::fromArray($config['assets']['invalidate_dependent_elements']['types']['assets'] ?? []), + assetDependentDocumentConfig: DependentTypeConfig::fromArray($config['assets']['invalidate_dependent_elements']['types']['documents'] ?? []), + assetDependentObjectConfig: DependentTypeConfig::fromArray($config['assets']['invalidate_dependent_elements']['types']['objects'] ?? []), + documentsEnabled: $config['documents']['enabled'] ?? false, + documentTypes: $config['documents']['types'] ?? [], + documentDependentElementsEnabled: $config['documents']['invalidate_dependent_elements']['enabled'] ?? false, + documentDependentAssetConfig: DependentTypeConfig::fromArray($config['documents']['invalidate_dependent_elements']['types']['assets'] ?? []), + documentDependentDocumentConfig: DependentTypeConfig::fromArray($config['documents']['invalidate_dependent_elements']['types']['documents'] ?? []), + documentDependentObjectConfig: DependentTypeConfig::fromArray($config['documents']['invalidate_dependent_elements']['types']['objects'] ?? []), + objectsEnabled: $config['objects']['enabled'] ?? false, + objectTypes: $config['objects']['types'] ?? [], + objectClasses: $config['objects']['classes'] ?? [], + objectDependentElementsEnabled: $config['objects']['invalidate_dependent_elements']['enabled'] ?? false, + objectDependentAssetConfig: DependentTypeConfig::fromArray($config['objects']['invalidate_dependent_elements']['types']['assets'] ?? []), + objectDependentDocumentConfig: DependentTypeConfig::fromArray($config['objects']['invalidate_dependent_elements']['types']['documents'] ?? []), + objectDependentObjectConfig: DependentTypeConfig::fromArray($config['objects']['invalidate_dependent_elements']['types']['objects'] ?? []), + ); + } + + public function isEnabled(ElementType $type): bool + { + return match ($type) { + ElementType::Asset => $this->assetsEnabled, + ElementType::Document => $this->documentsEnabled, + ElementType::Object => $this->objectsEnabled, + }; + } + + public function isTypeEnabled(ElementType $elementType, string $type): bool + { + $types = match ($elementType) { + ElementType::Asset => $this->assetTypes, + ElementType::Document => $this->documentTypes, + ElementType::Object => $this->objectTypes, + }; + + return $types[$type] ?? true; + } + + public function isObjectClassEnabled(?string $class): bool + { + return null === $class || ($this->objectClasses[$class] ?? true); + } + + public function isDependentElementsEnabled(ElementType $type): bool + { + return match ($type) { + ElementType::Asset => $this->assetDependentElementsEnabled, + ElementType::Document => $this->documentDependentElementsEnabled, + ElementType::Object => $this->objectDependentElementsEnabled, + }; + } + + public function isDependentTypeEnabled(ElementType $sourceType, ElementType $dependentType): bool + { + return $this->getDependentTypeConfig($sourceType, $dependentType)->isEnabled(); + } + + public function isDependentElementEnabled(ElementType $sourceType, ElementInterface $element): bool + { + $dependentType = ElementType::tryFromElement($element); + + if (null === $dependentType) { + return false; + } + + $dependentConfig = $this->getDependentTypeConfig($sourceType, $dependentType); + + if (!$dependentConfig->isEnabled()) { + return false; + } + + if (!$this->isTypeEnabled($dependentType, $element->getType())) { + return false; + } + + if (!$dependentConfig->isTypeEnabled($element->getType())) { + return false; + } + + if (ElementType::Object === $dependentType && $element instanceof Concrete) { + return $this->isObjectClassEnabled($element->getClassName()) + && $dependentConfig->isObjectClassEnabled($element->getClassName()); + } + + return true; + } + + private function getDependentTypeConfig(ElementType $source, ElementType $dependent): DependentTypeConfig + { + return match ($source) { + ElementType::Asset => match ($dependent) { + ElementType::Asset => $this->assetDependentAssetConfig, + ElementType::Document => $this->assetDependentDocumentConfig, + ElementType::Object => $this->assetDependentObjectConfig, + }, + ElementType::Document => match ($dependent) { + ElementType::Asset => $this->documentDependentAssetConfig, + ElementType::Document => $this->documentDependentDocumentConfig, + ElementType::Object => $this->documentDependentObjectConfig, + }, + ElementType::Object => match ($dependent) { + ElementType::Asset => $this->objectDependentAssetConfig, + ElementType::Document => $this->objectDependentDocumentConfig, + ElementType::Object => $this->objectDependentObjectConfig, + }, + }; + } +} diff --git a/src/Element/InvalidateElementListener.php b/src/Element/InvalidateElementListener.php index d8a2d8f..fc40b1e 100644 --- a/src/Element/InvalidateElementListener.php +++ b/src/Element/InvalidateElementListener.php @@ -12,32 +12,44 @@ final class InvalidateElementListener public function __construct( private readonly CacheInvalidator $cacheInvalidator, private readonly EventDispatcherInterface $dispatcher, + private readonly DependentElementFinder $dependentElementFinder, ) { } public function onUpdate(ElementEventInterface $event): void { - if ($event->hasArgument('saveVersionOnly') || $event->hasArgument('autoSave')) { - return; + if (!$event->hasArgument('saveVersionOnly') && !$event->hasArgument('autoSave')) { + $this->invalidateWithDependentElements($event->getElement()); } - - $this->invalidateElement($event->getElement()); } public function onDelete(ElementEventInterface $event): void { - $this->invalidateElement($event->getElement()); + $this->invalidateWithDependentElements($event->getElement()); } - private function invalidateElement(ElementInterface $element): void + private function invalidateWithDependentElements(ElementInterface $element): void + { + if (!$this->invalidateElement($element)) { + return; + } + + foreach ($this->dependentElementFinder->findFor($element) as $dependent) { + $this->invalidateElement($dependent); + } + } + + private function invalidateElement(ElementInterface $element): bool { $invalidationEvent = $this->dispatcher->dispatch(ElementInvalidationEvent::fromElement($element)); \assert($invalidationEvent instanceof ElementInvalidationEvent); if ($invalidationEvent->cancel) { - return; + return false; } $this->cacheInvalidator->invalidate($invalidationEvent->cacheTags()); + + return true; } } diff --git a/tests/Integration/Configuration/CollectConfigurationDataTest.php b/tests/Integration/Configuration/CollectConfigurationDataTest.php index d0e1870..e320dbb 100644 --- a/tests/Integration/Configuration/CollectConfigurationDataTest.php +++ b/tests/Integration/Configuration/CollectConfigurationDataTest.php @@ -40,7 +40,7 @@ protected function setUp(): void ])] public function collects_configuration_data(): void { - self::arrange(fn () => TestDocumentFactory::simplePage())->save(); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); $this->client->enableProfiler(); @@ -65,7 +65,7 @@ public function collects_configuration_data(): void ])] public function does_not_collect_configuration_data_when_profiler_is_disabled(): void { - self::arrange(fn () => TestDocumentFactory::simplePage())->save(); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); $this->client->enableProfiler(); diff --git a/tests/Integration/Helpers/TestAssetFactory.php b/tests/Integration/Helpers/TestAssetFactory.php index 5e075e9..ffb7ec2 100644 --- a/tests/Integration/Helpers/TestAssetFactory.php +++ b/tests/Integration/Helpers/TestAssetFactory.php @@ -6,11 +6,11 @@ final class TestAssetFactory { - public static function simpleAsset(): Asset + public static function simpleAsset(int $id = 42, string $fileName = 'test-asset.txt'): Asset { $asset = new Asset(); - $asset->setId(42); - $asset->setFilename('test-asset.txt'); + $asset->setId($id); + $asset->setFilename($fileName); $asset->setParentId(1); $asset->setData('This is the content of the test asset.'); $asset->setMimetype('text/plain'); @@ -18,22 +18,22 @@ public static function simpleAsset(): Asset return $asset; } - public static function simpleImage(): Asset\Image + public static function simpleImage(int $id = 17, string $fileName = 'test-asset.jpg'): Asset\Image { $image = new Asset\Image(); - $image->setId(17); - $image->setFilename('test-asset.jpg'); + $image->setId($id); + $image->setFilename($fileName); $image->setParentId(1); $image->setMimetype('image/jpeg'); return $image; } - public static function simpleFolder(): Asset\Folder + public static function simpleFolder(int $id = 23, string $key = 'test-asset-folder'): Asset\Folder { $folder = new Asset\Folder(); - $folder->setKey('test-asset-folder'); - $folder->setId(23); + $folder->setId($id); + $folder->setKey($key); $folder->setParentId(1); return $folder; diff --git a/tests/Integration/Helpers/TestDocumentFactory.php b/tests/Integration/Helpers/TestDocumentFactory.php index 7f7c4eb..6dc6455 100644 --- a/tests/Integration/Helpers/TestDocumentFactory.php +++ b/tests/Integration/Helpers/TestDocumentFactory.php @@ -2,6 +2,8 @@ namespace Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers; +use Pimcore\Model\DataObject\TestObject; +use Pimcore\Model\Document\Editable\Relation; use Pimcore\Model\Document\Email; use Pimcore\Model\Document\Folder; use Pimcore\Model\Document\Hardlink; @@ -10,55 +12,67 @@ final class TestDocumentFactory { - public static function simplePage(): Page + public static function simplePage(int $id = 42, string $key = 'test_document_page', ?TestObject $relatedObject = null): Page { $page = new Page(); - $page->setId(42); - $page->setKey('test_document_page'); + $page->setId($id); + $page->setKey($key); $page->setPublished(true); $page->setParentId(1); + if (null !== $relatedObject) { + $objectRelation = new Relation(); + $objectRelation->setName('relatedObject'); + $objectRelation->setDataFromResource([ + 'id' => $relatedObject->getId(), + 'type' => 'object', + 'subtype' => 'object', + ]); + + $page->setEditable($objectRelation); + } + return $page; } - public static function simpleSnippet(): Snippet + public static function simpleSnippet(int $id = 23, string $key = 'test_document_snippet'): Snippet { $snippet = new Snippet(); - $snippet->setId(23); - $snippet->setKey('test_document_snippet'); + $snippet->setId($id); + $snippet->setKey($key); $snippet->setPublished(true); $snippet->setParentId(1); return $snippet; } - public static function simpleEmail(): Email + public static function simpleEmail(int $id = 17, string $key = 'test_document_email'): Email { $email = new Email(); - $email->setId(17); - $email->setKey('test_document_link'); + $email->setId($id); + $email->setKey($key); $email->setPublished(true); $email->setParentId(1); return $email; } - public static function simpleHardLink(): Hardlink + public static function simpleHardLink(int $id = 33, string $key = 'test_document_hard_link'): Hardlink { $hardlink = new Hardlink(); - $hardlink->setId(33); - $hardlink->setKey('test_document_hard_link'); + $hardlink->setId($id); + $hardlink->setKey($key); $hardlink->setPublished(true); $hardlink->setParentId(1); return $hardlink; } - public static function simpleFolder(): Folder + public static function simpleFolder(int $id = 97, string $key = 'test_document_folder'): Folder { $folder = new Folder(); - $folder->setId(97); - $folder->setKey('test_document_folder'); + $folder->setId($id); + $folder->setKey($key); $folder->setPublished(true); $folder->setParentId(1); diff --git a/tests/Integration/Helpers/TestObjectFactory.php b/tests/Integration/Helpers/TestObjectFactory.php index 98d9f42..bb5da78 100644 --- a/tests/Integration/Helpers/TestObjectFactory.php +++ b/tests/Integration/Helpers/TestObjectFactory.php @@ -2,29 +2,35 @@ namespace Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers; +use Pimcore\Model\Asset\Image; use Pimcore\Model\DataObject; use Pimcore\Model\DataObject\AbstractObject; -use Pimcore\Model\DataObject\TestDataObject; +use Pimcore\Model\DataObject\TestObject; +use Pimcore\Model\Document\Page; final class TestObjectFactory { - public static function simpleObject(): TestDataObject + /** + * @param list $related + */ + public static function simpleObject(int $id = 42, string $key = 'test_object', array $related = []): TestObject { - $object = new TestDataObject(); - $object->setId(42); - $object->setKey('test_object'); + $object = new TestObject(); + $object->setId($id); + $object->setKey($key); $object->setContent('Test content'); + $object->setRelated($related); $object->setPublished(true); $object->setParentId(1); return $object; } - public static function simpleVariant(): TestDataObject + public static function simpleVariant(int $id = 17, string $key = 'simple_variant'): TestObject { - $object = new TestDataObject(); - $object->setId(17); - $object->setKey('test_variant'); + $object = new TestObject(); + $object->setId($id); + $object->setKey($key); $object->setContent('Test content'); $object->setPublished(true); $object->setParentId(1); @@ -33,11 +39,11 @@ public static function simpleVariant(): TestDataObject return $object; } - public static function simpleFolder(): DataObject\Folder + public static function simpleFolder(int $id = 43, string $key = 'simple_folder'): DataObject\Folder { $folder = new DataObject\Folder(); - $folder->setId(23); - $folder->setKey('test_folder'); + $folder->setId($id); + $folder->setKey($key); $folder->setParentId(1); return $folder; diff --git a/tests/Integration/Invalidation/CancelInvalidationTest.php b/tests/Integration/Invalidation/CancelInvalidationTest.php index ec9364c..5462afe 100644 --- a/tests/Integration/Invalidation/CancelInvalidationTest.php +++ b/tests/Integration/Invalidation/CancelInvalidationTest.php @@ -45,7 +45,7 @@ protected function setUp(): void ])] public function cancel_invalidation_on_object_update(): void { - $object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + $object = self::arrange(fn () => TestObjectFactory::simpleObject(42)->save()); $object->setContent('Updated test content')->save(); @@ -62,7 +62,7 @@ public function cancel_invalidation_on_object_update(): void ])] public function cancel_invalidation_on_document_update(): void { - $document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + $document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $document->setKey('updated_test_document_page')->save(); @@ -79,7 +79,7 @@ public function cancel_invalidation_on_document_update(): void ])] public function cancel_invalidation_on_asset_update(): void { - $asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + $asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); $asset->setData('Updated test content')->save(); @@ -96,7 +96,7 @@ public function cancel_invalidation_on_asset_update(): void ])] public function cancel_invalidation_on_object_delete(): void { - $object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + $object = self::arrange(fn () => TestObjectFactory::simpleObject(42)->save()); $object->delete(); @@ -113,7 +113,7 @@ public function cancel_invalidation_on_object_delete(): void ])] public function cancel_invalidation_on_document_delete(): void { - $document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + $document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $document->delete(); @@ -130,7 +130,7 @@ public function cancel_invalidation_on_document_delete(): void ])] public function cancel_invalidation_on_asset_delete(): void { - $asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + $asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); $asset->delete(); diff --git a/tests/Integration/Invalidation/InvalidateAdditionalTagTest.php b/tests/Integration/Invalidation/InvalidateAdditionalTagTest.php index 55f8662..402af43 100644 --- a/tests/Integration/Invalidation/InvalidateAdditionalTagTest.php +++ b/tests/Integration/Invalidation/InvalidateAdditionalTagTest.php @@ -53,7 +53,7 @@ protected function setUp(): void ])] public function invalidate_additional_tag_on_object_update(): void { - $object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + $object = self::arrange(fn () => TestObjectFactory::simpleObject(42)->save()); $object->setContent('Updated test content')->save(); @@ -73,7 +73,7 @@ public function invalidate_additional_tag_on_object_update(): void ])] public function does_not_invalidate_additional_tag_on_object_update_when_cache_type_is_disabled(): void { - $object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + $object = self::arrange(fn () => TestObjectFactory::simpleObject(42)->save()); $object->setKey('updated_test_object')->save(); @@ -93,11 +93,11 @@ public function does_not_invalidate_additional_tag_on_object_update_when_cache_t ])] public function invalidate_additional_tag_on_document_update(): void { - $document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + $document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $document->setKey('updated_test_document_page')->save(); - $this->cacheManager->invalidateTags(['d42', 'foo-bar'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['d5', 'foo-bar'])->shouldHaveBeenCalledTimes(1); } /** @@ -113,11 +113,11 @@ public function invalidate_additional_tag_on_document_update(): void ])] public function does_not_invalidate_additional_tag_on_document_update_when_cache_type_is_disabled(): void { - $document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + $document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $document->setKey('updated_test_document_page')->save(); - $this->cacheManager->invalidateTags(['d42', 'foo-bar'])->shouldNotHaveBeenCalled(); + $this->cacheManager->invalidateTags(['d5', 'foo-bar'])->shouldNotHaveBeenCalled(); } /** @@ -133,11 +133,11 @@ public function does_not_invalidate_additional_tag_on_document_update_when_cache ])] public function invalidate_additional_tag_on_asset_update(): void { - $asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + $asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); $asset->setData('Updated test content')->save(); - $this->cacheManager->invalidateTags(['a42', 'foo-bar'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['a5', 'foo-bar'])->shouldHaveBeenCalledTimes(1); } /** @@ -153,11 +153,11 @@ public function invalidate_additional_tag_on_asset_update(): void ])] public function does_not_invalidate_additional_tag_on_asset_update_when_cache_type_is_disabled(): void { - $asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + $asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); $asset->setData('Updated test content')->save(); - $this->cacheManager->invalidateTags(['a42', 'foo-bar'])->shouldNotHaveBeenCalled(); + $this->cacheManager->invalidateTags(['a5', 'foo-bar'])->shouldNotHaveBeenCalled(); } /** @@ -173,7 +173,7 @@ public function does_not_invalidate_additional_tag_on_asset_update_when_cache_ty ])] public function invalidate_additional_tag_on_object_deletion(): void { - $object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + $object = self::arrange(fn () => TestObjectFactory::simpleObject(42)->save()); $object->delete(); @@ -193,7 +193,7 @@ public function invalidate_additional_tag_on_object_deletion(): void ])] public function does_not_invalidate_additional_tag_on_object_deletion_when_cache_type_is_disabled(): void { - $object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + $object = self::arrange(fn () => TestObjectFactory::simpleObject(42)->save()); $object->delete(); @@ -213,11 +213,11 @@ public function does_not_invalidate_additional_tag_on_object_deletion_when_cache ])] public function invalidate_additional_tag_on_asset_deletion(): void { - $asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + $asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); $asset->delete(); - $this->cacheManager->invalidateTags(['a42', 'foo-bar'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['a5', 'foo-bar'])->shouldHaveBeenCalledTimes(1); } /** @@ -233,11 +233,11 @@ public function invalidate_additional_tag_on_asset_deletion(): void ])] public function does_not_invalidate_additional_tag_on_asset_deletion_when_cache_type_is_disabled(): void { - $asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + $asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); $asset->delete(); - $this->cacheManager->invalidateTags(['a42', 'foo-bar'])->shouldNotHaveBeenCalled(); + $this->cacheManager->invalidateTags(['a5', 'foo-bar'])->shouldNotHaveBeenCalled(); } /** @@ -253,11 +253,11 @@ public function does_not_invalidate_additional_tag_on_asset_deletion_when_cache_ ])] public function invalidate_additional_tag_on_document_deletion(): void { - $document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + $document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $document->delete(); - $this->cacheManager->invalidateTags(['d42', 'foo-bar'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['d5', 'foo-bar'])->shouldHaveBeenCalledTimes(1); } /** @@ -273,10 +273,10 @@ public function invalidate_additional_tag_on_document_deletion(): void ])] public function does_not_invalidate_additional_tag_on_document_deletion_when_cache_type_was_disabled(): void { - $document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + $document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $document->delete(); - $this->cacheManager->invalidateTags(['d42', 'foo-bar'])->shouldNotHaveBeenCalled(); + $this->cacheManager->invalidateTags(['d5', 'foo-bar'])->shouldNotHaveBeenCalled(); } } diff --git a/tests/Integration/Invalidation/InvalidateAssetTest.php b/tests/Integration/Invalidation/InvalidateAssetTest.php index e66430d..8098b4d 100644 --- a/tests/Integration/Invalidation/InvalidateAssetTest.php +++ b/tests/Integration/Invalidation/InvalidateAssetTest.php @@ -3,8 +3,10 @@ namespace Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Invalidation; use FOS\HttpCacheBundle\CacheManager; +use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\ArrangeCacheTest; use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\TestAssetFactory; +use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\TestObjectFactory; use Neusta\Pimcore\TestingFramework\Database\ResetDatabase; use Neusta\Pimcore\TestingFramework\Test\Attribute\ConfigureExtension; use Neusta\Pimcore\TestingFramework\Test\ConfigurableKernelTestCase; @@ -34,9 +36,9 @@ protected function setUp(): void $this->cacheManager->invalidateTags(Argument::any())->willReturn($this->cacheManager->reveal()); self::getContainer()->set('fos_http_cache.cache_manager', $this->cacheManager->reveal()); - $this->asset = self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); - $this->folder = self::arrange(fn () => TestAssetFactory::simpleFolder()->save()); - $this->image = self::arrange(fn () => TestAssetFactory::simpleImage()->save()); + $this->asset = self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); + $this->folder = self::arrange(fn () => TestAssetFactory::simpleFolder(12)->save()); + $this->image = self::arrange(fn () => TestAssetFactory::simpleImage(29)->save()); } /** @@ -51,7 +53,7 @@ public function response_is_invalidated_when_asset_is_updated(): void { $this->asset->setData('Updated test content')->save(); - $this->cacheManager->invalidateTags(['a42'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['a5'])->shouldHaveBeenCalledTimes(1); } /** @@ -66,7 +68,7 @@ public function response_is_invalidated_when_asset_is_deleted(): void { $this->asset->delete(); - $this->cacheManager->invalidateTags(['a42'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['a5'])->shouldHaveBeenCalledTimes(1); } /** @@ -137,6 +139,85 @@ public function response_is_not_invalidated_when_asset_type_is_disabled_on_delet $this->cacheManager->invalidateTags(Argument::any())->shouldNotHaveBeenCalled(); } + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'assets' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => true, + ], + ], + ], + 'objects' => true, + ], + ])] + public function dependent_object_is_invalidated_on_asset_update(): void + { + $object = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'test_object_with_image', [$this->image])->save(), + ); + + $this->image->setMimeType('image/png')->save(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($object)->toString()]) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'assets' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => true, + ], + ], + ], + 'objects' => true, + ], + ])] + public function dependent_object_is_invalidated_on_asset_deletion(): void + { + $object = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'test_object_with_image', [$this->image])->save(), + ); + + $this->image->delete(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($object)->toString()]) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => true, + 'assets' => true, + ], + ])] + public function dependent_elements_are_not_invalidated_when_asset_is_updated(): void + { + $object = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'test_object_with_image', [$this->image])->save(), + ); + + $this->asset->setData('Updated test content')->save(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($object)->toString()]) + ->shouldNotHaveBeenCalled(); + } + /** * @test */ diff --git a/tests/Integration/Invalidation/InvalidateDependentElementTest.php b/tests/Integration/Invalidation/InvalidateDependentElementTest.php new file mode 100644 index 0000000..3f181ed --- /dev/null +++ b/tests/Integration/Invalidation/InvalidateDependentElementTest.php @@ -0,0 +1,156 @@ + */ + private ObjectProphecy $cacheManager; + + private TestObject $sourceObject; + + private Page $dependentPage; + + private TestObject $dependentObject; + + protected function setUp(): void + { + $this->cacheManager = $this->prophesize(CacheManager::class); + $this->cacheManager->invalidateTags(Argument::any())->willReturn($this->cacheManager->reveal()); + self::getContainer()->set('fos_http_cache.cache_manager', $this->cacheManager->reveal()); + + $this->sourceObject = self::arrange(static fn () => TestObjectFactory::simpleObject(100, 'source_object')->save()); + $this->dependentPage = self::arrange(fn () => TestDocumentFactory::simplePage(200, 'dep_page', $this->sourceObject)->save()); + $this->dependentObject = self::arrange(fn () => TestObjectFactory::simpleObject(300, 'dep_object', [$this->sourceObject])->save()); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'documents' => true, + ], + ], + ], + 'documents' => true, + ], + ])] + public function dependent_document_is_invalidated_when_source_object_is_updated(): void + { + $this->sourceObject->setContent('Updated content')->save(); + + $this->cacheManager->invalidateTags(['o' . $this->sourceObject->getId()])->shouldHaveBeenCalledOnce(); + $this->cacheManager->invalidateTags(['d' . $this->dependentPage->getId()])->shouldHaveBeenCalledOnce(); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'documents' => true, + ], + ], + ], + 'documents' => [ + 'types' => [ + 'page' => false, // globally disable page type + ], + ], + ], + ])] + public function dependent_document_is_not_invalidated_when_document_type_is_globally_disabled(): void + { + $this->sourceObject->setContent('Updated content')->save(); + + $this->cacheManager->invalidateTags(['o' . $this->sourceObject->getId()])->shouldHaveBeenCalledOnce(); + $this->cacheManager->invalidateTags(['d' . $this->dependentPage->getId()])->shouldNotHaveBeenCalled(); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'documents' => [ + 'enabled' => true, + 'types' => [ + 'page' => false, // disable page in dependent config + ], + ], + ], + ], + ], + 'documents' => true, + ], + ])] + public function dependent_document_is_not_invalidated_when_page_type_is_disabled_in_dependent_config(): void + { + $this->sourceObject->setContent('Updated content')->save(); + + $this->cacheManager->invalidateTags(['o' . $this->sourceObject->getId()])->shouldHaveBeenCalledOnce(); + $this->cacheManager->invalidateTags(['d' . $this->dependentPage->getId()])->shouldNotHaveBeenCalled(); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => [ + 'enabled' => true, + 'classes' => [ + 'TestObject' => false, // disable this class in dependent config + ], + ], + ], + ], + ], + ], + ])] + public function dependent_object_is_not_invalidated_when_class_is_disabled_in_dependent_config(): void + { + $this->sourceObject->setContent('Updated content')->save(); + + $this->cacheManager->invalidateTags(['o' . $this->sourceObject->getId()])->shouldHaveBeenCalledOnce(); + $this->cacheManager->invalidateTags(['o' . $this->dependentObject->getId()])->shouldNotHaveBeenCalled(); + } +} diff --git a/tests/Integration/Invalidation/InvalidateDocumentTest.php b/tests/Integration/Invalidation/InvalidateDocumentTest.php index 1618cbe..f0ce4bd 100644 --- a/tests/Integration/Invalidation/InvalidateDocumentTest.php +++ b/tests/Integration/Invalidation/InvalidateDocumentTest.php @@ -3,8 +3,10 @@ namespace Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Invalidation; use FOS\HttpCacheBundle\CacheManager; +use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\ArrangeCacheTest; use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\TestDocumentFactory; +use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\TestObjectFactory; use Neusta\Pimcore\TestingFramework\Database\ResetDatabase; use Neusta\Pimcore\TestingFramework\Test\Attribute\ConfigureExtension; use Neusta\Pimcore\TestingFramework\Test\ConfigurableKernelTestCase; @@ -36,10 +38,10 @@ protected function setUp(): void $this->cacheManager->invalidateTags(Argument::any())->willReturn($this->cacheManager->reveal()); self::getContainer()->set('fos_http_cache.cache_manager', $this->cacheManager->reveal()); - $this->document = self::arrange(fn () => TestDocumentFactory::simplePage()->save()); - $this->hardlink = self::arrange(fn () => TestDocumentFactory::simpleHardLink()->save()); - $this->email = self::arrange(fn () => TestDocumentFactory::simpleEmail()->save()); - $this->folder = self::arrange(fn () => TestDocumentFactory::simpleFolder()->save()); + $this->document = self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); + $this->hardlink = self::arrange(fn () => TestDocumentFactory::simpleHardLink(12)->save()); + $this->email = self::arrange(fn () => TestDocumentFactory::simpleEmail(29)->save()); + $this->folder = self::arrange(fn () => TestDocumentFactory::simpleFolder(70)->save()); } /** @@ -54,7 +56,39 @@ public function response_is_invalidated_when_document_is_updated(): void { $this->document->setKey('updated_test_document_page')->save(); - $this->cacheManager->invalidateTags(['d42'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['d5'])->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'documents' => true, + ], + ], + ], + 'documents' => true, + ], + ])] + public function dependent_document_is_invalidated_on_object_update(): void + { + $dependent = self::arrange( + fn () => TestObjectFactory::simpleObject(12)->save(), + ); + $document = self::arrange( + fn () => TestDocumentFactory::simplePage(96, 'other_test_document_page', $dependent)->save(), + ); + + $dependent->setContent('Updated test content')->save(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($document)->toString()]) + ->shouldHaveBeenCalledTimes(1); } /** @@ -69,7 +103,39 @@ public function response_is_invalidated_when_document_is_deleted(): void { $this->document->delete(); - $this->cacheManager->invalidateTags(['d42'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags(['d5'])->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'documents' => true, + ], + ], + ], + 'documents' => true, + ], + ])] + public function dependent_document_is_invalidated_on_object_deletion(): void + { + $dependent = self::arrange( + fn () => TestObjectFactory::simpleObject(12)->save(), + ); + $document = self::arrange( + fn () => TestDocumentFactory::simplePage(96, 'other_test_document_page', $dependent)->save(), + ); + + $dependent->delete(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($document)->toString()]) + ->shouldHaveBeenCalledTimes(1); } /** @@ -200,6 +266,88 @@ public function response_is_not_invalidated_when_document_type_is_disabled_on_de $this->cacheManager->invalidateTags(Argument::any())->shouldNotHaveBeenCalled(); } + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'documents' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => true, + ], + ], + ], + 'objects' => true, + ], + ])] + public function dependent_object_is_invalidated_on_document_update(): void + { + $object = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'test_object_with_page', [$this->document])->save(), + ); + + $this->document->setKey('updated_test_document_page')->save(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($object)->toString()]) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'documents' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => true, + ], + ], + ], + 'objects' => true, + ], + ])] + public function dependent_object_is_invalidated_on_document_deletion(): void + { + $object = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'test_object_with_page', [$this->document])->save(), + ); + + $this->document->delete(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($object)->toString()]) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => true, + 'documents' => true, + ], + ])] + public function dependent_elements_are_not_invalidated_when_document_is_updated(): void + { + $object = self::arrange( + fn () => TestObjectFactory::simpleObject(12)->save(), + ); + $document = self::arrange( + fn () => TestDocumentFactory::simplePage(96, 'other_test_document_page', $object)->save(), + ); + + $document->setKey('updated_other_test_document_page')->save(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($object)->toString()]) + ->shouldNotHaveBeenCalled(); + } + /** * @test */ diff --git a/tests/Integration/Invalidation/InvalidateObjectTest.php b/tests/Integration/Invalidation/InvalidateObjectTest.php index a1c409f..a5f0733 100644 --- a/tests/Integration/Invalidation/InvalidateObjectTest.php +++ b/tests/Integration/Invalidation/InvalidateObjectTest.php @@ -3,13 +3,14 @@ namespace Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Invalidation; use FOS\HttpCacheBundle\CacheManager; +use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\ArrangeCacheTest; use Neusta\Pimcore\HttpCacheBundle\Tests\Integration\Helpers\TestObjectFactory; use Neusta\Pimcore\TestingFramework\Database\ResetDatabase; use Neusta\Pimcore\TestingFramework\Test\Attribute\ConfigureExtension; use Neusta\Pimcore\TestingFramework\Test\ConfigurableKernelTestCase; use Pimcore\Model\DataObject; -use Pimcore\Model\DataObject\TestDataObject; +use Pimcore\Model\DataObject\TestObject; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; @@ -23,9 +24,9 @@ final class InvalidateObjectTest extends ConfigurableKernelTestCase /** @var ObjectProphecy */ private ObjectProphecy $cacheManager; - private TestDataObject $object; + private TestObject $object; - private TestDataObject $variant; + private TestObject $variant; private DataObject\Folder $folder; @@ -35,9 +36,9 @@ protected function setUp(): void $this->cacheManager->invalidateTags(Argument::any())->willReturn($this->cacheManager->reveal()); self::getContainer()->set('fos_http_cache.cache_manager', $this->cacheManager->reveal()); - $this->object = self::arrange(fn () => TestObjectFactory::simpleObject()->save()); - $this->folder = self::arrange(fn () => TestObjectFactory::simpleFolder()->save()); - $this->variant = self::arrange(fn () => TestObjectFactory::simpleVariant()->save()); + $this->object = self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); + $this->folder = self::arrange(fn () => TestObjectFactory::simpleFolder(29)->save()); + $this->variant = self::arrange(fn () => TestObjectFactory::simpleVariant(70)->save()); } /** @@ -52,7 +53,36 @@ public function response_is_invalidated_when_object_is_updated(): void { $this->object->setContent('Updated test content')->save(); - $this->cacheManager->invalidateTags(['o42'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags([CacheTag::fromElement($this->object)->toString()]) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => true, + ], + ], + ], + ], + ])] + public function dependent_object_is_invalidated_on_object_update(): void + { + $dependent = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'other_test_object', [$this->object])->save(), + ); + + $this->object->setContent('Updated test content')->save(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($dependent)->toString()]) + ->shouldHaveBeenCalledTimes(1); } /** @@ -67,7 +97,36 @@ public function response_is_invalidated_when_object_is_deleted(): void { $this->object->delete(); - $this->cacheManager->invalidateTags(['o42'])->shouldHaveBeenCalledTimes(1); + $this->cacheManager->invalidateTags([CacheTag::fromElement($this->object)->toString()]) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + #[ConfigureExtension('neusta_pimcore_http_cache', [ + 'elements' => [ + 'objects' => [ + 'enabled' => true, + 'invalidate_dependent_elements' => [ + 'enabled' => true, + 'types' => [ + 'objects' => true, + ], + ], + ], + ], + ])] + public function dependent_object_is_invalidated_on_object_deletion(): void + { + $dependent = self::arrange( + fn () => TestObjectFactory::simpleObject(12, 'other_test_object', [$this->object])->save(), + ); + + $this->object->delete(); + + $this->cacheManager->invalidateTags([CacheTag::fromElement($dependent)->toString()]) + ->shouldHaveBeenCalledTimes(1); } /** @@ -148,7 +207,7 @@ public function response_is_not_invalidated_when_specified_type_is_disabled_on_d 'objects' => [ 'enabled' => true, 'classes' => [ - 'TestDataObject' => false, + 'TestObject' => false, ], ], ], @@ -168,7 +227,7 @@ public function response_is_not_invalidated_when_custom_data_object_class_is_dis 'objects' => [ 'enabled' => true, 'classes' => [ - 'TestDataObject' => false, + 'TestObject' => false, ], ], ], diff --git a/tests/Integration/Tagging/CollectTagsDataTest.php b/tests/Integration/Tagging/CollectTagsDataTest.php index 6a83a95..45453af 100644 --- a/tests/Integration/Tagging/CollectTagsDataTest.php +++ b/tests/Integration/Tagging/CollectTagsDataTest.php @@ -46,7 +46,7 @@ protected function setUp(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_document_route.php')] public function collect_tags_for_type_document(): void { - self::arrange(fn () => TestDocumentFactory::simplePage())->save(); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); $this->client->enableProfiler(); @@ -54,8 +54,8 @@ public function collect_tags_for_type_document(): void $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); self::assertInstanceOf(DataCollector::class, $dataCollector); - self::assertSame( - [['tag' => 'd1', 'type' => 'document'], ['tag' => 'd42', 'type' => 'document']], + self::assertEqualsCanonicalizing( + [['tag' => 'd1', 'type' => 'document'], ['tag' => 'd5', 'type' => 'document']], $dataCollector->getTags(), ); } @@ -71,16 +71,16 @@ public function collect_tags_for_type_document(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function collect_tags_for_type_object(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); self::assertInstanceOf(DataCollector::class, $dataCollector); self::assertSame( - [['tag' => 'o42', 'type' => 'object']], + [['tag' => 'o5', 'type' => 'object']], $dataCollector->getTags(), ); } @@ -96,16 +96,16 @@ public function collect_tags_for_type_object(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_asset_route.php')] public function collect_tags_of_type_asset(): void { - self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); - $this->client->request('GET', '/get-asset?id=42'); + $this->client->request('GET', '/get-asset?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); self::assertInstanceOf(DataCollector::class, $dataCollector); self::assertSame( - [['tag' => 'a42', 'type' => 'asset']], + [['tag' => 'a5', 'type' => 'asset']], $dataCollector->getTags(), ); } @@ -124,7 +124,7 @@ public function collect_tags_of_type_asset(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function collect_tags_of_type_custom(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); self::getContainer()->get('event_dispatcher')->addListener( ElementTaggingEvent::class, @@ -133,7 +133,7 @@ public function collect_tags_of_type_custom(): void ), ); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); @@ -156,9 +156,9 @@ public function collect_tags_of_type_custom(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function does_not_collect_tags_when_type_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); @@ -178,10 +178,10 @@ public function does_not_collect_tags_when_type_is_disabled(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function does_not_collect_tags_when_caching_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); self::getContainer()->get(CacheActivator::class)->deactivateCaching(); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); @@ -206,9 +206,9 @@ public function does_not_collect_tags_when_caching_is_disabled(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function does_not_collect_tags_when_object_type_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleVariant()->save()); + self::arrange(fn () => TestObjectFactory::simpleVariant(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); @@ -224,7 +224,7 @@ public function does_not_collect_tags_when_object_type_is_disabled(): void 'elements' => [ 'objects' => [ 'classes' => [ - 'TestDataObject' => false, + 'TestObject' => false, ], 'enabled' => true, ], @@ -233,9 +233,9 @@ public function does_not_collect_tags_when_object_type_is_disabled(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function does_not_collect_tags_when_object_class_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); @@ -261,9 +261,9 @@ public function does_not_collect_tags_when_object_class_is_disabled(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function does_not_collect_tags_when_profiler_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); self::assertFalse($this->client->getProfile()); @@ -278,9 +278,9 @@ public function does_not_collect_tags_when_profiler_is_disabled(): void #[ConfigureRoute(__DIR__ . '/../Fixtures/get_object_route.php')] public function does_not_collect_tags_when_collect_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $this->client->enableProfiler(); $dataCollector = $this->client->getProfile()->getCollector('pimcore_http_cache'); diff --git a/tests/Integration/Tagging/TagAdditionalTagTest.php b/tests/Integration/Tagging/TagAdditionalTagTest.php index 5abf34f..1674ac3 100644 --- a/tests/Integration/Tagging/TagAdditionalTagTest.php +++ b/tests/Integration/Tagging/TagAdditionalTagTest.php @@ -44,24 +44,24 @@ protected function setUp(): void ])] public function response_is_tagged_with_additional_tag_when_asset_is_loaded(): void { - self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); - self::arrange(fn () => TestAssetFactory::simpleImage()->save()); + self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); + self::arrange(fn () => TestAssetFactory::simpleImage(29)->save()); self::getContainer()->get('event_dispatcher')->addListener( ElementTaggingEvent::class, fn (ElementTaggingEvent $event) => $event->addTag( - CacheTag::fromString('17', new ElementCacheType(ElementType::Asset)), + CacheTag::fromString('29', new ElementCacheType(ElementType::Asset)), ), ); - $this->client->request('GET', '/get-asset?id=42'); + $this->client->request('GET', '/get-asset?id=5'); $response = $this->client->getResponse(); self::assertSame('This is the content of the test asset.', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringContainsString('a17', $response->headers->get('X-Cache-Tags')); + self::assertStringContainsString('a29', $response->headers->get('X-Cache-Tags')); } /** @@ -74,13 +74,13 @@ public function response_is_tagged_with_additional_tag_when_asset_is_loaded(): v ])] public function response_is_tagged_with_additional_tag_when_document_is_loaded(): void { - self::arrange(fn () => TestDocumentFactory::simplePage()->save()); - self::arrange(fn () => TestDocumentFactory::simpleSnippet()->save()); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); + self::arrange(fn () => TestDocumentFactory::simpleSnippet(12)->save()); self::getContainer()->get('event_dispatcher')->addListener( ElementTaggingEvent::class, fn (ElementTaggingEvent $event) => $event->addTag( - CacheTag::fromString('23', new ElementCacheType(ElementType::Document)), + CacheTag::fromString('12', new ElementCacheType(ElementType::Document)), ), ); @@ -91,7 +91,7 @@ public function response_is_tagged_with_additional_tag_when_document_is_loaded() self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringContainsString('d23', $response->headers->get('X-Cache-Tags')); + self::assertStringContainsString('d12', $response->headers->get('X-Cache-Tags')); } /** @@ -104,24 +104,24 @@ public function response_is_tagged_with_additional_tag_when_document_is_loaded() ])] public function response_is_tagged_with_additional_tag_when_object_is_loaded(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); - self::arrange(fn () => TestObjectFactory::simpleVariant()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); + self::arrange(fn () => TestObjectFactory::simpleVariant(12)->save()); self::getContainer()->get('event_dispatcher')->addListener( ElementTaggingEvent::class, fn (ElementTaggingEvent $event) => $event->addTag( - CacheTag::fromString('17', new ElementCacheType(ElementType::Object)), + CacheTag::fromString('12', new ElementCacheType(ElementType::Object)), ), ); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringContainsString('o17', $response->headers->get('X-Cache-Tags')); + self::assertStringContainsString('o12', $response->headers->get('X-Cache-Tags')); } /** @@ -137,7 +137,7 @@ public function response_is_tagged_with_additional_tag_when_object_is_loaded(): ])] public function response_is_tagged_with_custom_tag_when_element_is_loaded(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); self::getContainer()->get('event_dispatcher')->addListener( ElementTaggingEvent::class, @@ -146,7 +146,7 @@ public function response_is_tagged_with_custom_tag_when_element_is_loaded(): voi ), ); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); diff --git a/tests/Integration/Tagging/TagAssetTest.php b/tests/Integration/Tagging/TagAssetTest.php index d1aa49a..b036434 100644 --- a/tests/Integration/Tagging/TagAssetTest.php +++ b/tests/Integration/Tagging/TagAssetTest.php @@ -34,16 +34,16 @@ protected function setUp(): void ])] public function response_is_tagged_with_expected_tags_when_asset_is_loaded(): void { - self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); - $this->client->request('GET', '/get-asset?id=42'); + $this->client->request('GET', '/get-asset?id=5'); $response = $this->client->getResponse(); self::assertSame('This is the content of the test asset.', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertSame('a42', $response->headers->get('X-Cache-Tags')); + self::assertSame('a5', $response->headers->get('X-Cache-Tags')); } /** @@ -56,9 +56,9 @@ public function response_is_tagged_with_expected_tags_when_asset_is_loaded(): vo ])] public function response_is_not_tagged_when_assets_is_not_enabled(): void { - self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); - $this->client->request('GET', '/get-asset?id=42'); + $this->client->request('GET', '/get-asset?id=5'); $response = $this->client->getResponse(); self::assertSame('This is the content of the test asset.', $response->getContent()); @@ -78,10 +78,10 @@ public function response_is_not_tagged_when_assets_is_not_enabled(): void ])] public function response_is_not_tagged_when_caching_is_deactivated(): void { - self::arrange(fn () => TestAssetFactory::simpleAsset()->save()); + self::arrange(fn () => TestAssetFactory::simpleAsset(5)->save()); self::getContainer()->get(CacheActivator::class)->deactivateCaching(); - $this->client->request('GET', '/get-asset?id=42'); + $this->client->request('GET', '/get-asset?id=5'); $response = $this->client->getResponse(); self::assertSame('This is the content of the test asset.', $response->getContent()); @@ -101,9 +101,9 @@ public function response_is_not_tagged_when_caching_is_deactivated(): void ])] public function response_is_not_tagged_when_asset_is_of_type_folder(): void { - self::arrange(fn () => TestAssetFactory::simpleFolder()->save()); + self::arrange(fn () => TestAssetFactory::simpleFolder(12)->save()); - $this->client->request('GET', '/get-asset?id=23'); + $this->client->request('GET', '/get-asset?id=12'); $response = $this->client->getResponse(); self::assertSame('', $response->getContent()); @@ -128,9 +128,9 @@ public function response_is_not_tagged_when_asset_is_of_type_folder(): void ])] public function response_is_not_tagged_for_specified_asset_type(): void { - self::arrange(fn () => TestAssetFactory::simpleImage()->save()); + self::arrange(fn () => TestAssetFactory::simpleImage(29)->save()); - $this->client->request('GET', '/get-asset?id=17'); + $this->client->request('GET', '/get-asset?id=29'); $response = $this->client->getResponse(); self::assertSame('', $response->getContent()); diff --git a/tests/Integration/Tagging/TagDocumentTest.php b/tests/Integration/Tagging/TagDocumentTest.php index e6b1fd3..cda03ec 100644 --- a/tests/Integration/Tagging/TagDocumentTest.php +++ b/tests/Integration/Tagging/TagDocumentTest.php @@ -34,7 +34,7 @@ protected function setUp(): void ])] public function response_is_tagged_with_expected_tags_when_page_is_loaded(): void { - self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); @@ -43,7 +43,7 @@ public function response_is_tagged_with_expected_tags_when_page_is_loaded(): voi self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringContainsString('d42', $response->headers->get('X-Cache-Tags')); + self::assertStringContainsString('d5', $response->headers->get('X-Cache-Tags')); } /** @@ -56,16 +56,16 @@ public function response_is_tagged_with_expected_tags_when_page_is_loaded(): voi ])] public function response_is_tagged_with_expected_tags_when_snippet_is_loaded(): void { - self::arrange(fn () => TestDocumentFactory::simpleSnippet()->save()); + self::arrange(fn () => TestDocumentFactory::simpleSnippet(12)->save()); - $this->client->request('GET', '/get-document?id=23'); + $this->client->request('GET', '/get-document?id=12'); $response = $this->client->getResponse(); self::assertSame('Document with key: test_document_snippet', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringContainsString('d23', $response->headers->get('X-Cache-Tags')); + self::assertStringContainsString('d12', $response->headers->get('X-Cache-Tags')); } /** @@ -78,16 +78,16 @@ public function response_is_tagged_with_expected_tags_when_snippet_is_loaded(): ])] public function response_is_not_tagged_when_document_type_is_email(): void { - self::arrange(fn () => TestDocumentFactory::simpleEmail()->save()); + self::arrange(fn () => TestDocumentFactory::simpleEmail(29)->save()); - $this->client->request('GET', '/get-document?id=17'); + $this->client->request('GET', '/get-document?id=29'); $response = $this->client->getResponse(); - self::assertSame('Document with key: test_document_link', $response->getContent()); + self::assertSame('Document with key: test_document_email', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringNotContainsString('d17', $response->headers->get('X-Cache-Tags')); + self::assertStringNotContainsString('d29', $response->headers->get('X-Cache-Tags')); } /** @@ -100,16 +100,16 @@ public function response_is_not_tagged_when_document_type_is_email(): void ])] public function response_is_not_tagged_when_document_type_is_hard_link(): void { - self::arrange(fn () => TestDocumentFactory::simpleHardLink()->save()); + self::arrange(fn () => TestDocumentFactory::simpleHardLink(12)->save()); - $this->client->request('GET', '/get-document?id=33'); + $this->client->request('GET', '/get-document?id=12'); $response = $this->client->getResponse(); self::assertSame('Document with key: test_document_hard_link', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringNotContainsString('d33', $response->headers->get('X-Cache-Tags')); + self::assertStringNotContainsString('d12', $response->headers->get('X-Cache-Tags')); } /** @@ -122,16 +122,16 @@ public function response_is_not_tagged_when_document_type_is_hard_link(): void ])] public function response_is_not_tagged_when_document_type_is_folder(): void { - self::arrange(fn () => TestDocumentFactory::simpleFolder()->save()); + self::arrange(fn () => TestDocumentFactory::simpleFolder(70)->save()); - $this->client->request('GET', '/get-document?id=97'); + $this->client->request('GET', '/get-document?id=70'); $response = $this->client->getResponse(); self::assertSame('Document with key: test_document_folder', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertStringNotContainsString('d97', $response->headers->get('X-Cache-Tags')); + self::assertStringNotContainsString('d70', $response->headers->get('X-Cache-Tags')); } /** @@ -144,7 +144,7 @@ public function response_is_not_tagged_when_document_type_is_folder(): void ])] public function response_is_not_tagged_when_documents_is_not_enabled(): void { - self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); @@ -166,7 +166,7 @@ public function response_is_not_tagged_when_documents_is_not_enabled(): void ])] public function response_is_not_tagged_when_caching_is_deactivated(): void { - self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); self::getContainer()->get(CacheActivator::class)->deactivateCaching(); $this->client->request('GET', '/test_document_page'); @@ -189,7 +189,7 @@ public function response_is_not_tagged_when_caching_is_deactivated(): void ])] public function response_is_tagged_with_root_document_tag_when_loaded(): void { - self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); @@ -215,7 +215,7 @@ public function response_is_tagged_with_root_document_tag_when_loaded(): void ])] public function response_is_not_tagged_when_type_is_disabled(): void { - self::arrange(fn () => TestDocumentFactory::simplePage()->save()); + self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()); $this->client->request('GET', '/test_document_page'); diff --git a/tests/Integration/Tagging/TagObjectTest.php b/tests/Integration/Tagging/TagObjectTest.php index a502917..7386464 100644 --- a/tests/Integration/Tagging/TagObjectTest.php +++ b/tests/Integration/Tagging/TagObjectTest.php @@ -34,16 +34,16 @@ protected function setUp(): void ])] public function response_is_tagged_with_expected_tags_when_object_is_loaded(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertSame('o42', $response->headers->get('X-Cache-Tags')); + self::assertSame('o5', $response->headers->get('X-Cache-Tags')); } /** @@ -56,9 +56,9 @@ public function response_is_tagged_with_expected_tags_when_object_is_loaded(): v ])] public function response_is_not_tagged_when_objects_is_not_enabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); @@ -78,10 +78,10 @@ public function response_is_not_tagged_when_objects_is_not_enabled(): void ])] public function response_is_not_tagged_when_caching_is_deactivated(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); self::getContainer()->get(CacheActivator::class)->deactivateCaching(); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); @@ -106,9 +106,9 @@ public function response_is_not_tagged_when_caching_is_deactivated(): void ])] public function response_is_not_tagged_when_object_type_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleVariant()->save()); + self::arrange(fn () => TestObjectFactory::simpleVariant(5)->save()); - $this->client->request('GET', '/get-object?id=17'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); @@ -133,16 +133,16 @@ public function response_is_not_tagged_when_object_type_is_disabled(): void ])] public function response_ist_tagged_when_object_type_is_enabled(): void { - self::arrange(fn () => TestObjectFactory::simpleVariant()->save()); + self::arrange(fn () => TestObjectFactory::simpleVariant(12)->save()); - $this->client->request('GET', '/get-object?id=17'); + $this->client->request('GET', '/get-object?id=12'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertSame('o17', $response->headers->get('X-Cache-Tags')); + self::assertSame('o12', $response->headers->get('X-Cache-Tags')); } /** @@ -152,7 +152,7 @@ public function response_ist_tagged_when_object_type_is_enabled(): void 'elements' => [ 'objects' => [ 'classes' => [ - 'TestDataObject' => false, + 'TestObject' => false, ], 'enabled' => true, ], @@ -160,9 +160,9 @@ public function response_ist_tagged_when_object_type_is_enabled(): void ])] public function response_is_not_tagged_when_object_class_is_disabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); @@ -179,7 +179,7 @@ public function response_is_not_tagged_when_object_class_is_disabled(): void 'elements' => [ 'objects' => [ 'classes' => [ - 'TestDataObject' => true, + 'TestObject' => true, ], 'enabled' => true, ], @@ -187,15 +187,15 @@ public function response_is_not_tagged_when_object_class_is_disabled(): void ])] public function response_is_tagged_when_object_class_is_enabled(): void { - self::arrange(fn () => TestObjectFactory::simpleObject()->save()); + self::arrange(fn () => TestObjectFactory::simpleObject(5)->save()); - $this->client->request('GET', '/get-object?id=42'); + $this->client->request('GET', '/get-object?id=5'); $response = $this->client->getResponse(); self::assertSame('Test content', $response->getContent()); self::assertSame(200, $response->getStatusCode()); self::assertTrue($response->headers->getCacheControlDirective('public')); self::assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); - self::assertSame('o42', $response->headers->get('X-Cache-Tags')); + self::assertSame('o5', $response->headers->get('X-Cache-Tags')); } } diff --git a/tests/Unit/Cache/CacheTagChecker/Element/AssetCacheTagCheckerTest.php b/tests/Unit/Cache/CacheTagChecker/Element/AssetCacheTagCheckerTest.php index e128923..03887d8 100644 --- a/tests/Unit/Cache/CacheTagChecker/Element/AssetCacheTagCheckerTest.php +++ b/tests/Unit/Cache/CacheTagChecker/Element/AssetCacheTagCheckerTest.php @@ -5,6 +5,7 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use PHPUnit\Framework\TestCase; use Pimcore\Model\Asset; use Prophecy\PhpUnit\ProphecyTrait; @@ -30,7 +31,7 @@ public function it_returns_false_when_asset_is_disabled(): void $asset = $this->prophesize(Asset::class); $elementCacheTagChecker = new CacheTagChecker\Element\AssetCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => false, 'types' => []], + config: ElementsConfig::fromArray(['assets' => ['enabled' => false, 'types' => []]]), ); $asset->getId()->willReturn(42); @@ -48,7 +49,7 @@ public function it_returns_false_when_asset_does_not_exist(): void $asset = $this->prophesize(Asset::class); $elementCacheTagChecker = new CacheTagChecker\Element\AssetCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => true]], + config: ElementsConfig::fromArray(['assets' => ['enabled' => true, 'types' => ['foo' => true]]]), ); $asset->getId()->willReturn(42); @@ -67,7 +68,7 @@ public function it_returns_false_when_asset_type_is_disabled(): void $asset = $this->prophesize(Asset::class); $elementCacheTagChecker = new CacheTagChecker\Element\AssetCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => false]], + config: ElementsConfig::fromArray(['assets' => ['enabled' => true, 'types' => ['foo' => false]]]), ); $asset->getId()->willReturn(42); @@ -87,7 +88,7 @@ public function it_returns_true_when_asset_type_is_enabled(): void $asset = $this->prophesize(Asset::class); $elementCacheTagChecker = new CacheTagChecker\Element\AssetCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => true]], + config: ElementsConfig::fromArray(['assets' => ['enabled' => true, 'types' => ['foo' => true]]]), ); $asset->getId()->willReturn(42); @@ -107,7 +108,7 @@ public function it_returns_true_when_asset_type_is_not_disabled(): void $asset = $this->prophesize(Asset::class); $elementCacheTagChecker = new CacheTagChecker\Element\AssetCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => false]], + config: ElementsConfig::fromArray(['assets' => ['enabled' => true, 'types' => ['foo' => false]]]), ); $asset->getId()->willReturn(42); diff --git a/tests/Unit/Cache/CacheTagChecker/Element/DocumentCacheTagCheckerTest.php b/tests/Unit/Cache/CacheTagChecker/Element/DocumentCacheTagCheckerTest.php index 6c9d3f7..77076fe 100644 --- a/tests/Unit/Cache/CacheTagChecker/Element/DocumentCacheTagCheckerTest.php +++ b/tests/Unit/Cache/CacheTagChecker/Element/DocumentCacheTagCheckerTest.php @@ -5,6 +5,7 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker\Element\DocumentCacheTagChecker; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use PHPUnit\Framework\TestCase; use Pimcore\Model\Document; use Prophecy\PhpUnit\ProphecyTrait; @@ -30,7 +31,7 @@ public function it_returns_false_when_document_is_disabled(): void $document = $this->prophesize(Document::class); $elementCacheTagChecker = new DocumentCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => false, 'types' => []], + config: ElementsConfig::fromArray(['documents' => ['enabled' => false, 'types' => []]]), ); $document->getId()->willReturn(42); @@ -48,7 +49,7 @@ public function it_returns_false_when_document_does_not_exist(): void $document = $this->prophesize(Document::class); $elementCacheTagChecker = new DocumentCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => []], + config: ElementsConfig::fromArray(['documents' => ['enabled' => true, 'types' => []]]), ); $document->getId()->willReturn(42); @@ -67,7 +68,7 @@ public function it_returns_false_when_document_type_is_disabled(): void $document = $this->prophesize(Document::class); $elementCacheTagChecker = new DocumentCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => false]], + config: ElementsConfig::fromArray(['documents' => ['enabled' => true, 'types' => ['foo' => false]]]), ); $document->getId()->willReturn(42); @@ -88,7 +89,7 @@ public function it_returns_true_when_document_type_is_enabled(): void $document = $this->prophesize(Document::class); $elementCacheTagChecker = new DocumentCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => true]], + config: ElementsConfig::fromArray(['documents' => ['enabled' => true, 'types' => ['foo' => true]]]), ); $document->getId()->willReturn(42); @@ -109,7 +110,7 @@ public function it_returns_true_when_document_type_is_not_disabled(): void $document = $this->prophesize(Document::class); $elementCacheTagChecker = new DocumentCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => ['foo' => false]], + config: ElementsConfig::fromArray(['documents' => ['enabled' => true, 'types' => ['foo' => false]]]), ); $document->getId()->willReturn(42); diff --git a/tests/Unit/Cache/CacheTagChecker/Element/ObjectCacheTagCheckerTest.php b/tests/Unit/Cache/CacheTagChecker/Element/ObjectCacheTagCheckerTest.php index 103a6f2..f70dcfe 100644 --- a/tests/Unit/Cache/CacheTagChecker/Element/ObjectCacheTagCheckerTest.php +++ b/tests/Unit/Cache/CacheTagChecker/Element/ObjectCacheTagCheckerTest.php @@ -5,6 +5,7 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker\Element\ObjectCacheTagChecker; use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementsConfig; use PHPUnit\Framework\TestCase; use Pimcore\Model\DataObject; use Pimcore\Model\DataObject\AbstractObject; @@ -31,7 +32,7 @@ public function it_returns_false_when_object_is_disabled(): void $object = $this->prophesize(DataObject::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => false, 'types' => [], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => false, 'types' => [], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -49,7 +50,7 @@ public function it_returns_false_when_object_does_not_exist(): void $object = $this->prophesize(DataObject::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -68,7 +69,7 @@ public function it_returns_false_when_object_type_is_disabled(): void $object = $this->prophesize(DataObject::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [AbstractObject::OBJECT_TYPE_FOLDER => false], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [AbstractObject::OBJECT_TYPE_FOLDER => false], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -88,7 +89,7 @@ public function it_returns_true_when_object_type_is_enabled(): void $object = $this->prophesize(DataObject::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [AbstractObject::OBJECT_TYPE_VARIANT => true], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [AbstractObject::OBJECT_TYPE_VARIANT => true], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -108,7 +109,7 @@ public function it_returns_true_when_object_type_is_not_disabled(): void $object = $this->prophesize(DataObject::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [AbstractObject::OBJECT_TYPE_FOLDER => false], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [AbstractObject::OBJECT_TYPE_FOLDER => false], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -128,7 +129,7 @@ public function it_returns_true_when_object_is_not_concrete(): void $object = $this->prophesize(DataObject::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -148,7 +149,7 @@ public function it_returns_true_when_class_is_not_disabled(): void $object = $this->prophesize(DataObject\Concrete::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [], 'classes' => []], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [], 'classes' => []]]), ); $object->getId()->willReturn(42); @@ -169,7 +170,7 @@ public function it_returns_false_when_class_is_disabled(): void $object = $this->prophesize(DataObject\Concrete::class); $elementCacheTagChecker = new ObjectCacheTagChecker( $this->elementRepository->reveal(), - config: ['enabled' => true, 'types' => [], 'classes' => ['Foo' => false]], + config: ElementsConfig::fromArray(['objects' => ['enabled' => true, 'types' => [], 'classes' => ['Foo' => false]]]), ); $object->getId()->willReturn(42); diff --git a/tests/Unit/Element/DependentElementFinderTest.php b/tests/Unit/Element/DependentElementFinderTest.php new file mode 100755 index 0000000..a338b1a --- /dev/null +++ b/tests/Unit/Element/DependentElementFinderTest.php @@ -0,0 +1,708 @@ + */ + private ObjectProphecy $elementRepository; + + protected function setUp(): void + { + $this->elementRepository = $this->prophesize(ElementRepository::class); + } + + /** + * @test + */ + public function findFor_returns_empty_when_dependent_elements_are_disabled(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([]), + ); + + $element = $this->prophesize(TestObject::class); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + $this->elementRepository->findObject(Argument::any())->shouldNotHaveBeenCalled(); + } + + /** + * @test + */ + public function findFor_skips_entries_without_id_or_type(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependency->getRequiredBy()->willReturn([ + ['type' => 'object'], // missing id + ['id' => 23], // missing type + [], // missing both + ]); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + $this->elementRepository->findObject(Argument::any())->shouldNotHaveBeenCalled(); + } + + /** + * @test + */ + public function findFor_skips_entries_with_unknown_type(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'unknown']]); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + $this->elementRepository->findObject(Argument::any())->shouldNotHaveBeenCalled(); + } + + /** + * @test + */ + public function findFor_skips_entries_when_dependent_element_type_is_disabled(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => false]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + $this->elementRepository->findObject(Argument::any())->shouldNotHaveBeenCalled(); + } + + /** + * @test + */ + public function findFor_skips_dependent_element_when_not_found(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn(null); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_returns_dependent_object(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentElement = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentElement->getType()->willReturn('object'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentElement->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentElement->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_returns_all_dependent_elements(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependent1 = $this->prophesize(DataObject::class); + $dependent2 = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependent1->getType()->willReturn('object'); + $dependent2->getType()->willReturn('object'); + $dependency->getRequiredBy()->willReturn([ + ['id' => 11, 'type' => 'object'], + ['id' => 22, 'type' => 'object'], + ]); + $this->elementRepository->findObject(11)->willReturn($dependent1->reveal()); + $this->elementRepository->findObject(22)->willReturn($dependent2->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(2, $result); + self::assertSame($dependent1->reveal(), $result[0]); + self::assertSame($dependent2->reveal(), $result[1]); + } + + /** + * @test + */ + public function findFor_returns_dependent_document(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['documents' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentDocument = $this->prophesize(Document::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentDocument->getType()->willReturn('page'); + $dependency->getRequiredBy()->willReturn([['id' => 5, 'type' => 'document']]); + $this->elementRepository->findDocument(5)->willReturn($dependentDocument->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentDocument->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_returns_dependent_asset(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['assets' => true]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentAsset = $this->prophesize(Asset::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentAsset->getType()->willReturn('image'); + $dependency->getRequiredBy()->willReturn([['id' => 7, 'type' => 'asset']]); + $this->elementRepository->findAsset(7)->willReturn($dependentAsset->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentAsset->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_returns_dependent_element_when_source_is_an_asset(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['assets' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(Asset::class); + $dependency = $this->prophesize(Dependency::class); + $dependentObject = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentObject->getType()->willReturn('object'); + $dependency->getRequiredBy()->willReturn([['id' => 9, 'type' => 'object']]); + $this->elementRepository->findObject(9)->willReturn($dependentObject->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentObject->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_returns_dependent_element_when_source_is_a_document(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['documents' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]]]]), + ); + + $element = $this->prophesize(Document::class); + $dependency = $this->prophesize(Dependency::class); + $dependentObject = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentObject->getType()->willReturn('object'); + $dependency->getRequiredBy()->willReturn([['id' => 14, 'type' => 'object']]); + $this->elementRepository->findObject(14)->willReturn($dependentObject->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentObject->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_returns_dependent_asset_with_explicitly_enabled_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['assets' => ['enabled' => true, 'types' => ['image' => true]]]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentAsset = $this->prophesize(Asset::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentAsset->getType()->willReturn('image'); + $dependency->getRequiredBy()->willReturn([['id' => 7, 'type' => 'asset']]); + $this->elementRepository->findAsset(7)->willReturn($dependentAsset->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentAsset->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_returns_dependent_document_with_explicitly_enabled_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['documents' => ['enabled' => true, 'types' => ['page' => true]]]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentDocument = $this->prophesize(Document::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentDocument->getType()->willReturn('page'); + $dependency->getRequiredBy()->willReturn([['id' => 5, 'type' => 'document']]); + $this->elementRepository->findDocument(5)->willReturn($dependentDocument->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertCount(1, $result); + self::assertSame($dependentDocument->reveal(), $result[0]); + } + + /** + * @test + */ + public function findFor_skips_dependent_object_with_disabled_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]], 'types' => ['folder' => false]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentFolder = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_object_with_disabled_class(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => true]], 'classes' => ['IgnoredClass' => false]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentObject = $this->prophesize(Concrete::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentObject->getType()->willReturn('object'); + $dependentObject->getClassName()->willReturn('IgnoredClass'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentObject->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_document_with_disabled_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['documents' => true]]], 'documents' => ['types' => ['email' => false]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentDocument = $this->prophesize(Document::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentDocument->getType()->willReturn('email'); + $dependency->getRequiredBy()->willReturn([['id' => 5, 'type' => 'document']]); + $this->elementRepository->findDocument(5)->willReturn($dependentDocument->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_object_with_subtype_disabled_in_dependent_config(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => ['enabled' => true, 'types' => ['folder' => false]]]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentFolder = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_object_with_class_disabled_in_dependent_config(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['objects' => ['enabled' => true, 'classes' => ['IgnoredClass' => false]]]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentObject = $this->prophesize(Concrete::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentObject->getType()->willReturn('object'); + $dependentObject->getClassName()->willReturn('IgnoredClass'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentObject->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + * + * Global config allows the subtype, but dependent config disables it — element is skipped. + */ + public function findFor_skips_dependent_object_when_global_allows_but_dependent_config_disables_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'objects' => [ + 'types' => ['folder' => true], // global: folders allowed + 'invalidate_dependent_elements' => ['enabled' => true, 'types' => [ + 'objects' => ['enabled' => true, 'types' => ['folder' => false]], // dependent: folders excluded + ]], + ], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentFolder = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + * + * Dependent config allows the subtype, but global config disables it — global takes precedence. + */ + public function findFor_skips_dependent_object_when_dependent_config_allows_but_global_disables_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'objects' => [ + 'types' => ['folder' => false], // global: folders disabled + 'invalidate_dependent_elements' => ['enabled' => true, 'types' => [ + 'objects' => ['enabled' => true, 'types' => ['folder' => true]], // dependent: folders allowed + ]], + ], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentFolder = $this->prophesize(DataObject::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); + $this->elementRepository->findObject(23)->willReturn($dependentFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_asset_with_disabled_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['assets' => true]]], + 'assets' => ['types' => ['folder' => false]], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentAssetFolder = $this->prophesize(Asset::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentAssetFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 7, 'type' => 'asset']]); + $this->elementRepository->findAsset(7)->willReturn($dependentAssetFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_asset_with_subtype_disabled_in_dependent_config(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['assets' => ['enabled' => true, 'types' => ['folder' => false]]]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentAssetFolder = $this->prophesize(Asset::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentAssetFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 7, 'type' => 'asset']]); + $this->elementRepository->findAsset(7)->willReturn($dependentAssetFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function findFor_skips_dependent_document_with_subtype_disabled_in_dependent_config(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray(['objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => ['documents' => ['enabled' => true, 'types' => ['email' => false]]]]]]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentDocument = $this->prophesize(Document::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentDocument->getType()->willReturn('email'); + $dependency->getRequiredBy()->willReturn([['id' => 5, 'type' => 'document']]); + $this->elementRepository->findDocument(5)->willReturn($dependentDocument->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + * + * Global config allows the asset subtype, but dependent config disables it — element is skipped. + */ + public function findFor_skips_dependent_asset_when_global_allows_but_dependent_config_disables_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'assets' => ['types' => ['folder' => true]], // global: asset folders allowed + 'objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => [ + 'assets' => ['enabled' => true, 'types' => ['folder' => false]], // dependent: asset folders excluded + ]]], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentAssetFolder = $this->prophesize(Asset::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentAssetFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 7, 'type' => 'asset']]); + $this->elementRepository->findAsset(7)->willReturn($dependentAssetFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + * + * Dependent config allows the asset subtype, but global config disables it — global takes precedence. + */ + public function findFor_skips_dependent_asset_when_dependent_config_allows_but_global_disables_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'assets' => ['types' => ['folder' => false]], // global: asset folders disabled + 'objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => [ + 'assets' => ['enabled' => true, 'types' => ['folder' => true]], // dependent: asset folders allowed + ]]], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentAssetFolder = $this->prophesize(Asset::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentAssetFolder->getType()->willReturn('folder'); + $dependency->getRequiredBy()->willReturn([['id' => 7, 'type' => 'asset']]); + $this->elementRepository->findAsset(7)->willReturn($dependentAssetFolder->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + * + * Global config allows the document subtype, but dependent config disables it — element is skipped. + */ + public function findFor_skips_dependent_document_when_global_allows_but_dependent_config_disables_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'documents' => ['types' => ['link' => true]], // global: link documents allowed + 'objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => [ + 'documents' => ['enabled' => true, 'types' => ['link' => false]], // dependent: link documents excluded + ]]], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentDocument = $this->prophesize(Document::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentDocument->getType()->willReturn('link'); + $dependency->getRequiredBy()->willReturn([['id' => 5, 'type' => 'document']]); + $this->elementRepository->findDocument(5)->willReturn($dependentDocument->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } + + /** + * @test + * + * Dependent config allows the document subtype, but global config disables it — global takes precedence. + */ + public function findFor_skips_dependent_document_when_dependent_config_allows_but_global_disables_subtype(): void + { + $finder = new DependentElementFinder( + $this->elementRepository->reveal(), + ElementsConfig::fromArray([ + 'documents' => ['types' => ['link' => false]], // global: link documents disabled + 'objects' => ['invalidate_dependent_elements' => ['enabled' => true, 'types' => [ + 'documents' => ['enabled' => true, 'types' => ['link' => true]], // dependent: link documents allowed + ]]], + ]), + ); + + $element = $this->prophesize(TestObject::class); + $dependency = $this->prophesize(Dependency::class); + $dependentDocument = $this->prophesize(Document::class); + $element->getDependencies()->willReturn($dependency->reveal()); + $dependentDocument->getType()->willReturn('link'); + $dependency->getRequiredBy()->willReturn([['id' => 5, 'type' => 'document']]); + $this->elementRepository->findDocument(5)->willReturn($dependentDocument->reveal()); + + $result = $finder->findFor($element->reveal()); + + self::assertSame([], $result); + } +} diff --git a/tests/Unit/Element/InvalidateElementListenerTest.php b/tests/Unit/Element/InvalidateElementListenerTest.php index 21d541b..b7c9234 100644 --- a/tests/Unit/Element/InvalidateElementListenerTest.php +++ b/tests/Unit/Element/InvalidateElementListenerTest.php @@ -5,7 +5,9 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheInvalidator; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTags; +use Neusta\Pimcore\HttpCacheBundle\Element\DependentElementFinder; use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent; +use Neusta\Pimcore\HttpCacheBundle\Element\ElementType; use Neusta\Pimcore\HttpCacheBundle\Element\InvalidateElementListener; use PHPUnit\Framework\TestCase; use Pimcore\Event\Model\AssetEvent; @@ -32,17 +34,23 @@ final class InvalidateElementListenerTest extends TestCase /** @var ObjectProphecy */ private $eventDispatcher; + /** @var ObjectProphecy */ + private $dependentElementFinder; + protected function setUp(): void { $this->cacheInvalidator = $this->prophesize(CacheInvalidator::class); $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $this->dependentElementFinder = $this->prophesize(DependentElementFinder::class); $this->invalidateElementListener = new InvalidateElementListener( $this->cacheInvalidator->reveal(), $this->eventDispatcher->reveal(), + $this->dependentElementFinder->reveal(), ); $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) ->willReturnArgument(); + $this->dependentElementFinder->findFor(Argument::any())->willReturn([]); } /** @@ -105,6 +113,41 @@ public function onUpdate_should_invalidate_elements(ElementEventInterface $event ->shouldHaveBeenCalledOnce(); } + /** + * @test + * + * @dataProvider elementProvider + */ + public function onUpdate_should_call_dependent_element_finder(ElementEventInterface $event): void + { + $element = $event->getElement(); + + $this->invalidateElementListener->onUpdate($event); + + $this->dependentElementFinder->findFor($element) + ->shouldHaveBeenCalledOnce(); + } + + /** + * @test + * + * @dataProvider elementProvider + */ + public function onUpdate_should_not_call_dependent_element_invalidator_when_source_invalidation_is_canceled( + ElementEventInterface $event, + ): void { + $element = $event->getElement(); + $invalidationEvent = ElementInvalidationEvent::fromElement($element); + $invalidationEvent->cancel = true; + + $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) + ->willReturn($invalidationEvent); + + $this->invalidateElementListener->onUpdate($event); + + $this->dependentElementFinder->findFor(Argument::any())->shouldNotHaveBeenCalled(); + } + /** * @test * @@ -177,6 +220,41 @@ public function onDelete_should_invalidate_elements(ElementEventInterface $event ->shouldHaveBeenCalledOnce(); } + /** + * @test + * + * @dataProvider elementProvider + */ + public function onDelete_should_call_dependent_element_finder(ElementEventInterface $event): void + { + $element = $event->getElement(); + + $this->invalidateElementListener->onDelete($event); + + $this->dependentElementFinder->findFor($element) + ->shouldHaveBeenCalledOnce(); + } + + /** + * @test + * + * @dataProvider elementProvider + */ + public function onDelete_should_not_call_dependent_element_invalidator_when_source_invalidation_is_canceled( + ElementEventInterface $event, + ): void { + $element = $event->getElement(); + $invalidationEvent = ElementInvalidationEvent::fromElement($element); + $invalidationEvent->cancel = true; + + $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) + ->willReturn($invalidationEvent); + + $this->invalidateElementListener->onDelete($event); + + $this->dependentElementFinder->findFor(Argument::any())->shouldNotHaveBeenCalled(); + } + /** * @test * @@ -225,14 +303,17 @@ public function elementProvider(): iterable { $asset = $this->prophesize(Asset::class); $asset->getId()->willReturn(42); + $asset->getType()->willReturn(ElementType::Asset->value); yield 'Asset' => ['event' => new AssetEvent($asset->reveal())]; $document = $this->prophesize(Document::class); $document->getId()->willReturn(42); + $document->getType()->willReturn(ElementType::Document->value); yield 'Document' => ['event' => new DocumentEvent($document->reveal())]; $dataObject = $this->prophesize(DataObject::class); $dataObject->getId()->willReturn(42); + $dataObject->getType()->willReturn(ElementType::Object->value); yield 'Object' => ['event' => new DataObjectEvent($dataObject->reveal())]; } } diff --git a/tests/app/config/pimcore/classes/definition_TestDataObject.php b/tests/app/config/pimcore/classes/definition_TestObject.php similarity index 62% rename from tests/app/config/pimcore/classes/definition_TestDataObject.php rename to tests/app/config/pimcore/classes/definition_TestObject.php index c7ed3b8..f556bc9 100644 --- a/tests/app/config/pimcore/classes/definition_TestDataObject.php +++ b/tests/app/config/pimcore/classes/definition_TestObject.php @@ -1,14 +1,23 @@ null, - 'id' => 'test_data_object', - 'name' => 'TestDataObject', + 'id' => 'test_object', + 'name' => 'TestObject', 'description' => '', 'creationDate' => 0, - 'modificationDate' => 1685448671, - 'userOwner' => 1, - 'userModification' => 1, + 'modificationDate' => 1755079708, + 'userOwner' => 58, + 'userModification' => 58, 'parentClass' => '', 'implementsInterfaces' => '', 'listingParentClass' => '', @@ -40,8 +49,8 @@ 'type' => null, 'region' => null, 'title' => '', - 'width' => null, - 'height' => null, + 'width' => '', + 'height' => '', 'collapsible' => false, 'collapsed' => false, 'bodyStyle' => '', @@ -52,7 +61,7 @@ 'name' => 'content', 'title' => 'Content', 'tooltip' => '', - 'mandatory' => true, + 'mandatory' => false, 'noteditable' => false, 'index' => false, 'locked' => false, @@ -66,7 +75,7 @@ 'visibleSearch' => false, 'blockedVarsForExport' => [ ], - 'width' => null, + 'width' => '', 'defaultValue' => null, 'columnLength' => 190, 'regex' => '', @@ -76,6 +85,49 @@ 'showCharCount' => false, 'defaultValueGenerator' => '', ]), + 1 => Pimcore\Model\DataObject\ClassDefinition\Data\ManyToManyRelation::__set_state([ + 'name' => 'related', + 'title' => 'Related', + 'tooltip' => '', + 'mandatory' => false, + 'noteditable' => false, + 'index' => false, + 'locked' => false, + 'style' => '', + 'permissions' => null, + 'datatype' => 'data', + 'fieldtype' => 'manyToManyRelation', + 'relationType' => true, + 'invisible' => false, + 'visibleGridView' => false, + 'visibleSearch' => false, + 'blockedVarsForExport' => [ + ], + 'classes' => [ + 0 => [ + 'classes' => 'TestObject', + ], + ], + 'pathFormatterClass' => '', + 'width' => '', + 'height' => '', + 'maxItems' => null, + 'assetUploadPath' => '', + 'objectsAllowed' => true, + 'assetsAllowed' => true, + 'assetTypes' => [ + 0 => [ + 'assetTypes' => 'image', + ], + ], + 'documentsAllowed' => true, + 'documentTypes' => [ + 0 => [ + 'documentTypes' => 'page', + ], + ], + 'enableTextSelection' => false, + ]), ], 'locked' => false, 'blockedVarsForExport' => [ @@ -83,8 +135,8 @@ 'fieldtype' => 'panel', 'layout' => null, 'border' => false, - 'icon' => null, - 'labelWidth' => 100, + 'icon' => '', + 'labelWidth' => 0, 'labelAlign' => 'left', ]), ], diff --git a/tests/app/src/Controller/GetObjectController.php b/tests/app/src/Controller/GetObjectController.php index fd69901..31443a1 100644 --- a/tests/app/src/Controller/GetObjectController.php +++ b/tests/app/src/Controller/GetObjectController.php @@ -2,7 +2,7 @@ namespace App\Controller; -use Pimcore\Model\DataObject\TestDataObject; +use Pimcore\Model\DataObject\TestObject; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -10,7 +10,7 @@ final class GetObjectController { public function __invoke(Request $request): Response { - if (!$object = TestDataObject::getById($request->query->get('id'))) { + if (!$object = TestObject::getById($request->query->get('id'))) { return new Response('Object not found', Response::HTTP_NOT_FOUND); }