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

Cache per documentation set #3487

Merged
merged 3 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
132 changes: 89 additions & 43 deletions src/phpDocumentor/Descriptor/Cache/ProjectDescriptorMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@

namespace phpDocumentor\Descriptor\Cache;

use phpDocumentor\Descriptor\ApiSetDescriptor;
use phpDocumentor\Descriptor\FileDescriptor;
use phpDocumentor\Descriptor\ProjectDescriptor;
use phpDocumentor\Descriptor\VersionDescriptor;
use phpDocumentor\Reflection\File;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;

use function array_diff;
use function array_map;
use function md5;
use function sprintf;

/**
* Maps a projectDescriptor to and from a cache instance.
Expand Down Expand Up @@ -51,7 +54,73 @@ public function populate(ProjectDescriptor $projectDescriptor): void
{
$this->loadCacheItemAsSettings($projectDescriptor);

$fileList = $this->cache->getItem(self::FILE_LIST)->get();
foreach ($projectDescriptor->getVersions() as $version) {
$this->populateVersion($version);
}
}

/**
* Stores a Project Descriptor in the Cache.
*/
public function save(ProjectDescriptor $projectDescriptor): void
{
// store the settings for this Project Descriptor
$item = $this->cache->getItem(self::KEY_SETTINGS);
$this->cache->saveDeferred($item->set($projectDescriptor->getSettings()));

foreach ($projectDescriptor->getVersions() as $version) {
$this->saveVersion($version);
}
}

/**
* Removes all files in cache that do not occur in the given FileSet Collection.
*
* @param File[] $files
*/
public function garbageCollect(VersionDescriptor $version, ApiSetDescriptor $apiSet, array $files): void
{
$fileListKey = $this->getApiSetFileListCacheKey($version, $apiSet);
$fileListItem = $this->cache->getItem($fileListKey);
$cachedFileList = $fileListItem->get();

if ($cachedFileList === null) {
return;
}

$realFileKeys = array_map(
fn (File $file) => $this->getApiSetFileKey($fileListKey, $file->path()),
$files
);

$this->cache->deleteItems(array_diff($cachedFileList, $realFileKeys));
}

private function loadCacheItemAsSettings(ProjectDescriptor $projectDescriptor): void
{
$item = $this->cache->getItem(self::KEY_SETTINGS);
if (!$item->isHit()) {
return;
}

$settings = $item->get();
$projectDescriptor->setSettings($settings);
}

private function populateVersion(VersionDescriptor $version): void
{
/** @var ApiSetDescriptor[] $apiSets */
$apiSets = $version->getDocumentationSets()->filter(ApiSetDescriptor::class);

foreach ($apiSets as $apiSet) {
$this->populateApiSet($version, $apiSet);
}
}

private function populateApiSet(VersionDescriptor $version, ApiSetDescriptor $apiSet): void
{
$key = $this->getApiSetFileListCacheKey($version, $apiSet);
$fileList = $this->cache->getItem($key)->get();
if ($fileList === null) {
return;
}
Expand All @@ -64,28 +133,30 @@ public function populate(ProjectDescriptor $projectDescriptor): void
continue;
}

$projectDescriptor->getFiles()->set($file->getPath(), $file);
$apiSet->getFiles()->set($file->getPath(), $file);
}
}

/**
* Stores a Project Descriptor in the Cache.
*/
public function save(ProjectDescriptor $projectDescriptor): void
private function saveVersion(VersionDescriptor $version): void
{
$fileListItem = $this->cache->getItem(self::FILE_LIST);
$currentFileList = $fileListItem->get();
$apiSets = $version->getDocumentationSets()->filter(ApiSetDescriptor::class);
foreach ($apiSets as $apiSet) {
$this->saveApiSet($version, $apiSet);
}
}

