Skip to content

Commit

Permalink
chore(cache): improve invalidation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
sidux committed Jul 8, 2024
1 parent 3b3f5a3 commit 4d8df97
Show file tree
Hide file tree
Showing 12 changed files with 458 additions and 189 deletions.
3 changes: 3 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="

use devenv
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ build/
vendor/
.phpunit.result.cache
tests/cache
# Devenv
.devenv*
devenv.local.nix

# direnv
.direnv

# pre-commit
.pre-commit-config.yaml
122 changes: 122 additions & 0 deletions devenv.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1720180409,
"owner": "cachix",
"repo": "devenv",
"rev": "7b3ed618571f0d14655b05f7b1c6ef600904383a",
"treeHash": "14b4b6bc32582a78300257c3b618d821557eb530",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1716977621,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1719957072,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7144d6241f02d171d25fba3edeaf15e0f2592105",
"treeHash": "415bf0e03835797927c1b2cb46a557bcecc36673",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1719259945,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"treeHash": "1a76ff89a9d4017b48abbb1bad8837b35d604ffc",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}
26 changes: 26 additions & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{ pkgs, lib, config, inputs, ... }:

{
languages.php = {
enable = true;
version = lib.mkDefault "8.2";
extensions = [ "xdebug" ];

ini = ''
memory_limit = -1
opcache.enable = 1
opcache.revalidate_freq = 0
opcache.validate_timestamps = 1
opcache.max_accelerated_files = 30000
opcache.memory_consumption = 256M
opcache.interned_strings_buffer = 20
realpath_cache_ttl = 3600
xdebug.idekey = "PHPSTORM"
xdebug.start_with_request = "yes"
zend.assertions = 1
date.timezone = "Europe/Paris"
xdebug.output_dir = ".devenv/state/xdebug"
xdebug.mode = "off"
'';
};
}
15 changes: 15 additions & 0 deletions devenv.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling

# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true

# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"

# If you have more than one devenv you can merge them
#imports:
# - ./backend
14 changes: 14 additions & 0 deletions src/Attribute/Cache/Tag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace OpenClassrooms\ServiceProxy\Attribute\Cache;

#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
final class Tag
{
public function __construct(
public readonly ?string $prefix = null
) {
}
}
1 change: 0 additions & 1 deletion src/Interceptor/Contract/Cache/AutoTaggable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@

interface AutoTaggable
{
public function getId(): string|int;
}
128 changes: 19 additions & 109 deletions src/Interceptor/Impl/CacheInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
use OpenClassrooms\ServiceProxy\Helper\TypesExtractor;
use OpenClassrooms\ServiceProxy\Interceptor\Config\CacheInterceptorConfig;
use OpenClassrooms\ServiceProxy\Interceptor\Contract\AbstractInterceptor;
use OpenClassrooms\ServiceProxy\Interceptor\Contract\Cache\AutoTaggable;
use OpenClassrooms\ServiceProxy\Interceptor\Contract\PrefixInterceptor;
use OpenClassrooms\ServiceProxy\Interceptor\Contract\SuffixInterceptor;
use OpenClassrooms\ServiceProxy\Interceptor\Exception\InternalCodeRetrievalException;
use OpenClassrooms\ServiceProxy\Model\Request\Instance;
use OpenClassrooms\ServiceProxy\Model\Response\Response;
use OpenClassrooms\ServiceProxy\Util\Expression;
use Symfony\Component\PropertyInfo\Type;

