From 62c32fc59b35b90a923d9c87c5a4b8f2e496b38d Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 9 Apr 2024 16:03:11 +0800 Subject: [PATCH] Update storage signature, add storage of package metadata --- src/Composer.php | 13 +++- src/Package/Packagist.php | 57 +++++++++++----- src/Storage/Memory.php | 31 +++++---- src/Storage/Storage.php | 8 +-- tests/Cases/Package/PackageTest.php | 45 +++++++++++++ tests/Cases/Storage/MemoryStorageTest.php | 80 +++++++++++------------ 6 files changed, 158 insertions(+), 76 deletions(-) diff --git a/src/Composer.php b/src/Composer.php index 5a82205..74ace45 100644 --- a/src/Composer.php +++ b/src/Composer.php @@ -11,6 +11,7 @@ use Winter\Packager\Package\Package; use Winter\Packager\Package\Packagist; use Winter\Packager\Package\VersionedPackage; +use Winter\Packager\Storage\Storage; /** * Represents a Composer instance. @@ -467,8 +468,18 @@ public static function newConstraint(mixed ...$arguments): Constraint * * `Name or Reference ` */ - public static function setAgent(string $agent): void + public function setAgent(string $agent): static { Packagist::setAgent($agent); + return $this; + } + + /** + * Sets the metadata storage for Packagist requests. + */ + public function setStorage(Storage $storage): static + { + Packagist::setStorage($storage); + return $this; } } diff --git a/src/Package/Packagist.php b/src/Package/Packagist.php index ba51c45..9d23620 100644 --- a/src/Package/Packagist.php +++ b/src/Package/Packagist.php @@ -9,6 +9,7 @@ use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Winter\Packager\Exceptions\PackagistException; +use Winter\Packager\Storage\Storage; /** * Packagist class. @@ -27,13 +28,22 @@ class Packagist protected static string $agent = 'Winter Packager '; + protected static ?Storage $storage = null; + /** * Get information on a package in the Packagist API. * * @return array */ - public static function getPackage(string $namespace, string $name, ?string $version = null): array - { + public static function getPackage( + string $namespace, + string $name, + ?string $version = null + ): array { + if (!is_null(static::$storage) && static::$storage->has($namespace, $name, $version)) { + return static::$storage->get($namespace, $name, $version); + } + $client = static::getClient(); $request = static::newRepoRequest($namespace . '/' . $name . '.json'); @@ -49,29 +59,32 @@ public static function getPackage(string $namespace, string $name, ?string $vers $body = json_decode($response->getBody()->getContents(), true); - if (is_null($version)) { - if (!isset($body['packages'][$namespace . '/' . $name][0])) { - throw new PackagistException('Package information not found'); - } - } else { - if (!isset($body['packages'][$namespace . '/' . $name])) { - throw new PackagistException('Package information not found'); - } + if (!isset($body['packages'][$namespace . '/' . $name])) { + throw new PackagistException('Package information not found'); + } - $versions = MetadataMinifier::expand($body['packages'][$namespace . '/' . $name]); - $parser = new VersionParser; - $packageVersionNormalized = $parser->normalize($version); + $versions = []; + foreach (MetadataMinifier::expand($body['packages'][$namespace . '/' . $name]) as $packageVersion) { + $versions[$packageVersion['version_normalized']] = $packageVersion; - foreach ($versions as $packageVersion) { - if ($packageVersion['version_normalized'] === $packageVersionNormalized) { - return $packageVersion; - } + // Store metadata + if (!is_null(static::$storage)) { + static::$storage->set($namespace, $name, $packageVersion['version_normalized'], $packageVersion); } + } + if (is_null($version)) { + return reset($versions); + } + + $parser = new VersionParser; + $versionNormalized = $parser->normalize($version); + + if (!array_key_exists($versionNormalized, $versions)) { throw new PackagistException('Package version not found'); } - return $body['packages'][$namespace . '/' . $name][0]; + return $versions[$versionNormalized]; } public static function getClient(): ClientInterface @@ -104,6 +117,14 @@ public static function setAgent(string $agent): void static::$agent = trim($name) . ' <' . trim($email) . '>'; } + /** + * Sets the storage for metadata. + */ + public static function setStorage(?Storage $storage = null): void + { + static::$storage = $storage; + } + public static function newApiRequest(string $url = ''): RequestInterface { $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', self::PACKAGIST_API_URL . ltrim($url, '/')); diff --git a/src/Storage/Memory.php b/src/Storage/Memory.php index 5b604f2..a646f77 100644 --- a/src/Storage/Memory.php +++ b/src/Storage/Memory.php @@ -37,58 +37,58 @@ public function __construct(?VersionParser $versionParser = null) /** * {@inheritDoc} */ - public function get(string $package, ?string $version = null): ?array + public function get(string $namespace, string $name, ?string $version = null): ?array { if (isset($version)) { $versionNormalized = $this->versionParser->normalize($version); - return $this->packages[$package][$versionNormalized] ?? null; + return $this->packages[$this->packageName($namespace, $name)][$versionNormalized] ?? null; } - return $this->packages[$package] ?? null; + return $this->packages[$this->packageName($namespace, $name)] ?? null; } /** * {@inheritDoc} */ - public function set(string $package, string $version, array $packageData): void + public function set(string $namespace, string $name, string $version, array $packageData): void { - if (!isset($this->packages[$package])) { - $this->packages[$package] = []; + if (!isset($this->packages[$this->packageName($namespace, $name)])) { + $this->packages[$this->packageName($namespace, $name)] = []; } $versionNormalized = $this->versionParser->normalize($version); - $this->packages[$package][$versionNormalized] = $packageData; + $this->packages[$this->packageName($namespace, $name)][$versionNormalized] = $packageData; } /** * {@inheritDoc} */ - public function forget(string $package, ?string $version = null): void + public function forget(string $namespace, string $name, ?string $version = null): void { if (isset($version)) { $versionNormalized = $this->versionParser->normalize($version); - unset($this->packages[$package][$versionNormalized]); + unset($this->packages[$this->packageName($namespace, $name)][$versionNormalized]); return; } - unset($this->packages[$package]); + unset($this->packages[$this->packageName($namespace, $name)]); } /** * {@inheritDoc} */ - public function has(string $package, ?string $version = null): bool + public function has(string $namespace, string $name, ?string $version = null): bool { if (isset($version)) { $versionNormalized = $this->versionParser->normalize($version); - return isset($this->packages[$package][$versionNormalized]); + return isset($this->packages[$this->packageName($namespace, $name)][$versionNormalized]); } - return isset($this->packages[$package]); + return isset($this->packages[$this->packageName($namespace, $name)]); } /** @@ -98,4 +98,9 @@ public function clear(): void { $this->packages = []; } + + protected function packageName(string $namespace, string $name): string + { + return $namespace . '/' . $name; + } } diff --git a/src/Storage/Storage.php b/src/Storage/Storage.php index 949812e..35a7d54 100644 --- a/src/Storage/Storage.php +++ b/src/Storage/Storage.php @@ -22,14 +22,14 @@ interface Storage * * @return array|null */ - public function get(string $package, ?string $version = null): ?array; + public function get(string $namespace, string $name, ?string $version = null): ?array; /** * Sets the package metadata for the given package name and version. * * @param array $packageData */ - public function set(string $packageName, string $version, array $packageData): void; + public function set(string $namespace, string $name, string $version, array $packageData): void; /** * Forgets the package metadata for the given package name. @@ -39,12 +39,12 @@ public function set(string $packageName, string $version, array $packageData): v * * This should be a no-op if the package metadata does not exist. */ - public function forget(string $package, ?string $version = null): void; + public function forget(string $namespace, string $name, ?string $version = null): void; /** * Determines if the package metadata exists in storage, optionally with the specified version. */ - public function has(string $package, ?string $version = null): bool; + public function has(string $namespace, string $name, ?string $version = null): bool; /** * Clears all package metadata. diff --git a/tests/Cases/Package/PackageTest.php b/tests/Cases/Package/PackageTest.php index 0967f2b..2c24002 100644 --- a/tests/Cases/Package/PackageTest.php +++ b/tests/Cases/Package/PackageTest.php @@ -5,6 +5,7 @@ namespace Winter\Packager\Tests\Cases\Package; use Winter\Packager\Composer; +use Winter\Packager\Package\Packagist; use Winter\Packager\Tests\ComposerTestCase; /** @@ -53,4 +54,48 @@ public function itCanConvertAVersionedPackageToADetailedPackage() $this->assertEquals('pages', $package->getExtras()['installer-name']); $this->assertEquals('2.0.3.0', $package->getVersionNormalized()); } + + /** + * @test + * @testdox it can store and retrieve a package from storage + * @covers \Winter\Packager\Package\VersionedPackage::toDetailed + */ + public function itCanStoreAndRetrieveAPackageFromStorage() + { + $proxy = new \Winter\Packager\Storage\Memory; + $storage = $this->getMockBuilder(\Winter\Packager\Storage\Memory::class) + ->setProxyTarget($proxy) + ->enableProxyingToOriginalMethods() + ->getMock(); + + Packagist::setStorage($storage); + + $storage->expects($this->atLeastOnce()) + ->method('set') + ->with('winter', 'wn-pages-plugin', $this->anything(), $this->anything()); + + $package = Composer::newVersionedPackage('winter', 'wn-pages-plugin', '', 'v2.0.3'); + $package = $package->toDetailed(); + + $storage->expects($this->once()) + ->method('has') + ->with('winter', 'wn-pages-plugin', 'v2.0.3'); + + $storage->expects($this->once()) + ->method('get') + ->with('winter', 'wn-pages-plugin', 'v2.0.3'); + + $package = Composer::newVersionedPackage('winter', 'wn-pages-plugin', '', 'v2.0.3'); + $package = $package->toDetailed(); + + $this->assertInstanceOf(\Winter\Packager\Package\DetailedVersionedPackage::class, $package); + $this->assertEquals('winter', $package->getNamespace()); + $this->assertEquals('wn-pages-plugin', $package->getName()); + $this->assertEquals('winter-plugin', $package->getType()); + $this->assertEquals('https://github.com/wintercms/wn-pages-plugin', $package->getHomepage()); + $this->assertArrayHasKey('installer-name', $package->getExtras()); + $this->assertArrayNotHasKey('winter', $package->getExtras()); + $this->assertEquals('pages', $package->getExtras()['installer-name']); + $this->assertEquals('2.0.3.0', $package->getVersionNormalized()); + } } diff --git a/tests/Cases/Storage/MemoryStorageTest.php b/tests/Cases/Storage/MemoryStorageTest.php index 0e76d6a..16eb8a7 100644 --- a/tests/Cases/Storage/MemoryStorageTest.php +++ b/tests/Cases/Storage/MemoryStorageTest.php @@ -45,9 +45,9 @@ public function canSetAndGetPackageData(): void 'keywords' => ['winter', 'plugin'], ]; - $this->memory->set('winter/test-package', 'v1.0.1', $packageData); + $this->memory->set('winter', 'test-package', 'v1.0.1', $packageData); - $this->assertEquals($packageData, $this->memory->get('winter/test-package', 'v1.0.1')); + $this->assertEquals($packageData, $this->memory->get('winter', 'test-package', 'v1.0.1')); } /** @@ -66,9 +66,9 @@ public function canSetAndGetPackageDataWithDifferingVersionDefinitions(): void 'keywords' => ['winter', 'plugin'], ]; - $this->memory->set('winter/test-package', '1.0.1', $packageData); + $this->memory->set('winter', 'test-package', '1.0.1', $packageData); - $this->assertEquals($packageData, $this->memory->get('winter/test-package', 'v1.0.1.0')); + $this->assertEquals($packageData, $this->memory->get('winter', 'test-package', 'v1.0.1.0')); } /** @@ -79,7 +79,7 @@ public function returnsNullWhenPackageDoesNotExistInStorage(): void { $this->memory = new Memory(); - $this->assertNull($this->memory->get('winter/test-package', 'v1.0.1')); + $this->assertNull($this->memory->get('winter', 'test-package', 'v1.0.1')); } /** @@ -112,24 +112,24 @@ public function canGetMultipleVersionsOfAPackage(): void 'keywords' => ['winter', 'plugin'], ]; - $this->memory->set('winter/test-package', 'v1.0.1', $packageData101); - $this->memory->set('winter/test-package', 'v1.0.2', $packageData102); - $this->memory->set('winter/test-package-2', 'v1.0.1', $packageData103); + $this->memory->set('winter', 'test-package', 'v1.0.1', $packageData101); + $this->memory->set('winter', 'test-package', 'v1.0.2', $packageData102); + $this->memory->set('winter', 'test-package-2', 'v1.0.1', $packageData103); - $this->assertEquals($packageData101, $this->memory->get('winter/test-package', 'v1.0.1')); - $this->assertEquals($packageData102, $this->memory->get('winter/test-package', 'v1.0.2')); - $this->assertEquals($packageData103, $this->memory->get('winter/test-package-2', 'v1.0.1')); + $this->assertEquals($packageData101, $this->memory->get('winter', 'test-package', 'v1.0.1')); + $this->assertEquals($packageData102, $this->memory->get('winter', 'test-package', 'v1.0.2')); + $this->assertEquals($packageData103, $this->memory->get('winter', 'test-package-2', 'v1.0.1')); - $this->assertCount(2, $this->memory->get('winter/test-package')); + $this->assertCount(2, $this->memory->get('winter', 'test-package')); $this->assertEquals([ '1.0.1.0' => $packageData101, '1.0.2.0' => $packageData102, - ], $this->memory->get('winter/test-package')); + ], $this->memory->get('winter', 'test-package')); - $this->assertCount(1, $this->memory->get('winter/test-package-2')); + $this->assertCount(1, $this->memory->get('winter', 'test-package-2')); $this->assertEquals([ '1.0.1.0' => $packageData103, - ], $this->memory->get('winter/test-package-2')); + ], $this->memory->get('winter', 'test-package-2')); } /** @@ -163,18 +163,18 @@ public function canCheckExistenceOfPackagesAndVersions(): void 'keywords' => ['winter', 'plugin'], ]; - $this->memory->set('winter/test-package', 'v1.0.1', $packageData101); - $this->memory->set('winter/test-package', 'v1.0.2', $packageData102); - $this->memory->set('winter/test-package', 'v1.0.3', $packageData103); + $this->memory->set('winter', 'test-package', 'v1.0.1', $packageData101); + $this->memory->set('winter', 'test-package', 'v1.0.2', $packageData102); + $this->memory->set('winter', 'test-package', 'v1.0.3', $packageData103); - $this->assertTrue($this->memory->has('winter/test-package')); - $this->assertTrue($this->memory->has('winter/test-package', 'v1.0.1')); - $this->assertTrue($this->memory->has('winter/test-package', 'v1.0.2')); - $this->assertTrue($this->memory->has('winter/test-package', 'v1.0.3')); - $this->assertFalse($this->memory->has('winter/another-package')); - $this->assertFalse($this->memory->has('winter/test-package', 'v1.0.4')); - $this->assertFalse($this->memory->has('winter/test-package', '2')); - $this->assertFalse($this->memory->has('winter/test-package', 'v2.0.1.0')); + $this->assertTrue($this->memory->has('winter', 'test-package')); + $this->assertTrue($this->memory->has('winter', 'test-package', 'v1.0.1')); + $this->assertTrue($this->memory->has('winter', 'test-package', 'v1.0.2')); + $this->assertTrue($this->memory->has('winter', 'test-package', 'v1.0.3')); + $this->assertFalse($this->memory->has('winter', 'another-package')); + $this->assertFalse($this->memory->has('winter', 'test-package', 'v1.0.4')); + $this->assertFalse($this->memory->has('winter', 'test-package', '2')); + $this->assertFalse($this->memory->has('winter', 'test-package', 'v2.0.1.0')); } /** @@ -208,20 +208,20 @@ public function canForgetPackagesAndVersions(): void 'keywords' => ['winter', 'plugin'], ]; - $this->memory->set('winter/test-package', 'v1.0.1', $packageData101); - $this->memory->set('winter/test-package', 'v1.0.2', $packageData102); - $this->memory->set('winter/test-package-2', 'v1.0.1', $packageData103); + $this->memory->set('winter', 'test-package', 'v1.0.1', $packageData101); + $this->memory->set('winter', 'test-package', 'v1.0.2', $packageData102); + $this->memory->set('winter', 'test-package-2', 'v1.0.1', $packageData103); - $this->memory->forget('winter/test-package', '1.0.1'); + $this->memory->forget('winter', 'test-package', '1.0.1'); - $this->assertCount(1, $this->memory->get('winter/test-package')); + $this->assertCount(1, $this->memory->get('winter', 'test-package')); $this->assertEquals([ '1.0.2.0' => $packageData102, - ], $this->memory->get('winter/test-package')); + ], $this->memory->get('winter', 'test-package')); - $this->memory->forget('winter/test-package-2'); + $this->memory->forget('winter', 'test-package-2'); - $this->assertNull($this->memory->get('winter/test-package-2')); + $this->assertNull($this->memory->get('winter', 'test-package-2')); } /** @@ -255,14 +255,14 @@ public function canClearTheStorageCompletely(): void 'keywords' => ['winter', 'plugin'], ]; - $this->memory->set('winter/test-package', 'v1.0.1', $packageData101); - $this->memory->set('winter/test-package', 'v1.0.2', $packageData102); - $this->memory->set('winter/test-package-2', 'v1.0.1', $packageData103); + $this->memory->set('winter', 'test-package', 'v1.0.1', $packageData101); + $this->memory->set('winter', 'test-package', 'v1.0.2', $packageData102); + $this->memory->set('winter', 'test-package-2', 'v1.0.1', $packageData103); $this->memory->clear(); - $this->assertNull($this->memory->get('winter/test-package')); - $this->assertNull($this->memory->get('winter/test-package', '1.0.2')); - $this->assertNull($this->memory->get('winter/test-package-2')); + $this->assertNull($this->memory->get('winter', 'test-package')); + $this->assertNull($this->memory->get('winter', 'test-package', '1.0.2')); + $this->assertNull($this->memory->get('winter', 'test-package-2')); } }