Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tag-based cache invalidation, and apply to Gedcom Records. #3748

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions app/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,73 @@
namespace Fisharebest\Webtrees;

use Closure;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

/**
* Wrapper around the symfony PSR6 cache library.
* Hash the keys to protect against characters that are not allowed in PSR6.
*/
class Cache
{
/** @var CacheInterface */
/** @var TagAwareCacheInterface */
private $cache;

/**
* Cache constructor.
*
* @param CacheInterface $cache
* @param TagAwareCacheInterface $cache
*/
public function __construct(CacheInterface $cache)
public function __construct(TagAwareCacheInterface $cache)
{
$this->cache = $cache;
}

/**
* Generate a key compatible with PSR-6 requirements
* @see https://www.php-fig.org/psr/psr-6/
*
* @param string $key
* @return string
*/
public function safeKey(string $key): string
{
return md5($key);
}

/**
* Fetch an item from the cache - or create it where it does not exist.
*
* @param string $key
* @param Closure $closure
* @param int|null $ttl
* @param string[] $tags
*
* @return mixed
*/
public function remember(string $key, Closure $closure, int $ttl = null)
public function remember(string $key, Closure $closure, int $ttl = null, array $tags = [])
{
return $this->cache->get(md5($key), static function (ItemInterface $item) use ($closure, $ttl) {
$item->expiresAfter($ttl);
$tags = array_map([$this, 'safeKey'], $tags);
return $this->cache->get(
$this->safeKey($key),
static function (ItemInterface $item) use ($closure, $tags, $ttl) {
$item->expiresAfter($ttl);
$item->tag($tags);

return $closure();
});
return $closure();
}
);
}

/**
* Invalidate cache items based on tags.
*
* @param string[] $tags
* @return bool
*/
public function invalidateTags(array $tags): bool
{
return $this->cache->invalidateTags(array_map([$this, 'safeKey'], $tags));
}

/**
Expand All @@ -67,6 +96,6 @@ public function remember(string $key, Closure $closure, int $ttl = null)
*/
public function forget(string $key): void
{
$this->cache->delete(md5($key));
$this->cache->delete($this->safeKey($key));
}
}
2 changes: 1 addition & 1 deletion app/Factories/AbstractGedcomRecordFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected function pendingChanges(Tree $tree): Collection
->where('status', '=', 'pending')
->orderBy('change_id')
->pluck('new_gedcom', 'xref');
});
}, null, ['pending-t-' . $tree->id()]);
}

/**
Expand Down
11 changes: 6 additions & 5 deletions app/Factories/CacheFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
use Fisharebest\Webtrees\Contracts\CacheFactoryInterface;
use Fisharebest\Webtrees\Webtrees;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;

use function random_int;

Expand All @@ -39,19 +40,19 @@ class CacheFactory implements CacheFactoryInterface
private const FILES_TTL = 8640000;
private const FILES_DIR = Webtrees::DATA_DIR . 'cache/';

/** @var ArrayAdapter */
/** @var TagAwareAdapter */
private $array_adapter;

/** @var FilesystemAdapter */
/** @var FilesystemTagAwareAdapter */
private $filesystem_adapter;

/**
* CacheFactory constructor.
*/
public function __construct()
{
$this->array_adapter = new ArrayAdapter(0, false);
$this->filesystem_adapter = new FilesystemAdapter('', self::FILES_TTL, self::FILES_DIR);
$this->array_adapter = new TagAwareAdapter(new ArrayAdapter(0, false));
$this->filesystem_adapter = new FilesystemTagAwareAdapter('', self::FILES_TTL, self::FILES_DIR);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/FamilyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Family
->map(Registry::individualFactory()->mapper($tree));

return new Family($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/GedcomRecordFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?GedcomRe
$type = $this->extractType($gedcom ?? $pending);

return $this->newGedcomRecord($type, $xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/HeaderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Header
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Header($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/IndividualFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Individu
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Individual($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/LocationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Location
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Location($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/MediaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Media
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Media($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/NoteFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Note
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Note($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/RepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Reposito
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Repository($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/SourceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Source
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Source($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/SubmissionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Submissi
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Submission($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Factories/SubmitterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function make(string $xref, Tree $tree, string $gedcom = null): ?Submitte
$xref = $this->extractXref($gedcom ?? $pending, $xref);

return new Submitter($xref, $gedcom ?? '', $pending, $tree);
});
}, null, ['gedrec-' . $xref . '@' . $tree->id()]);
}

/**
Expand Down
20 changes: 19 additions & 1 deletion app/GedcomRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public function canShow(int $access_level = null): bool

return Registry::cache()->array()->remember($cache_key, function () use ($access_level) {
return $this->canShowRecord($access_level);
});
}, null, ['gedrec-' . $this->tree->id() . '-' . $this->xref]);
}

/**
Expand Down Expand Up @@ -973,6 +973,7 @@ public function updateFact(string $fact_id, string $gedcom, bool $update_chan):
]);

$this->pending = $new_gedcom;
$this->invalidateInCache();

if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') {
app(PendingChangesService::class)->acceptRecord($this);
Expand Down Expand Up @@ -1019,6 +1020,7 @@ public function updateRecord(string $gedcom, bool $update_chan): void

// Clear the cache
$this->pending = $gedcom;
$this->invalidateInCache();

// Accept this pending change
if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') {
Expand Down Expand Up @@ -1050,6 +1052,8 @@ public function deleteRecord(): void
]);
}

$this->invalidateInCache();

// Auto-accept this pending change
if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') {
app(PendingChangesService::class)->acceptRecord($this);
Expand All @@ -1058,6 +1062,20 @@ public function deleteRecord(): void
Log::addEditLog('Delete: ' . static::RECORD_TYPE . ' ' . $this->xref, $this->tree);
}

/**
* Invalidate this record in the caches
* Only the array cache is invalidated
*
* @return bool
*/
public function invalidateInCache(): bool
{
return Registry::cache()->array()->invalidateTags([
'gedrec-' . $this->xref . '@' . $this->tree()->id() . '',
'pending-t-' . $this->tree->id()
]);
}

/**
* Remove all links from this record to $xref
*
Expand Down
30 changes: 18 additions & 12 deletions app/Module/MediaTabModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,21 +132,27 @@ public function getTabContent(Individual $individual): string
*/
protected function getFactsWithMedia(Individual $individual): Collection
{
return Registry::cache()->array()->remember(__CLASS__ . ':' . __METHOD__, static function () use ($individual): Collection {
$facts = $individual->facts();

foreach ($individual->spouseFamilies() as $family) {
if ($family->canShow()) {
$facts = $facts->concat($family->facts());
$cacheTag = $individual->xref() . '@' . $individual->tree()->id();
return Registry::cache()->array()->remember(
__CLASS__ . ':' . __METHOD__ . '-' . $cacheTag,
static function () use ($individual): Collection {
$facts = $individual->facts();

foreach ($individual->spouseFamilies() as $family) {
if ($family->canShow()) {
$facts = $facts->concat($family->facts());
}
}
}

$facts = $facts->filter(static function (Fact $fact): bool {
return preg_match('/(?:^1|\n\d) OBJE @' . Gedcom::REGEX_XREF . '@/', $fact->gedcom()) === 1;
});
$facts = $facts->filter(static function (Fact $fact): bool {
return preg_match('/(?:^1|\n\d) OBJE @' . Gedcom::REGEX_XREF . '@/', $fact->gedcom()) === 1;
});

return Fact::sortFacts($facts);
});
return Fact::sortFacts($facts);
},
null,
['gedrec-' . $cacheTag]
);
}

/**
Expand Down
Loading