final class CacheInterceptor extends AbstractInterceptor implements SuffixInterceptor, PrefixInterceptor
{
use CacheTagsTrait;

private const DEFAULT_POOL_NAME = 'default';

/**
Expand All @@ -46,16 +46,18 @@ public function __construct(
/**
* @return array<int, string>
*/
public static function getHits(?string $poolName = self::DEFAULT_POOL_NAME): array
public static function getHits(?string $poolName = null): array
{
$poolName ??= self::DEFAULT_POOL_NAME;
return self::$hits[$poolName] ?? [];
}

/**
* @return array<int, string>
*/
public static function getMisses(?string $poolName = self::DEFAULT_POOL_NAME): array
public static function getMisses(?string $poolName = null): array
{
$poolName ??= self::DEFAULT_POOL_NAME;
return self::$misses[$poolName] ?? [];
}

Expand Down Expand Up @@ -103,18 +105,19 @@ public function prefix(Instance $instance): Response

self::$hits[$pool] = self::$hits[$pool] ?? [];
self::$hits[$pool][] = $cacheKey;

$data = $data->get();
$tags = $this->getTags($instance, $attribute, $data);
foreach ($missedPools as $missedPool) {
$handler->save(
$missedPool,
$cacheKey,
$data->get(),
$data,
$attribute->ttl ?? $this->config->defaultTtl,
$this->getTags($instance, $attribute, $data)
$tags
);
}

return new Response($data->get(), true);
return new Response($data, true);
}

return new Response(null, false);
Expand Down Expand Up @@ -272,107 +275,6 @@ private function getInnerCode(\ReflectionMethod|\ReflectionClass $reflection): s
return $code;
}

/**
* @return array<int, string>
*/
private function getTags(Instance $instance, Cache $attribute, mixed $response = null): array
{
$parameters = $instance->getMethod()
->getParameters();

$tags = array_map(
static fn (string $expression) => Expression::evaluateToString($expression, $parameters),
$attribute->tags
);

if ($response !== null) {
$tags = array_values(array_filter([
...$tags,
...$this->guessObjectsTags(
$response,
$this->config->autoTagsExcludedClasses
),
]));
}

return $tags;
}

/**
* @param array<class-string> $excludedClasses
* @param array<string, string> $registeredTags
*
* @return array<string, string>
*/
private function guessObjectsTags(mixed $object, array $excludedClasses = [], array $registeredTags = []): array
{
if (!\is_object($object) && !is_iterable($object)) {
return $registeredTags;
}

foreach ($excludedClasses as $excludedClass) {
if ($object instanceof $excludedClass) {
return $registeredTags;
}
}

if (is_iterable($object)) {
foreach ($object as $item) {
$registeredTags = $this->guessObjectsTags($item, $excludedClasses, $registeredTags);
}

return $registeredTags;
}

if (!$object instanceof AutoTaggable) {
return $registeredTags;
}

$tag = $this->buildTag($object);

if (isset($registeredTags[$tag])) {
return $registeredTags;
}

$registeredTags[$tag] = $tag;

$ref = new \ReflectionClass($object);

foreach ($ref->getProperties() as $propRef) {
$subObject = $this->getPropertyValue($ref, $object, $propRef->getName());

$registeredTags = $this->guessObjectsTags($subObject, $excludedClasses, $registeredTags);
}

return $registeredTags;
}

private function buildTag(AutoTaggable $object): string
{
return str_replace('\\', '.', \get_class($object)) . '.' . $object->getId();
}

/**
* @param \ReflectionClass<object> $ref
*/
private function getPropertyValue(\ReflectionClass $ref, object $object, string $propertyName): mixed
{
$getter = 'get' . ucfirst($propertyName);
$refMethod = $ref->hasMethod($getter) ? $ref->getMethod($getter) : null;
if ($refMethod !== null && $refMethod->isPublic() && \count($refMethod->getParameters()) === 0) {
return $refMethod->invoke($object);
}

$propRef = $ref->getProperty($propertyName);
if (!$propRef->isInitialized($object)) {
return null;
}

$propRef->setAccessible(true);

return $propRef->getValue($object);
}

/**
* @param array<string, mixed> $parameters
*/
Expand All @@ -385,4 +287,12 @@ private function getParametersHash(array $parameters): string

return $identifier;
}

/**
* @return array<class-string>
*/
private function getAutoTagsExcludedClasses(): array
{
return $this->config->autoTagsExcludedClasses ?? [];
}
}
Loading

0 comments on commit 4d8df97

Please sign in to comment.