// store the settings for this Project Descriptor
$item = $this->cache->getItem(self::KEY_SETTINGS);
$this->cache->saveDeferred($item->set($projectDescriptor->getSettings()));
private function saveApiSet(VersionDescriptor $version, ApiSetDescriptor $apiSet): void
{
$fileListKey = $this->getApiSetFileListCacheKey($version, $apiSet);
$fileListItem = $this->cache->getItem($fileListKey);
$currentFileList = $fileListItem->get();

// store cache items
$fileKeys = [];
foreach ($projectDescriptor->getFiles() as $file) {
$key = self::FILE_PREFIX . md5($file->getPath());
foreach ($apiSet->getFiles() as $file) {
$key = $this->getApiSetFileKey($fileListKey, $file->getPath());
$fileKeys[] = $key;
$item = $this->cache->getItem($key);
$item = $this->cache->getItem($key);
$this->cache->saveDeferred($item->set($file));
}

Expand All @@ -105,38 +176,13 @@ public function save(ProjectDescriptor $projectDescriptor): void
$this->cache->deleteItems($invalidatedKeys);
}

/**
* Removes all files in cache that do not occur in the given FileSet Collection.
*
* @param File[] $files
*/
public function garbageCollect(array $files): void
private function getApiSetFileListCacheKey(VersionDescriptor $version, ApiSetDescriptor $apiSet): string
{
$fileListItem = $this->cache->getItem(self::FILE_LIST);
$cachedFileList = $fileListItem->get();

if ($cachedFileList === null) {
return;
}

$realFileKeys = array_map(
static function (File $file) {
return self::FILE_PREFIX . md5($file->path());
},
$files
);

$this->cache->deleteItems(array_diff($cachedFileList, $realFileKeys));
return sprintf('%s-%s-%s', self::FILE_LIST, $version->getNumber(), $apiSet->getName());
}

private function loadCacheItemAsSettings(ProjectDescriptor $projectDescriptor): void
private function getApiSetFileKey(string $fileListKey, string $path): string
{
$item = $this->cache->getItem(self::KEY_SETTINGS);
if (!$item->isHit()) {
return;
}

$settings = $item->get();
$projectDescriptor->setSettings($settings);
return sprintf('%s%s', self::FILE_PREFIX, md5($fileListKey . $path));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ public function __construct(ProjectDescriptorMapper $descriptorMapper)

public function __invoke(ApiSetPayload $payload): ApiSetPayload
{
$this->descriptorMapper->garbageCollect($payload->getFiles());
$this->descriptorMapper->garbageCollect(
$payload->getVersion(),
$payload->getApiSet(),
$payload->getFiles()
);

return $payload;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __invoke(Payload $payload): Payload
foreach ($versions as $version) {
foreach ($version->getDocumentationSets()->filter(ApiSetDescriptor::class) as $apiSet) {
$this->parseApiDocumentationSetPipeline->process(
new Parser\ApiSetPayload($payload->getConfig(), $payload->getBuilder(), $apiSet)
new Parser\ApiSetPayload($payload->getConfig(), $payload->getBuilder(), $version, $apiSet)
);
}
}
Expand Down
16 changes: 13 additions & 3 deletions src/phpDocumentor/Pipeline/Stage/Parser/ApiSetPayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use phpDocumentor\Configuration\Configuration;
use phpDocumentor\Descriptor\ApiSetDescriptor;
use phpDocumentor\Descriptor\ProjectDescriptorBuilder;
use phpDocumentor\Descriptor\VersionDescriptor;
use phpDocumentor\Reflection\File;

use function array_merge;
Expand All @@ -25,11 +26,13 @@
*/
final class ApiSetPayload
{
/** @var ConfigurationMap */
private array $configuration;
private ProjectDescriptorBuilder $builder;
private VersionDescriptor $version;
private ApiSetDescriptor $apiSet;

/** @var ConfigurationMap */
private array $configuration;

/** @var File[] */
private array $files;

Expand All @@ -40,13 +43,14 @@ final class ApiSetPayload
public function __construct(
array $configuration,
ProjectDescriptorBuilder $builder,
VersionDescriptor $version,
ApiSetDescriptor $apiSet,
array $files = []
) {
$this->configuration = $configuration;
$this->builder = $builder;
$this->version = $version;
$this->apiSet = $apiSet;

$this->files = $files;
}

Expand All @@ -63,6 +67,11 @@ public function getBuilder(): ProjectDescriptorBuilder
return $this->builder;
}

public function getVersion(): VersionDescriptor
{
return $this->version;
}

public function getApiSet(): ApiSetDescriptor
{
return $this->apiSet;
Expand All @@ -76,6 +85,7 @@ public function withFiles(array $files): self
return new static(
$this->getConfiguration(),
$this->getBuilder(),
$this->getVersion(),
$this->getApiSet(),
array_merge($this->getFiles(), $files)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ protected function setUp(): void
*/
public function testThatATheSettingsForAProjectDescriptorArePersistedAndCanBeRetrievedFromCache(): void
{
$versionNumber = $this->faker()->numerify('v#.#.#');
$apiSetName = $this->faker()->word;

$fileDescriptor = new FileDescriptor('fileHash');
$fileDescriptor->setPath('./src/MyClass.php');

$projectDescriptor = new ProjectDescriptor('project');
$set = $this->faker()->apiSetDescriptor();
$version = $this->faker()->versionDescriptor([$set]);
$projectDescriptor->getVersions()->add($version);
$projectDescriptor->getFiles()->set('./src/MyClass.php', $fileDescriptor);
$restoredSet = $this->faker()->apiSetDescriptor($apiSetName);
$restoredVersion = $this->faker()->versionDescriptor([$restoredSet], $versionNumber);
$projectDescriptor->getVersions()->add($restoredVersion);
$restoredSet->getFiles()->set('./src/MyClass.php', $fileDescriptor);

$this->assertFalse($projectDescriptor->getSettings()->shouldIncludeSource());
$projectDescriptor->getSettings()->includeSource();
Expand All @@ -65,12 +68,12 @@ public function testThatATheSettingsForAProjectDescriptorArePersistedAndCanBeRet
$this->mapper->save($projectDescriptor);

$restoredProjectDescriptor = new ProjectDescriptor('project2');
$set = $this->faker()->apiSetDescriptor();
$version = $this->faker()->versionDescriptor([$set]);
$restoredProjectDescriptor->getVersions()->add($version);
$restoredSet = $this->faker()->apiSetDescriptor($apiSetName);
$restoredVersion = $this->faker()->versionDescriptor([$restoredSet], $versionNumber);
$restoredProjectDescriptor->getVersions()->add($restoredVersion);
$this->mapper->populate($restoredProjectDescriptor);

$this->assertTrue($restoredProjectDescriptor->getSettings()->shouldIncludeSource());
$this->assertEquals($fileDescriptor, $restoredProjectDescriptor->getFiles()->get($fileDescriptor->getPath()));
$this->assertEquals($fileDescriptor, $restoredSet->getFiles()->get($fileDescriptor->getPath()));
}
}
8 changes: 4 additions & 4 deletions tests/unit/phpDocumentor/Faker/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,18 @@ public function fileDescriptor(): FileDescriptor
}

/** @param DocumentationSetDescriptor[] $documentationSets */
public function versionDescriptor(array $documentationSets): VersionDescriptor
public function versionDescriptor(array $documentationSets, ?string $version = null): VersionDescriptor
{
return new VersionDescriptor(
$this->generator->numerify('v#.#.#'),
$version ?? $this->generator->numerify('v#.#.#'),
DescriptorCollection::fromClassString(DocumentationSetDescriptor::class, $documentationSets)
);
}

public function apiSetDescriptor(): ApiSetDescriptor
public function apiSetDescriptor(?string $name = null): ApiSetDescriptor
{
return new ApiSetDescriptor(
$this->generator->word(),
$name ?? $this->generator->word(),
$this->source(),
(string) $this->path(),
$this->apiSpecification()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,15 @@ public function testItWillInstructTheDescriptorMapperToCollectGarbage(): void
$files = ['file1'];

$descriptorMapper = $this->prophesize(ProjectDescriptorMapper::class);
$descriptorMapper->garbageCollect($files)->shouldBeCalledOnce();

$fixture = new GarbageCollectCache($descriptorMapper->reveal());

$fixture(
new ApiSetPayload(
[],
$this->prophesize(ProjectDescriptorBuilder::class)->reveal(),
$this->faker()->apiSetDescriptor(),
$files
)
);
$builder = $this->prophesize(ProjectDescriptorBuilder::class)->reveal();
$apiSet = $this->faker()->apiSetDescriptor();
$version = $this->faker()->versionDescriptor([$apiSet]);

$descriptorMapper->garbageCollect($version, $apiSet, $files)->shouldBeCalledOnce();

$fixture(new ApiSetPayload([], $builder, $version, $apiSet, $files));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ public function testFilesAreCollectedAndAddedToPayload(): void
null
);

$payload = new ApiSetPayload(
['phpdocumentor' => ['versions' => ['1.0.0' => $version]]],
$this->prophesize(ProjectDescriptorBuilder::class)->reveal(),
$this->faker()->apiSetDescriptor()
);
$config = ['phpdocumentor' => ['versions' => ['1.0.0' => $version]]];
$builder = $this->prophesize(ProjectDescriptorBuilder::class)->reveal();
$apiSet = $this->faker()->apiSetDescriptor();
$version = $this->faker()->versionDescriptor([$apiSet]);

$payload = new ApiSetPayload($config, $builder, $version, $apiSet);

$result = $fixture($payload);
self::assertEquals([], $result->getFiles());

self::assertSame([], $result->getFiles());
}
}