Skip to content

Commit

Permalink
Implement tag-based cache invalidation, and apply to Gedcom Records.
Browse files Browse the repository at this point in the history
  • Loading branch information
jon48 committed Mar 13, 2021
1 parent 9028837 commit 7acddc2
Show file tree
Hide file tree
Showing 21 changed files with 256 additions and 44 deletions.
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

0 comments on commit 7acddc2

Please sign in to comment.