diff --git a/migrations/Version20260120114538.php b/migrations/Version20260120114538.php new file mode 100644 index 00000000..07c546a8 --- /dev/null +++ b/migrations/Version20260120114538.php @@ -0,0 +1,814 @@ +createMetadataTables(); + $this->fillMetadataTables(); + $this->addCurrentMetadataColumnToVersionTable(); + $this->updateVersionTableColumns(); + $this->dropVersionLinkTables(); + $this->dropVersionTableFields(); + } + + public function down(Schema $schema): void + { + $this->createVersionTableFields(); + $this->createVersionLinkTables(); + $this->revertVersionTableColumns(); + $this->fillVersionTables(); + $this->dropCurrentMetadataColumnFromVersionTable(); + $this->dropMetadataTables(); + } + + private function createMetadataTables(): void + { + $this->addSql(<<<'SQL' + CREATE TABLE metadata ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + revision INT NOT NULL, + package_name VARCHAR(255) NOT NULL, + version_name VARCHAR(255) NOT NULL, + normalized_version_name VARCHAR(191) NOT NULL, + description TEXT DEFAULT NULL, + readme TEXT DEFAULT NULL, + homepage VARCHAR(255) DEFAULT NULL, + license JSON NOT NULL, + type VARCHAR(255) DEFAULT NULL, + target_dir VARCHAR(255) DEFAULT NULL, + source JSON DEFAULT NULL, + dist JSON DEFAULT NULL, + autoload JSON NOT NULL, + binaries JSON DEFAULT NULL, + include_paths JSON DEFAULT NULL, + php_ext JSON DEFAULT NULL, + authors JSON DEFAULT NULL, + support JSON DEFAULT NULL, + funding JSON DEFAULT NULL, + extra JSON DEFAULT NULL, + released_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, + created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + last_modified_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, + version_id INT NOT NULL, + package_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_4F1434144BBC2705 ON metadata (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_4F143414F44CABFF ON metadata (package_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_keyword ( + metadata_id INT NOT NULL, + keyword_id INT NOT NULL, + PRIMARY KEY (metadata_id, keyword_id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_C8E2A1C3DC9EE959 ON metadata_keyword (metadata_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_C8E2A1C3115D4552 ON metadata_keyword (keyword_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_conflict_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + metadata_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_97454B50DC9EE959 ON metadata_conflict_link (metadata_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_dev_require_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + metadata_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_762BA762DC9EE959 ON metadata_dev_require_link (metadata_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_provide_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + metadata_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_571F5289DC9EE959 ON metadata_provide_link (metadata_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_replace_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + metadata_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_C5E02AEFDC9EE959 ON metadata_replace_link (metadata_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_require_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + metadata_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_380D3934DC9EE959 ON metadata_require_link (metadata_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE metadata_suggest_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + metadata_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9C8AE50DDC9EE959 ON metadata_suggest_link (metadata_id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata + ADD + CONSTRAINT FK_4F143414F44CABFF FOREIGN KEY (package_id) REFERENCES package (id) NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata + ADD + CONSTRAINT FK_4F1434144BBC2705 FOREIGN KEY (version_id) REFERENCES version (id) ON DELETE CASCADE NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_keyword + ADD + CONSTRAINT FK_C8E2A1C3DC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) ON DELETE CASCADE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_keyword + ADD + CONSTRAINT FK_C8E2A1C3115D4552 FOREIGN KEY (keyword_id) REFERENCES keyword (id) ON DELETE CASCADE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_conflict_link + ADD + CONSTRAINT FK_97454B50DC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_dev_require_link + ADD + CONSTRAINT FK_762BA762DC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_provide_link + ADD + CONSTRAINT FK_571F5289DC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_replace_link + ADD + CONSTRAINT FK_C5E02AEFDC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_require_link + ADD + CONSTRAINT FK_380D3934DC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) NOT DEFERRABLE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + metadata_suggest_link + ADD + CONSTRAINT FK_9C8AE50DDC9EE959 FOREIGN KEY (metadata_id) REFERENCES metadata (id) NOT DEFERRABLE + SQL); + } + + private function fillMetadataTables(): void + { + $this->addSql(<<<'SQL' + INSERT INTO metadata ( + revision, + package_name, + version_name, + normalized_version_name, + description, + readme, + homepage, + license, + type, + target_dir, + source, + dist, + autoload, + binaries, + include_paths, + php_ext, + authors, + support, + funding, + extra, + released_at, + created_at, + version_id, + package_id + ) + SELECT + 1, + name, + version, + normalized_version, + description, + readme, + homepage, + license, + type, + target_dir, + source, + dist, + autoload, + binaries, + include_paths, + php_ext, + authors, + support, + funding, + extra, + released_at, + CURRENT_DATE, + id, + package_id + FROM version + WHERE NOT EXISTS (SELECT 1 FROM metadata WHERE metadata.version_id = version.id) + SQL); + + $linkTables = ['require', 'dev_require', 'conflict', 'provide', 'replace', 'suggest']; + + foreach ($linkTables as $linkTable) { + $this->addSql(<<addSql(<<<'SQL' + ALTER TABLE version ADD current_metadata_id INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version + ADD + CONSTRAINT FK_BF1CD3C3624A4280 FOREIGN KEY (current_metadata_id) REFERENCES metadata (id) + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_BF1CD3C3624A4280 ON version (current_metadata_id) + SQL); + + $this->addSql(<<<'SQL' + UPDATE version + SET current_metadata_id = metadata.id + FROM metadata + WHERE metadata.version_id = version.id + SQL); + } + + private function updateVersionTableColumns(): void + { + $this->addSql(<<<'SQL' + DROP INDEX pkg_ver_idx + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version RENAME COLUMN name TO package_name + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version RENAME COLUMN version TO name + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version RENAME COLUMN normalized_version TO normalized_name + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX package_version_idx ON version (package_id, normalized_name) + SQL); + + $this->addSql(<<<'SQL' + ALTER TABLE version DROP CONSTRAINT fk_bf1cd3c3f44cabff + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ALTER package_id SET NOT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version + ADD + CONSTRAINT FK_BF1CD3C3F44CABFF FOREIGN KEY (package_id) REFERENCES package (id) ON DELETE CASCADE NOT DEFERRABLE + SQL); + } + + private function dropVersionLinkTables(): void + { + $this->addSql(<<<'SQL' + ALTER TABLE version_conflict_link DROP CONSTRAINT fk_ad52d6c4bbc2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_dev_require_link DROP CONSTRAINT fk_98f2e46e4bbc2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_keyword DROP CONSTRAINT fk_a65a946f4bbc2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_keyword DROP CONSTRAINT fk_a65a946f115d4552 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_provide_link DROP CONSTRAINT fk_150ec10c4bbc2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_replace_link DROP CONSTRAINT fk_87f1b96a4bbc2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_require_link DROP CONSTRAINT fk_7a1caab14bbc2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version_suggest_link DROP CONSTRAINT fk_de9b76884bbc2705 + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_conflict_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_dev_require_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_keyword + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_provide_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_replace_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_require_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE version_suggest_link + SQL); + } + + private function dropVersionTableFields(): void + { + $this->addSql(<<<'SQL' + ALTER TABLE version DROP package_name + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP description + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP readme + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP homepage + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP license + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP type + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP target_dir + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP source + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP dist + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP autoload + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP binaries + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP include_paths + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP php_ext + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP authors + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP support + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP funding + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP extra + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP released_at + SQL); + } + + private function createVersionTableFields(): void + { + $this->addSql(<<<'SQL' + ALTER TABLE version ADD package_name VARCHAR(255) DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD description TEXT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD readme TEXT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD homepage VARCHAR(255) DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD license JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD type VARCHAR(255) DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD target_dir VARCHAR(255) DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD source JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD dist JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD autoload JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD binaries JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD include_paths JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD php_ext JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD authors JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD support JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD funding JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD extra JSON DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ADD released_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL + SQL); + + $this->addSql(<<<'SQL' + UPDATE version + SET + package_name = metadata.package_name, + license = metadata.license, + autoload = metadata.autoload, + FROM metadata + WHERE version.current_metadata_id = metadata.id + SQL); + + $this->addSql(<<<'SQL' + ALTER TABLE version ALTER COLUMN package_name SET NOT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ALTER COLUMN license SET NOT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ALTER COLUMN autoload SET NOT NULL + SQL); + } + + private function createVersionLinkTables(): void + { + $this->addSql(<<<'SQL' + CREATE TABLE version_conflict_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + version_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_ad52d6c4bbc2705 ON version_conflict_link (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE version_dev_require_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + version_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_98f2e46e4bbc2705 ON version_dev_require_link (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE version_keyword ( + version_id INT NOT NULL, + keyword_id INT NOT NULL, + PRIMARY KEY (version_id, keyword_id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_a65a946f115d4552 ON version_keyword (keyword_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_a65a946f4bbc2705 ON version_keyword (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE version_provide_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + version_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_150ec10c4bbc2705 ON version_provide_link (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE version_replace_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + version_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_87f1b96a4bbc2705 ON version_replace_link (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE version_require_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + version_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_7a1caab14bbc2705 ON version_require_link (version_id) + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE version_suggest_link ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + linked_package_name VARCHAR(191) NOT NULL, + linked_version_constraint TEXT NOT NULL, + version_id INT NOT NULL, + PRIMARY KEY (id) + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_de9b76884bbc2705 ON version_suggest_link (version_id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_conflict_link + ADD + CONSTRAINT fk_ad52d6c4bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_dev_require_link + ADD + CONSTRAINT fk_98f2e46e4bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_keyword + ADD + CONSTRAINT fk_a65a946f4bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_keyword + ADD + CONSTRAINT fk_a65a946f115d4552 FOREIGN KEY (keyword_id) REFERENCES keyword (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_provide_link + ADD + CONSTRAINT fk_150ec10c4bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_replace_link + ADD + CONSTRAINT fk_87f1b96a4bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_require_link + ADD + CONSTRAINT fk_7a1caab14bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version_suggest_link + ADD + CONSTRAINT fk_de9b76884bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } + + private function revertVersionTableColumns(): void + { + $this->addSql(<<<'SQL' + DROP INDEX package_version_idx + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version RENAME COLUMN name TO version + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version RENAME COLUMN normalized_name TO normalized_version + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version RENAME COLUMN package_name TO name + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX pkg_ver_idx ON version (package_id, normalized_version) + SQL); + + $this->addSql(<<<'SQL' + ALTER TABLE version DROP CONSTRAINT FK_BF1CD3C3F44CABFF + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version ALTER package_id DROP NOT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + version + ADD + CONSTRAINT fk_bf1cd3c3f44cabff FOREIGN KEY (package_id) REFERENCES package (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } + + private function fillVersionTables(): void + { + $this->addSql(<<<'SQL' + UPDATE version + SET + description = metadata.description, + readme = metadata.readme, + homepage = metadata.homepage, + type = metadata.type, + target_dir = metadata.target_dir, + source = metadata.source, + dist = metadata.dist, + binaries = metadata.binaries, + include_paths = metadata.include_paths, + php_ext = metadata.php_ext, + authors = metadata.authors, + support = metadata.support, + funding = metadata.funding, + extra = metadata.extra, + released_at = metadata.released_at + FROM metadata + WHERE version.current_metadata_id = metadata.id + SQL); + + $linkTables = ['require', 'dev_require', 'conflict', 'provide', 'replace', 'suggest']; + + foreach ($linkTables as $linkTable) { + $this->addSql(<<addSql(<<<'SQL' + ALTER TABLE version DROP CONSTRAINT FK_BF1CD3C3624A4280 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX UNIQ_BF1CD3C3624A4280 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE version DROP current_metadata_id + SQL); + } + + private function dropMetadataTables(): void + { + $this->addSql(<<<'SQL' + ALTER TABLE metadata DROP CONSTRAINT FK_4F1434144BBC2705 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata DROP CONSTRAINT FK_4F143414F44CABFF + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_keyword DROP CONSTRAINT FK_C8E2A1C3DC9EE959 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_keyword DROP CONSTRAINT FK_C8E2A1C3115D4552 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_conflict_link DROP CONSTRAINT FK_97454B50DC9EE959 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_dev_require_link DROP CONSTRAINT FK_762BA762DC9EE959 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_provide_link DROP CONSTRAINT FK_571F5289DC9EE959 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_replace_link DROP CONSTRAINT FK_C5E02AEFDC9EE959 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_require_link DROP CONSTRAINT FK_380D3934DC9EE959 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE metadata_suggest_link DROP CONSTRAINT FK_9C8AE50DDC9EE959 + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_keyword + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_conflict_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_dev_require_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_provide_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_replace_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_require_link + SQL); + $this->addSql(<<<'SQL' + DROP TABLE metadata_suggest_link + SQL); + } +} diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 4660f082..a9ea39de 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -11,8 +11,8 @@ parameters: - tests/bootstrap.php ignoreErrors: - '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' - - '#CodedMonkey\\Dirigent\\Doctrine\\Entity\\AbstractVersionLink given\.#' - - '#^PHPDoc tag @var with type CodedMonkey\\Dirigent\\Doctrine\\Entity\\AbstractVersionLink is not subtype of native type#' + - '#CodedMonkey\\Dirigent\\Doctrine\\Entity\\AbstractMetadataLink given\.#' + - '#^PHPDoc tag @var with type CodedMonkey\\Dirigent\\Doctrine\\Entity\\AbstractMetadataLink is not subtype of native type#' - '#^Property CodedMonkey\\Dirigent\\Doctrine\\Entity\\[a-zA-Z]+\:\:\$id \(int\|null\) is never assigned int so it can be removed from the property type\.$#' - message: '#^Class CodedMonkey\\Dirigent\\Doctrine\\Entity\\TrackedEntity has an uninitialized readonly property \$createdAt\. Assign it in the constructor\.$#' diff --git a/src/Controller/ApiController.php b/src/Controller/ApiController.php index bfb5fc73..ea94b017 100644 --- a/src/Controller/ApiController.php +++ b/src/Controller/ApiController.php @@ -128,7 +128,7 @@ public function packageDistribution(Request $request, string $reference, string throw $this->createNotFoundException(); } - if (null === $version = $this->versionRepository->findOneByNormalizedVersion($package, $versionName)) { + if (null === $version = $this->versionRepository->findOneByNormalizedVersionName($package, $versionName)) { throw $this->createNotFoundException(); } diff --git a/src/Controller/Dashboard/DashboardPackagesInfoController.php b/src/Controller/Dashboard/DashboardPackagesInfoController.php index 74dbe0ef..3c895cf0 100644 --- a/src/Controller/Dashboard/DashboardPackagesInfoController.php +++ b/src/Controller/Dashboard/DashboardPackagesInfoController.php @@ -52,6 +52,7 @@ public function versionInfo(#[MapPackage] Package $package, #[MapPackage] Versio return $this->render('dashboard/packages/package_info.html.twig', [ 'package' => $package, 'version' => $version, + 'metadata' => $version->getCurrentMetadata(), 'dependentCount' => $dependentCount, 'implementationCount' => $implementationCount, diff --git a/src/Doctrine/Entity/AbstractVersionLink.php b/src/Doctrine/Entity/AbstractMetadataLink.php similarity index 80% rename from src/Doctrine/Entity/AbstractVersionLink.php rename to src/Doctrine/Entity/AbstractMetadataLink.php index f5da71b0..20ac194d 100644 --- a/src/Doctrine/Entity/AbstractVersionLink.php +++ b/src/Doctrine/Entity/AbstractMetadataLink.php @@ -6,14 +6,14 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\MappedSuperclass] -abstract class AbstractVersionLink +abstract class AbstractMetadataLink { #[ORM\Id] #[ORM\Column] #[ORM\GeneratedValue] private ?int $id = null; - protected Version $version; + protected Metadata $metadata; #[ORM\Column(length: 191)] private string $linkedPackageName; @@ -21,19 +21,19 @@ abstract class AbstractVersionLink #[ORM\Column(type: Types::TEXT)] private string $linkedVersionConstraint; - public function getId(): ?int + public function __construct(Metadata $metadata) { - return $this->id; + $this->metadata = $metadata; } - public function getVersion(): Version + public function getId(): ?int { - return $this->version; + return $this->id; } - public function setVersion(Version $version): void + public function getMetadata(): Metadata { - $this->version = $version; + return $this->metadata; } public function getLinkedPackageName(): string diff --git a/src/Doctrine/Entity/Keyword.php b/src/Doctrine/Entity/Keyword.php index 21e1c06e..837a42e4 100644 --- a/src/Doctrine/Entity/Keyword.php +++ b/src/Doctrine/Entity/Keyword.php @@ -3,11 +3,10 @@ namespace CodedMonkey\Dirigent\Doctrine\Entity; use CodedMonkey\Dirigent\Doctrine\Repository\KeywordRepository; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: KeywordRepository::class)] +#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')] class Keyword { #[ORM\Id] @@ -18,13 +17,9 @@ class Keyword #[ORM\Column(length: 191)] private string $name; - #[ORM\ManyToMany(targetEntity: Version::class, mappedBy: 'keywords')] - protected Collection $versions; - public function __construct(string $name) { $this->name = $name; - $this->versions = new ArrayCollection(); } public function getId(): ?int diff --git a/src/Doctrine/Entity/Metadata.php b/src/Doctrine/Entity/Metadata.php new file mode 100644 index 00000000..39feb887 --- /dev/null +++ b/src/Doctrine/Entity/Metadata.php @@ -0,0 +1,675 @@ + + */ + #[ORM\OneToMany(targetEntity: MetadataRequireLink::class, mappedBy: 'metadata', cascade: ['persist', 'detach', 'remove'])] + private Collection $require; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: MetadataDevRequireLink::class, mappedBy: 'metadata', cascade: ['persist', 'detach', 'remove'])] + private Collection $devRequire; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: MetadataConflictLink::class, mappedBy: 'metadata', cascade: ['persist', 'detach', 'remove'])] + private Collection $conflict; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: MetadataProvideLink::class, mappedBy: 'metadata', cascade: ['persist', 'detach', 'remove'])] + private Collection $provide; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: MetadataReplaceLink::class, mappedBy: 'metadata', cascade: ['persist', 'detach', 'remove'])] + private Collection $replace; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: MetadataSuggestLink::class, mappedBy: 'metadata', cascade: ['persist', 'detach', 'remove'])] + private Collection $suggest; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: Keyword::class, cascade: ['persist', 'detach', 'remove'])] + private Collection $keywords; + + public function __construct(Version $version) + { + $this->version = $version; + $this->package = $version->getPackage(); + + $this->require = new ArrayCollection(); + $this->devRequire = new ArrayCollection(); + $this->conflict = new ArrayCollection(); + $this->provide = new ArrayCollection(); + $this->replace = new ArrayCollection(); + $this->suggest = new ArrayCollection(); + $this->keywords = new ArrayCollection(); + } + + public function __toString(): string + { + return "$this->packageName $this->versionName ($this->normalizedVersionName)"; + } + + public function getId(): int + { + return $this->id; + } + + public function getRevision(): int + { + return $this->revision; + } + + public function setRevision(int $revision): void + { + $this->revision = $revision; + } + + public function getPackageName(): string + { + return $this->packageName; + } + + public function setPackageName(string $packageName): void + { + $this->packageName = $packageName; + } + + public function getVersionName(): string + { + return $this->versionName; + } + + public function setVersionName(string $versionName): void + { + $this->versionName = $versionName; + } + + public function getNormalizedVersionName(): string + { + return $this->normalizedVersionName; + } + + public function setNormalizedVersionName(string $normalizedVersionName): void + { + $this->normalizedVersionName = $normalizedVersionName; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): void + { + $this->description = $description; + } + + public function getReadme(): ?string + { + return $this->readme; + } + + public function setReadme(?string $readme): void + { + $this->readme = $readme; + } + + public function getHomepage(): ?string + { + return $this->homepage; + } + + public function setHomepage(?string $homepage): void + { + $this->homepage = $homepage; + } + + /** + * @return string[] + */ + public function getLicense(): array + { + return $this->license; + } + + /** + * @param string[] $license + */ + public function setLicense(array $license): void + { + $this->license = $license; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): void + { + $this->type = $type; + } + + public function getTargetDir(): ?string + { + return $this->targetDir; + } + + public function setTargetDir(?string $targetDir): void + { + $this->targetDir = $targetDir; + } + + public function getSource(): ?array + { + return $this->source; + } + + public function setSource(?array $source): void + { + $this->source = $source; + } + + public function getDist(): ?array + { + return $this->dist; + } + + public function setDist(?array $dist): void + { + $this->dist = $dist; + } + + public function getAutoload(): array + { + return $this->autoload; + } + + public function setAutoload(array $autoload): void + { + $this->autoload = $autoload; + } + + /** + * @return string[]|null + */ + public function getBinaries(): ?array + { + return $this->binaries; + } + + /** + * @param string[]|null $binaries + */ + public function setBinaries(?array $binaries): void + { + $this->binaries = $binaries; + } + + /** + * @return string[]|null + */ + public function getIncludePaths(): ?array + { + return $this->includePaths; + } + + /** + * @param string[]|null $paths + */ + public function setIncludePaths(?array $paths): void + { + $this->includePaths = $paths; + } + + public function getPhpExt(): ?array + { + return $this->phpExt; + } + + public function setPhpExt(?array $phpExt): void + { + $this->phpExt = $phpExt; + } + + public function getAuthors(): array + { + return $this->authors ?? []; + } + + public function setAuthors(array $authors): void + { + $this->authors = $authors; + } + + public function getSupport(): ?array + { + return $this->support; + } + + public function setSupport(?array $support): void + { + $this->support = $support; + } + + public function getFunding(): ?array + { + return $this->funding; + } + + public function setFunding(?array $funding): void + { + $this->funding = $funding; + } + + /** + * @return array|null + */ + public function getExtra(): ?array + { + return $this->extra; + } + + /** + * @param array|null $extra + */ + public function setExtra(?array $extra): void + { + $this->extra = $extra; + } + + public function getReleasedAt(): ?\DateTimeImmutable + { + return $this->releasedAt; + } + + public function setReleasedAt(?\DateTimeImmutable $releasedAt): void + { + $this->releasedAt = $releasedAt; + } + + public function getVersion(): Version + { + return $this->version; + } + + public function getPackage(): Package + { + return $this->package; + } + + /** + * @return Collection + */ + public function getRequire(): Collection + { + return $this->require; + } + + /** + * @return Collection + */ + public function getDevRequire(): Collection + { + return $this->devRequire; + } + + /** + * @return Collection + */ + public function getConflict(): Collection + { + return $this->conflict; + } + + /** + * @return Collection + */ + public function getProvide(): Collection + { + return $this->provide; + } + + /** + * @return Collection + */ + public function getReplace(): Collection + { + return $this->replace; + } + + /** + * @return Collection + */ + public function getSuggest(): Collection + { + return $this->suggest; + } + + /** + * @return Collection + */ + public function getKeywords(): Collection + { + return $this->keywords; + } + + public function hasSource(): bool + { + return null !== $this->source; + } + + public function getSourceReference(): ?string + { + return $this->source['reference'] ?? null; + } + + public function getSourceType(): ?string + { + return $this->source['type'] ?? null; + } + + public function getSourceUrl(): ?string + { + return $this->source['url'] ?? null; + } + + public function hasDist(): bool + { + return null !== $this->dist; + } + + public function getDistReference(): ?string + { + return $this->dist['reference'] ?? null; + } + + public function getDistType(): ?string + { + return $this->dist['type'] ?? null; + } + + public function getDistUrl(): ?string + { + return $this->dist['url'] ?? null; + } + + public function hasVersionAlias(): bool + { + return $this->version->isDevelopment() && $this->getVersionAlias(); + } + + public function getVersionAlias(): string + { + if (null !== $alias = $this->extra['branch-alias'][$this->versionName] ?? null) { + $alias = (new VersionParser())->normalizeBranch(str_replace('-dev', '', $alias)); + $alias = Preg::replace('{(\.9{7})+}', '.x', $alias); + + return $alias; + } + + return ''; + } + + /** + * Get authors, sorted to help the V2 metadata compression algo. + * + * @return array|null + */ + public function getAuthorsSorted(): ?array + { + if (null === $this->authors) { + return null; + } + + $authors = $this->authors; + foreach ($authors as &$author) { + usort($author, static function ($a, $b) { + static $order = ['name' => 1, 'email' => 2, 'homepage' => 3, 'role' => 4]; + $indexA = $order[$a] ?? 5; + $indexB = $order[$b] ?? 5; + + // Sort by order, or alphabetically if the fields are not pre-defined + return $indexA !== $indexB ? $indexA <=> $indexB : $a <=> $b; + }); + } + + return $authors; + } + + /** + * Get funding, sorted to help the V2 metadata compression algo. + * + * @return array|null + */ + public function getFundingSorted(): ?array + { + if (null === $this->funding) { + return null; + } + + $funding = $this->funding; + usort($funding, static function ($a, $b) { + $keyA = ($a['type'] ?? '') . ($a['url'] ?? ''); + $keyB = ($b['type'] ?? '') . ($b['url'] ?? ''); + + return $keyA <=> $keyB; + }); + + return $funding; + } + + public function getKeywordNames(): array + { + $names = []; + foreach ($this->keywords as $keyword) { + $names[] = $keyword->getName(); + } + + return $names; + } + + public function getBrowsableRepositoryUrl(): ?string + { + $reference = $this->getSourceReference(); + $url = $this->package->getBrowsableRepositoryUrl(); + if (null === $reference || null === $url) { + return null; + } + + if (str_starts_with($url, 'https://github.com/')) { + return "$url/tree/$reference"; + } elseif (str_starts_with($url, 'https://gitlab.com/')) { + return "$url/-/tree/$reference"; + } elseif (str_starts_with($url, 'https://bitbucket.org/')) { + return "$url/src/$reference/"; + } + + return null; + } + + public function toComposerArray(): array + { + // Set default fields + $data = [ + 'name' => $this->packageName, + 'description' => (string) $this->description, + 'keywords' => $this->getKeywordNames(), + 'homepage' => (string) $this->homepage, + 'version' => $this->versionName, + 'version_normalized' => $this->normalizedVersionName, + 'license' => $this->license, + 'authors' => $this->getAuthorsSorted(), + 'source' => $this->source, + 'dist' => $this->dist, + 'type' => $this->type, + 'autoload' => $this->autoload, + ]; + + // Set links + $supportedLinkTypes = [ + 'require' => $this->require, + 'require-dev' => $this->devRequire, + 'suggest' => $this->suggest, + 'conflict' => $this->conflict, + 'provide' => $this->provide, + 'replace' => $this->replace, + ]; + + foreach ($supportedLinkTypes as $linkType => $linkCollection) { + /** @var AbstractMetadataLink $link */ + foreach ($linkCollection as $link) { + $data[$linkType][$link->getLinkedPackageName()] = $link->getLinkedVersionConstraint(); + } + } + + // Set optional fields + if (null !== $this->support) { + $data['support'] = $this->support; + ksort($data['support']); + } + if (null !== $phpExt = $this->phpExt) { + if (isset($phpExt['configure-options'])) { + usort($phpExt['configure-options'], fn ($a, $b) => ($a['name'] ?? '') <=> ($b['name'] ?? '')); + } + + $data['php-ext'] = $phpExt; + } + if (null !== $funding = $this->getFundingSorted()) { + $data['funding'] = $funding; + } + if (null !== $this->releasedAt) { + $data['time'] = $this->releasedAt->format('Y-m-d\TH:i:sP'); + } + if (null !== $this->extra) { + $data['extra'] = $this->extra; + } + if (null !== $this->targetDir) { + $data['target-dir'] = $this->targetDir; + } + if (null !== $this->includePaths) { + $data['include-path'] = $this->includePaths; + } + if (null !== $this->binaries) { + $data['bin'] = $this->binaries; + } + + // Set administrative fields + if ($this->getVersion()->isDefaultBranch()) { + $data['default-branch'] = true; + } + if ($this->getPackage()->isAbandoned()) { + $data['abandoned'] = $this->getPackage()->getReplacementPackage() ?: true; + } + + return $data; + } +} diff --git a/src/Doctrine/Entity/MetadataConflictLink.php b/src/Doctrine/Entity/MetadataConflictLink.php new file mode 100644 index 00000000..910763d5 --- /dev/null +++ b/src/Doctrine/Entity/MetadataConflictLink.php @@ -0,0 +1,14 @@ +cachedVersions)) { $this->cachedVersions = []; foreach ($this->getVersions() as $version) { - $this->cachedVersions[strtolower($version->getNormalizedVersion())] = $version; + $this->cachedVersions[strtolower($version->getNormalizedName())] = $version; } } @@ -419,17 +420,17 @@ public function getActiveVersions(): array $activePrereleaseVersions = []; foreach ($this->getSortedVersions() as $version) { - if ('stable' !== VersionParser::parseStability($version->getNormalizedVersion())) { + if ('stable' !== VersionParser::parseStability($version->getNormalizedName())) { continue; } - [$majorVersion, $minorVersion] = explode('.', $version->getNormalizedVersion()); + [$majorVersion, $minorVersion] = explode('.', $version->getNormalizedName()); if ('0' === $majorVersion) { $prereleaseVersion = "$majorVersion.$minorVersion"; $activePrereleaseVersions[$prereleaseVersion] ??= $version; - if (version_compare($version->getNormalizedVersion(), $activePrereleaseVersions[$prereleaseVersion]->getNormalizedVersion(), '>')) { + if (version_compare($version->getNormalizedName(), $activePrereleaseVersions[$prereleaseVersion]->getNormalizedName(), '>')) { $activePrereleaseVersions[$prereleaseVersion] = $version; } @@ -437,7 +438,7 @@ public function getActiveVersions(): array } $activeVersions[$majorVersion] ??= $version; - if (version_compare($version->getNormalizedVersion(), $activeVersions[$majorVersion]->getNormalizedVersion(), '>')) { + if (version_compare($version->getNormalizedName(), $activeVersions[$majorVersion]->getNormalizedName(), '>')) { $activeVersions[$majorVersion] = $version; } } @@ -447,34 +448,34 @@ public function getActiveVersions(): array // Find newer unstable releases of active versions foreach ($this->getSortedVersions() as $version) { - if (in_array(VersionParser::parseStability($version->getNormalizedVersion()), ['stable', 'dev'], true)) { + if (in_array(VersionParser::parseStability($version->getNormalizedName()), ['stable', 'dev'], true)) { continue; } - [$majorVersion, $minorVersion] = explode('.', $version->getNormalizedVersion()); + [$majorVersion, $minorVersion] = explode('.', $version->getNormalizedName()); $developmentVersion = "$majorVersion.$minorVersion"; if ('0' === $majorVersion) { - if (isset($activePrereleaseVersions[$developmentVersion]) && !version_compare($version->getNormalizedVersion(), $activePrereleaseVersions[$developmentVersion]->getNormalizedVersion(), '>')) { + if (isset($activePrereleaseVersions[$developmentVersion]) && !version_compare($version->getNormalizedName(), $activePrereleaseVersions[$developmentVersion]->getNormalizedName(), '>')) { continue; } $activePrereleaseDevelopmentVersions[$developmentVersion] ??= $version; - if (version_compare($version->getNormalizedVersion(), $activePrereleaseDevelopmentVersions[$developmentVersion]->getNormalizedVersion(), '>')) { + if (version_compare($version->getNormalizedName(), $activePrereleaseDevelopmentVersions[$developmentVersion]->getNormalizedName(), '>')) { $activePrereleaseDevelopmentVersions[$developmentVersion] = $version; } continue; } - if (isset($activeVersions[$majorVersion]) && !version_compare($version->getNormalizedVersion(), $activeVersions[$majorVersion]->getNormalizedVersion(), '>')) { + if (isset($activeVersions[$majorVersion]) && !version_compare($version->getNormalizedName(), $activeVersions[$majorVersion]->getNormalizedName(), '>')) { continue; } $activeDevelopmentVersions[$developmentVersion] ??= $version; - if (version_compare($version->getNormalizedVersion(), $activeDevelopmentVersions[$developmentVersion]->getNormalizedVersion(), '>')) { - $activeDevelopmentVersions[$version->getNormalizedVersion()] = $version; + if (version_compare($version->getNormalizedName(), $activeDevelopmentVersions[$developmentVersion]->getNormalizedName(), '>')) { + $activeDevelopmentVersions[$version->getNormalizedName()] = $version; } } @@ -514,7 +515,7 @@ public function getHistoricalVersions(): array public function getDevVersions(): array { return array_filter($this->getSortedVersions(), static function (Version $version) { - if (str_ends_with($version->getNormalizedVersion(), '.9999999-dev')) { + if (str_ends_with($version->getNormalizedName(), '.9999999-dev')) { return true; } @@ -531,13 +532,13 @@ public function getDevVersions(): array */ public function getDevBranchVersions(): array { - return array_filter($this->getSortedVersions(), static fn (Version $version) => str_starts_with($version->getNormalizedVersion(), 'dev-')); + return array_filter($this->getSortedVersions(), static fn (Version $version) => str_starts_with($version->getNormalizedName(), 'dev-')); } public static function sortVersions(Version $a, Version $b): int { - $aVersion = $a->getNormalizedVersion(); - $bVersion = $b->getNormalizedVersion(); + $aVersion = $a->getNormalizedName(); + $bVersion = $b->getNormalizedName(); // use branch alias for sorting if one is provided if (isset($a->getExtra()['branch-alias'][$aVersion])) { @@ -562,7 +563,7 @@ public static function sortVersions(Version $a, Version $b): int if ($aVersion === $bVersion) { // make sure sort is stable if ($a->getReleasedAt() === $b->getReleasedAt()) { - return $a->getNormalizedVersion() <=> $b->getNormalizedVersion(); + return $a->getNormalizedName() <=> $b->getNormalizedName(); } return $b->getReleasedAt() > $a->getReleasedAt() ? 1 : -1; diff --git a/src/Doctrine/Entity/Version.php b/src/Doctrine/Entity/Version.php index 927d0b57..49b3c3e1 100644 --- a/src/Doctrine/Entity/Version.php +++ b/src/Doctrine/Entity/Version.php @@ -3,15 +3,13 @@ namespace CodedMonkey\Dirigent\Doctrine\Entity; use CodedMonkey\Dirigent\Doctrine\Repository\VersionRepository; -use Composer\Package\Version\VersionParser; -use Composer\Pcre\Preg; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; -use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: VersionRepository::class)] -#[ORM\UniqueConstraint(name: 'pkg_ver_idx', columns: ['package_id', 'normalized_version'])] +#[ORM\UniqueConstraint(name: 'package_version_idx', columns: ['package_id', 'normalized_name'])] +#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')] class Version extends TrackedEntity { #[ORM\Id] @@ -22,123 +20,47 @@ class Version extends TrackedEntity #[ORM\Column] private string $name; - #[ORM\Column] - private string $version; - #[ORM\Column(length: 191)] - private string $normalizedVersion; - - #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $description = null; - - #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $readme = null; - - #[ORM\Column(nullable: true)] - private ?string $homepage = null; + private string $normalizedName; #[ORM\Column] private bool $development; #[ORM\Column] - private array $license; - - #[ORM\Column(nullable: true)] - private ?string $type = null; - - #[ORM\Column(nullable: true)] - private ?string $targetDir = null; - - #[ORM\Column(nullable: true)] - private ?array $source = null; - - #[ORM\Column(nullable: true)] - private ?array $dist = null; - - #[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionRequireLink::class, cascade: ['persist', 'detach', 'remove'])] - private Collection $require; - - #[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionDevRequireLink::class, cascade: ['persist', 'detach', 'remove'])] - private Collection $devRequire; - - #[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionConflictLink::class, cascade: ['persist', 'detach', 'remove'])] - private Collection $conflict; - - #[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionProvideLink::class, cascade: ['persist', 'detach', 'remove'])] - private Collection $provide; - - #[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionReplaceLink::class, cascade: ['persist', 'detach', 'remove'])] - private Collection $replace; - - #[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionSuggestLink::class, cascade: ['persist', 'detach', 'remove'])] - private Collection $suggest; - - #[ORM\ManyToMany(targetEntity: Keyword::class, inversedBy: 'versions', cascade: ['persist', 'detach', 'remove'])] - private Collection $keywords; - - #[ORM\Column] - private array $autoload; - - /** - * @var array|null - */ - #[ORM\Column(nullable: true)] - private ?array $binaries = null; - - /** - * @var array|null - */ - #[ORM\Column(nullable: true)] - private ?array $includePaths = null; - - #[ORM\Column(nullable: true)] - private ?array $phpExt = null; - - #[ORM\Column(nullable: true)] - private ?array $authors = null; - - #[ORM\Column(nullable: true)] - private ?array $support = null; - - #[ORM\Column(nullable: true)] - private ?array $funding = null; + private bool $defaultBranch = false; #[ORM\Column(nullable: true)] - private ?array $extra = null; - - #[ORM\Column] - private bool $defaultBranch = false; + private ?\DateTimeImmutable $updatedAt = null; #[ORM\ManyToOne(targetEntity: Package::class, inversedBy: 'versions')] - private ?Package $package = null; + #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] + private Package $package; + + #[ORM\OneToOne] + private ?Metadata $currentMetadata = null; #[ORM\OneToOne(mappedBy: 'version', cascade: ['persist', 'detach', 'remove'])] private VersionInstallations $installations; - #[ORM\Column(nullable: true)] - private ?\DateTimeImmutable $updatedAt = null; - - #[ORM\Column(nullable: true)] - private ?\DateTimeImmutable $releasedAt = null; + #[ORM\OneToMany(targetEntity: Metadata::class, mappedBy: 'version', cascade: ['persist', 'detach', 'remove'])] + private Collection $metadata; - public function __construct() + public function __construct(Package $package) { - $this->require = new ArrayCollection(); - $this->devRequire = new ArrayCollection(); - $this->conflict = new ArrayCollection(); - $this->provide = new ArrayCollection(); - $this->replace = new ArrayCollection(); - $this->suggest = new ArrayCollection(); - $this->keywords = new ArrayCollection(); + $this->package = $package; + $this->installations = new VersionInstallations($this); + $this->metadata = new ArrayCollection(); } public function __toString(): string { - return "$this->name $this->version ($this->normalizedVersion)"; + $packageName = $this->package->getName(); + + return "$packageName $this->name ($this->normalizedName)"; } - public function getId(): int + public function getId(): ?int { return $this->id; } @@ -153,198 +75,173 @@ public function setName(string $name): void $this->name = $name; } - public function getVersion(): string + public function getNormalizedName(): string { - return $this->version; + return $this->normalizedName; } - public function setVersion(string $version): void + public function setNormalizedName(string $normalizedName): void { - $this->version = $version; + $this->normalizedName = $normalizedName; } - public function getNormalizedVersion(): string + public function isDevelopment(): bool { - return $this->normalizedVersion; + return $this->development; } - public function setNormalizedVersion(string $normalizedVersion): void + public function setDevelopment(bool $development): void { - $this->normalizedVersion = $normalizedVersion; + $this->development = $development; } - public function getDescription(): ?string + public function isDefaultBranch(): bool { - return $this->description; + return $this->defaultBranch; } - public function setDescription(?string $description): void + public function setDefaultBranch(bool $defaultBranch): void { - $this->description = $description; + $this->defaultBranch = $defaultBranch; } - public function getReadme(): ?string + public function getUpdatedAt(): ?\DateTimeImmutable { - return $this->readme; + return $this->updatedAt; } - public function setReadme(?string $readme): void + public function setUpdatedAt(\DateTimeImmutable $updatedAt): void { - $this->readme = $readme; + $this->updatedAt = $updatedAt; } - public function getHomepage(): ?string + public function getReleasedAt(): ?\DateTimeImmutable { - return $this->homepage; + return $this->currentMetadata->getReleasedAt(); } - public function setHomepage(?string $homepage): void + public function getPackage(): ?Package { - $this->homepage = $homepage; + return $this->package; } - public function isDevelopment(): bool + public function getCurrentMetadata(): ?Metadata { - return $this->development; + return $this->currentMetadata; } - public function setDevelopment(bool $development): void + public function setCurrentMetadata(Metadata $metadata): void { - $this->development = $development; + $this->currentMetadata = $metadata; } - /** - * @return array - */ - public function getLicense(): array + public function getInstallations(): VersionInstallations { - return $this->license; + return $this->installations; } /** - * @param array $license + * @return Collection */ - public function setLicense(array $license): void + public function getMetadata(): Collection { - $this->license = $license; + return $this->metadata; } - public function getType(): ?string + public function getPackageName(): string { - return $this->type; + return $this->package->getName(); } - public function setType(?string $type): void + public function getDescription(): ?string { - $this->type = $type; + return $this->currentMetadata->getDescription(); } - public function getTargetDir(): ?string + public function getReadme(): ?string { - return $this->targetDir; + return $this->currentMetadata->getReadme(); } - public function setTargetDir(?string $targetDir): void + public function getHomepage(): ?string { - $this->targetDir = $targetDir; + return $this->currentMetadata->getHomepage(); } - public function getSource(): ?array + /** + * @return string[] + */ + public function getLicense(): array { - return $this->source; + return $this->currentMetadata->getLicense(); } - public function setSource(?array $source): void + public function getType(): ?string { - $this->source = $source; + return $this->currentMetadata->getType(); } - public function getDist(): ?array + public function getTargetDir(): ?string { - return $this->dist; + return $this->currentMetadata->getTargetDir(); } - public function setDist(?array $dist): void + public function getSource(): ?array { - $this->dist = $dist; + return $this->currentMetadata->getSource(); } - /** - * @return Collection - */ - public function getRequire(): Collection + public function getDist(): ?array { - return $this->require; + return $this->currentMetadata->getDist(); } - public function addRequireLink(VersionRequireLink $require): void + /** + * @return Collection + */ + public function getRequire(): Collection { - $this->require[] = $require; + return $this->currentMetadata->getRequire(); } /** - * @return Collection + * @return Collection */ public function getDevRequire(): Collection { - return $this->devRequire; - } - - public function addDevRequireLink(VersionDevRequireLink $devRequire): void - { - $this->devRequire[] = $devRequire; + return $this->currentMetadata->getDevRequire(); } /** - * @return Collection + * @return Collection */ public function getConflict(): Collection { - return $this->conflict; - } - - public function addConflictLink(VersionConflictLink $conflict): void - { - $this->conflict[] = $conflict; + return $this->currentMetadata->getConflict(); } /** - * @return Collection + * @return Collection */ public function getProvide(): Collection { - return $this->provide; - } - - public function addProvideLink(VersionProvideLink $provide): void - { - $this->provide[] = $provide; + return $this->currentMetadata->getProvide(); } /** - * @return Collection + * @return Collection */ public function getReplace(): Collection { - return $this->replace; - } - - public function addReplaceLink(VersionReplaceLink $replace): void - { - $this->replace[] = $replace; + return $this->currentMetadata->getReplace(); } /** - * @return Collection + * @return Collection */ public function getSuggest(): Collection { - return $this->suggest; - } - - public function addSuggestLink(VersionSuggestLink $suggest): void - { - $this->suggest[] = $suggest; + return $this->currentMetadata->getSuggest(); } /** @@ -352,94 +249,48 @@ public function addSuggestLink(VersionSuggestLink $suggest): void */ public function getKeywords(): Collection { - return $this->keywords; - } - - public function addKeyword(Keyword $keyword): void - { - $this->keywords[] = $keyword; + return $this->currentMetadata->getKeywords(); } public function getAutoload(): array { - return $this->autoload; - } - - public function setAutoload(array $autoload): void - { - $this->autoload = $autoload; + return $this->currentMetadata->getAutoload(); } /** - * @return array|null + * @return string[]|null */ public function getBinaries(): ?array { - return $this->binaries; - } - - /** - * @param array|null $binaries - */ - public function setBinaries(?array $binaries): void - { - $this->binaries = $binaries; + return $this->currentMetadata->getBinaries(); } /** - * @return array|null + * @return string[]|null */ public function getIncludePaths(): ?array { - return $this->includePaths; - } - - /** - * @param array|null $paths - */ - public function setIncludePaths(?array $paths): void - { - $this->includePaths = $paths; + return $this->currentMetadata->getIncludePaths(); } public function getPhpExt(): ?array { - return $this->phpExt; - } - - public function setPhpExt(?array $phpExt): void - { - $this->phpExt = $phpExt; + return $this->currentMetadata->getPhpExt(); } public function getAuthors(): array { - return $this->authors ?? []; - } - - public function setAuthors(array $authors): void - { - $this->authors = $authors; + return $this->currentMetadata->getAuthors(); } public function getSupport(): ?array { - return $this->support; - } - - public function setSupport(?array $support): void - { - $this->support = $support; + return $this->currentMetadata->getSupport(); } public function getFunding(): ?array { - return $this->funding; - } - - public function setFunding(?array $funding): void - { - $this->funding = $funding; + return $this->currentMetadata->getFunding(); } /** @@ -447,292 +298,87 @@ public function setFunding(?array $funding): void */ public function getExtra(): ?array { - return $this->extra; - } - - /** - * @param array|null $extra - */ - public function setExtra(?array $extra): void - { - $this->extra = $extra; - } - - public function isDefaultBranch(): bool - { - return $this->defaultBranch; - } - - public function setDefaultBranch(bool $defaultBranch): void - { - $this->defaultBranch = $defaultBranch; - } - - public function getPackage(): ?Package - { - return $this->package; - } - - public function setPackage(Package $package): void - { - $this->package = $package; - } - - public function getInstallations(): VersionInstallations - { - return $this->installations; - } - - public function getUpdatedAt(): ?\DateTimeImmutable - { - return $this->updatedAt; - } - - public function setUpdatedAt(\DateTimeImmutable $updatedAt): void - { - $this->updatedAt = $updatedAt; - } - - public function getReleasedAt(): ?\DateTimeImmutable - { - return $this->releasedAt; - } - - public function setReleasedAt(?\DateTimeImmutable $releasedAt): void - { - $this->releasedAt = $releasedAt; + return $this->currentMetadata->getExtra(); } public function hasSource(): bool { - return null !== $this->source; + return $this->currentMetadata->hasSource(); } public function getSourceReference(): ?string { - return $this->source['reference'] ?? null; + return $this->currentMetadata->getSourceReference(); } public function getSourceType(): ?string { - return $this->source['type'] ?? null; + return $this->currentMetadata->getSourceType(); } public function getSourceUrl(): ?string { - return $this->source['url'] ?? null; + return $this->currentMetadata->getSourceUrl(); } public function hasDist(): bool { - return null !== $this->dist; + return $this->currentMetadata->hasDist(); } public function getDistReference(): ?string { - return $this->dist['reference'] ?? null; + return $this->currentMetadata->getDistReference(); } public function getDistType(): ?string { - return $this->dist['type'] ?? null; + return $this->currentMetadata->getDistType(); } public function getDistUrl(): ?string { - return $this->dist['url'] ?? null; + return $this->currentMetadata->getDistUrl(); } public function hasVersionAlias(): bool { - return $this->isDevelopment() && $this->getVersionAlias(); + return $this->currentMetadata->hasVersionAlias(); } public function getVersionAlias(): string { - $extra = $this->getExtra(); - - if (isset($extra['branch-alias'][$this->getVersion()])) { - $parser = new VersionParser(); - $version = $parser->normalizeBranch(str_replace('-dev', '', $extra['branch-alias'][$this->getVersion()])); - - return Preg::replace('{(\.9{7})+}', '.x', $version); - } - - return ''; + return $this->currentMetadata->getVersionAlias(); } public function getVersionTitle(): string { - return $this->version . ($this->hasVersionAlias() ? ' / ' . $this->getVersionAlias() : ''); - } - - /** - * Get funding, sorted to help the V2 metadata compression algo. - * - * @return array|null - */ - public function getFundingSorted(): ?array - { - if (null === $this->funding) { - return null; - } - - $funding = $this->funding; - usort($funding, static function ($a, $b) { - $keyA = ($a['type'] ?? '') . ($a['url'] ?? ''); - $keyB = ($b['type'] ?? '') . ($b['url'] ?? ''); - - return $keyA <=> $keyB; - }); - - return $funding; + return $this->name . ($this->hasVersionAlias() ? ' / ' . $this->getVersionAlias() : ''); } public function getMajorVersion(): int { - $split = explode('.', $this->version); + $split = explode('.', $this->name); return (int) $split[0]; } public function getMinorVersion(): int { - $split = explode('.', $this->version); + $split = explode('.', $this->name); return (int) $split[1]; } public function getPatchVersion(): int { - $split = explode('.', $this->version); + $split = explode('.', $this->name); return (int) $split[2]; } public function getBrowsableRepositoryUrl(): ?string { - $reference = $this->getSourceReference(); - $url = $this->package->getBrowsableRepositoryUrl(); - if (null === $reference || null === $url) { - return null; - } - - if (false === $this->isDevelopment()) { - $reference = $this->getVersion(); - } - - if (str_starts_with($url, 'https://github.com/')) { - return "$url/tree/$reference"; - } elseif (str_starts_with($url, 'https://gitlab.com/')) { - return "$url/-/tree/$reference"; - } elseif (str_starts_with($url, 'https://bitbucket.org/')) { - return "$url/src/$reference/"; - } - - return null; - } - - public function toComposerArray(): array - { - $keywords = []; - foreach ($this->getKeywords() as $keyword) { - $keywords[] = $keyword->getName(); - } - - $authors = $this->getAuthors(); - foreach ($authors as &$author) { - uksort($author, [$this, 'sortAuthorKeys']); - } - unset($author); - - $data = [ - 'name' => $this->getName(), - 'description' => (string) $this->getDescription(), - 'keywords' => $keywords, - 'homepage' => (string) $this->getHomepage(), - 'version' => $this->getVersion(), - 'version_normalized' => $this->getNormalizedVersion(), - 'license' => $this->getLicense(), - 'authors' => $authors, - 'source' => $this->getSource(), - 'dist' => $this->getDist(), - 'type' => $this->getType(), - ]; - - if ($this->getSupport()) { - $data['support'] = $this->getSupport(); - } - if (null !== $this->getPhpExt()) { - $data['php-ext'] = $this->getPhpExt(); - } - $funding = $this->getFundingSorted(); - if (null !== $funding) { - $data['funding'] = $funding; - } - if ($this->getReleasedAt()) { - $data['time'] = $this->getReleasedAt()->format('Y-m-d\TH:i:sP'); - } - if ($this->getAutoload()) { - $data['autoload'] = $this->getAutoload(); - } - if ($this->getExtra()) { - $data['extra'] = $this->getExtra(); - } - if ($this->getTargetDir()) { - $data['target-dir'] = $this->getTargetDir(); - } - if ($this->getIncludePaths()) { - $data['include-path'] = $this->getIncludePaths(); - } - if ($this->getBinaries()) { - $data['bin'] = $this->getBinaries(); - } - - $supportedLinkTypes = [ - 'require' => 'require', - 'devRequire' => 'require-dev', - 'suggest' => 'suggest', - 'conflict' => 'conflict', - 'provide' => 'provide', - 'replace' => 'replace', - ]; - - if ($this->isDefaultBranch()) { - $data['default-branch'] = true; - } - - foreach ($supportedLinkTypes as $method => $linkType) { - /** @var AbstractVersionLink $link */ - foreach ($this->{'get' . $method}() as $link) { - $data[$linkType][$link->getLinkedPackageName()] = $link->getLinkedVersionConstraint(); - } - } - - if ($this->getPackage()->isAbandoned()) { - $data['abandoned'] = $this->getPackage()->getReplacementPackage() ?: true; - } - - if (isset($data['support'])) { - ksort($data['support']); - } - - if (isset($data['php-ext']['configure-options'])) { - usort($data['php-ext']['configure-options'], fn ($a, $b) => ($a['name'] ?? '') <=> ($b['name'] ?? '')); - } - - return $data; - } - - private function sortAuthorKeys(string $a, string $b): int - { - static $order = ['name' => 1, 'email' => 2, 'homepage' => 3, 'role' => 4]; - $aIndex = $order[$a] ?? 5; - $bIndex = $order[$b] ?? 5; - if ($aIndex === $bIndex) { - return $a <=> $b; - } - - return $aIndex <=> $bIndex; + return $this->currentMetadata->getBrowsableRepositoryUrl(); } } diff --git a/src/Doctrine/Entity/VersionConflictLink.php b/src/Doctrine/Entity/VersionConflictLink.php deleted file mode 100644 index c9e1c7f4..00000000 --- a/src/Doctrine/Entity/VersionConflictLink.php +++ /dev/null @@ -1,13 +0,0 @@ -getObjectManager()->getRepository(Metadata::class); + + $revision = $repository->getNextRevision($metadata); + $metadata->setRevision($revision); + } +} diff --git a/src/Doctrine/Repository/MetadataRepository.php b/src/Doctrine/Repository/MetadataRepository.php new file mode 100644 index 00000000..5cb372d6 --- /dev/null +++ b/src/Doctrine/Repository/MetadataRepository.php @@ -0,0 +1,59 @@ + + * + * @method Metadata|null find($id, $lockMode = null, $lockVersion = null) + * @method Metadata[] findAll() + * @method Metadata[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @method Metadata|null findOneBy(array $criteria, array $orderBy = null) + */ +class MetadataRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Metadata::class); + } + + public function save(Metadata $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(Metadata $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function getNextRevision(Metadata $metadata): int + { + $version = $metadata->getVersion(); + + if (null === $version->getId()) { + return 1; + } + + $lastRevision = $this->createQueryBuilder('metadata') + ->select('MAX(metadata.revision)') + ->where('metadata.version = :version') + ->setParameter('version', $metadata->getVersion()) + ->getQuery() + ->getSingleScalarResult(); + + return (int) $lastRevision + 1; + } +} diff --git a/src/Doctrine/Repository/VersionRepository.php b/src/Doctrine/Repository/VersionRepository.php index d0524151..45b57488 100644 --- a/src/Doctrine/Repository/VersionRepository.php +++ b/src/Doctrine/Repository/VersionRepository.php @@ -40,29 +40,25 @@ public function remove(Version $entity, bool $flush = false): void } } - public function findOneByNormalizedVersion(Package $package, string $version): ?Version + public function findOneByNormalizedVersionName(Package $package, string $name): ?Version { - return $this->findOneBy(['package' => $package, 'normalizedVersion' => $version]); + return $this->findOneBy(['package' => $package, 'normalizedName' => $name]); } /** - * @return array + * @return array */ public function getVersionMetadataForUpdate(Package $package): array { $rows = $this->getEntityManager()->getConnection()->fetchAllAssociative( - 'SELECT id, version, normalized_version, source FROM version v WHERE v.package_id = :id', - ['id' => $package->getId()] + 'SELECT id, normalized_name FROM version v WHERE v.package_id = :id', + ['id' => $package->getId()], ); $versions = []; foreach ($rows as $row) { - if ($row['source']) { - $row['source'] = json_decode($row['source'], true); - } - - $key = strtolower($row['normalized_version']); - $versions[$key] = $row; + $key = strtolower($row['normalized_name']); + $versions[$key] = $row['id']; } return $versions; diff --git a/src/Message/TrackInstallationsHandler.php b/src/Message/TrackInstallationsHandler.php index 7872ec40..a1c5eef2 100644 --- a/src/Message/TrackInstallationsHandler.php +++ b/src/Message/TrackInstallationsHandler.php @@ -24,7 +24,7 @@ public function __invoke(TrackInstallations $message): void continue; } - if (!$version = $this->versionRepository->findOneByNormalizedVersion($package, $install['version'])) { + if (!$version = $this->versionRepository->findOneByNormalizedVersionName($package, $install['version'])) { continue; } diff --git a/src/Message/UpdatePackageLinksHandler.php b/src/Message/UpdatePackageLinksHandler.php index 5fbf504a..a83f5e01 100644 --- a/src/Message/UpdatePackageLinksHandler.php +++ b/src/Message/UpdatePackageLinksHandler.php @@ -20,7 +20,7 @@ public function __construct( public function __invoke(UpdatePackageLinks $message): void { $package = $this->getPackage($this->packageRepository, $message->packageId); - $version = $this->versionRepository->findOneByNormalizedVersion($package, $message->versionName); + $version = $this->versionRepository->findOneByNormalizedVersionName($package, $message->versionName); $this->packageRepository->updatePackageLinks($package, $version); } diff --git a/src/Package/PackageDistributionResolver.php b/src/Package/PackageDistributionResolver.php index 8b7b4d46..ec88a783 100644 --- a/src/Package/PackageDistributionResolver.php +++ b/src/Package/PackageDistributionResolver.php @@ -35,7 +35,7 @@ public function resolve(Version $version, string $reference, string $type): bool { $package = $version->getPackage(); $packageName = $package->getName(); - $versionName = $version->getNormalizedVersion(); + $versionName = $version->getNormalizedName(); if ($this->exists($packageName, $versionName, $reference, $type)) { return true; diff --git a/src/Package/PackageMetadataResolver.php b/src/Package/PackageMetadataResolver.php index 7ad02e90..0229b364 100644 --- a/src/Package/PackageMetadataResolver.php +++ b/src/Package/PackageMetadataResolver.php @@ -4,18 +4,19 @@ use cebe\markdown\GithubMarkdown; use CodedMonkey\Dirigent\Composer\ComposerClient; -use CodedMonkey\Dirigent\Doctrine\Entity\AbstractVersionLink; +use CodedMonkey\Dirigent\Doctrine\Entity\AbstractMetadataLink; +use CodedMonkey\Dirigent\Doctrine\Entity\Metadata; +use CodedMonkey\Dirigent\Doctrine\Entity\MetadataConflictLink; +use CodedMonkey\Dirigent\Doctrine\Entity\MetadataDevRequireLink; +use CodedMonkey\Dirigent\Doctrine\Entity\MetadataProvideLink; +use CodedMonkey\Dirigent\Doctrine\Entity\MetadataReplaceLink; +use CodedMonkey\Dirigent\Doctrine\Entity\MetadataRequireLink; +use CodedMonkey\Dirigent\Doctrine\Entity\MetadataSuggestLink; use CodedMonkey\Dirigent\Doctrine\Entity\Package; use CodedMonkey\Dirigent\Doctrine\Entity\PackageFetchStrategy; use CodedMonkey\Dirigent\Doctrine\Entity\Registry; use CodedMonkey\Dirigent\Doctrine\Entity\RegistryPackageMirroring; use CodedMonkey\Dirigent\Doctrine\Entity\Version; -use CodedMonkey\Dirigent\Doctrine\Entity\VersionConflictLink; -use CodedMonkey\Dirigent\Doctrine\Entity\VersionDevRequireLink; -use CodedMonkey\Dirigent\Doctrine\Entity\VersionProvideLink; -use CodedMonkey\Dirigent\Doctrine\Entity\VersionReplaceLink; -use CodedMonkey\Dirigent\Doctrine\Entity\VersionRequireLink; -use CodedMonkey\Dirigent\Doctrine\Entity\VersionSuggestLink; use CodedMonkey\Dirigent\Doctrine\Repository\KeywordRepository; use CodedMonkey\Dirigent\Doctrine\Repository\RegistryRepository; use CodedMonkey\Dirigent\Doctrine\Repository\VersionRepository; @@ -23,6 +24,7 @@ use CodedMonkey\Dirigent\Message\UpdatePackageLinks; use Composer\Package\AliasPackage; use Composer\Package\CompletePackageInterface; +use Composer\Package\Link as ComposerPackageLink; use Composer\Pcre\Preg; use Composer\Repository\Vcs\VcsDriverInterface; use Doctrine\ORM\EntityManagerInterface; @@ -44,23 +46,23 @@ private const array SUPPORTED_LINK_TYPES = [ 'conflict' => [ 'method' => 'getConflicts', - 'entity' => VersionConflictLink::class, + 'entity' => MetadataConflictLink::class, ], 'devRequire' => [ 'method' => 'getDevRequires', - 'entity' => VersionDevRequireLink::class, + 'entity' => MetadataDevRequireLink::class, ], 'provide' => [ 'method' => 'getProvides', - 'entity' => VersionProvideLink::class, + 'entity' => MetadataProvideLink::class, ], 'replace' => [ 'method' => 'getReplaces', - 'entity' => VersionReplaceLink::class, + 'entity' => MetadataReplaceLink::class, ], 'require' => [ 'method' => 'getRequires', - 'entity' => VersionRequireLink::class, + 'entity' => MetadataRequireLink::class, ], ]; @@ -178,8 +180,11 @@ private function resolveVcsRepository(Package $package): void private function updatePackage(Package $package, array $composerPackages, ?VcsDriverInterface $driver = null): void { $existingVersionMetadata = $this->versionRepository->getVersionMetadataForUpdate($package); + /** @var ?Version $primaryVersion Version to use as the package info source */ $primaryVersion = null; + /** @var ?CompletePackageInterface $primaryVersionData */ + $primaryVersionData = null; // Every Composer package is a separate package version foreach ($composerPackages as $composerPackage) { @@ -187,81 +192,104 @@ private function updatePackage(Package $package, array $composerPackages, ?VcsDr continue; } - $version = $this->versionRepository->findOneByNormalizedVersion($package, $composerPackage->getVersion()) ?: new Version(); + $key = strtolower($composerPackage->getVersion()); + if ($versionId = $existingVersionMetadata[$key] ?? null) { + $version = $this->entityManager->getReference(Version::class, $versionId); + } else { + $version = new Version($package); + $version->setName($composerPackage->getPrettyVersion()); + $version->setNormalizedName($composerPackage->getVersion()); + $version->setDevelopment($composerPackage->isDev()); - if (!$package->getVersions()->contains($version)) { $package->getVersions()->add($version); - $this->entityManager->persist($version); } - $this->updateVersion($package, $version, $composerPackage, $driver); - $versionName = $version->getNormalizedVersion(); + $this->updateVersion($version, $composerPackage, $driver); // Use the first version which should be the highest stable version by default $primaryVersion ??= $version; + $primaryVersionData ??= $composerPackage; // If default branch is present however we prefer that as the canonical package link source if ($version->isDefaultBranch()) { $primaryVersion = $version; + $primaryVersionData = $composerPackage; } - unset($existingVersionMetadata[$versionName]); + unset($existingVersionMetadata[$key]); } if ($primaryVersion) { + // Update package fields from metadata + $package->setDescription($this->sanitize($primaryVersionData->getDescription())); + $package->setType($this->sanitize($primaryVersionData->getType())); + + // Update abandoned data at the package level + $package->setAbandoned($primaryVersionData->isAbandoned()); + $package->setReplacementPackage($primaryVersionData->getReplacementPackage()); + // Only update the repository URL if the package is mirrored if ($package->getMirrorRegistry()) { $package->setRepositoryUrl($primaryVersion->getSourceUrl()); } - $this->messenger->dispatch(new UpdatePackageLinks($package->getId(), $primaryVersion->getNormalizedVersion()), [ + $this->messenger->dispatch(new UpdatePackageLinks($package->getId(), $primaryVersion->getNormalizedName()), [ new DispatchAfterCurrentBusStamp(), new TransportNamesStamp('async'), ]); } // Remove outdated versions - foreach ($existingVersionMetadata as $versionMetadata) { - $version = $this->entityManager->getReference(Version::class, $versionMetadata['id']); + foreach ($existingVersionMetadata as $versionId) { + $version = $this->entityManager->getReference(Version::class, $versionId); $this->entityManager->remove($version); } $package->setUpdatedAt(new \DateTimeImmutable()); + + $this->entityManager->persist($package); } - private function updateVersion(Package $package, Version $version, CompletePackageInterface $data, ?VcsDriverInterface $driver = null): void + private function updateVersion(Version $version, CompletePackageInterface $data, ?VcsDriverInterface $driver = null): void { - $em = $this->entityManager; - - $description = $this->sanitize($data->getDescription()); - - $version->setName($package->getName()); - $version->setVersion($data->getPrettyVersion()); - $version->setNormalizedVersion($data->getVersion()); - $version->setDescription($description); - $version->setDevelopment($data->isDev()); - $version->setPhpExt($data->getPhpExt()); - $version->setDefaultBranch($data->isDefaultBranch()); - $version->setTargetDir($data->getTargetDir()); - $version->setAutoload($data->getAutoload()); - $version->setExtra($data->getExtra()); - $version->setBinaries($data->getBinaries()); - $version->setIncludePaths($data->getIncludePaths()); - $version->setSupport($data->getSupport()); - $version->setFunding($data->getFunding()); - $version->setHomepage($data->getHomepage()); - $version->setLicense($data->getLicense() ?: []); - $version->setType($this->sanitize($data->getType())); - - $version->setPackage($package); + $metadata = $this->createMetadata($version, $data, $driver); + + if ($this->hasMetadataChanged($version->getCurrentMetadata(), $metadata)) { + $version->setCurrentMetadata($metadata); + + $this->entityManager->persist($metadata); + + $version->setDefaultBranch($data->isDefaultBranch()); + } + $version->setUpdatedAt(new \DateTimeImmutable()); - $version->setReleasedAt(\DateTimeImmutable::createFromInterface($data->getReleaseDate())); - $version->setAuthors([]); + $this->entityManager->persist($version); + } + + private function createMetadata(Version $version, CompletePackageInterface $data, ?VcsDriverInterface $driver): Metadata + { + $metadata = new Metadata($version); + $metadata->setPackageName($data->getName()); + $metadata->setVersionName($data->getPrettyVersion()); + $metadata->setNormalizedVersionName($data->getVersion()); + $metadata->setDescription($this->sanitize($data->getDescription())); + $metadata->setPhpExt($data->getPhpExt()); + $metadata->setTargetDir($data->getTargetDir()); + $metadata->setAutoload($data->getAutoload()); + $metadata->setExtra($data->getExtra()); + $metadata->setBinaries($data->getBinaries()); + $metadata->setIncludePaths($data->getIncludePaths()); + $metadata->setSupport($data->getSupport()); + $metadata->setFunding($data->getFunding()); + $metadata->setHomepage($data->getHomepage()); + $metadata->setLicense($data->getLicense() ?: []); + $metadata->setType($this->sanitize($data->getType())); + $metadata->setReleasedAt(\DateTimeImmutable::createFromInterface($data->getReleaseDate())); + if ($data->getAuthors()) { $authors = []; foreach ($data->getAuthors() as $authorData) { $author = []; - foreach (['email', 'name', 'homepage', 'role'] as $field) { if (isset($authorData[$field])) { $author[$field] = trim($authorData[$field]); @@ -271,53 +299,39 @@ private function updateVersion(Package $package, Version $version, CompletePacka } } - // skip authors with no information + // Skip authors with no information if (!isset($authorData['email']) && !isset($authorData['name'])) { continue; } $authors[] = $author; } - $version->setAuthors($authors); + + $metadata->setAuthors($authors); } if ($data->getSourceType()) { - $source['type'] = $data->getSourceType(); - // force public URLs even if the package somehow got downgraded to a GitDriver - $source['url'] = static::optimizeRepositoryUrl($data->getSourceUrl()); - $source['reference'] = $data->getSourceReference(); - $version->setSource($source); - } else { - $version->setSource(null); + $metadata->setSource([ + 'type' => $data->getSourceType(), + 'url' => static::optimizeRepositoryUrl($data->getSourceUrl()), + 'reference' => $data->getSourceReference(), + ]); } if ($data->getDistType()) { - $dist['type'] = $data->getDistType(); - $dist['url'] = $data->getDistUrl(); - $dist['reference'] = $data->getDistReference(); - $dist['shasum'] = $data->getDistSha1Checksum(); - $version->setDist($dist); - } else { - $version->setDist(null); - } - - if ($data->isDefaultBranch()) { - $package->setRepositoryUrl($data->getSourceUrl()); - $package->setDescription($description); - $package->setType($this->sanitize($data->getType())); - if ($data->isAbandoned() && !$package->isAbandoned()) { - // $io->write('Marking package abandoned as per composer metadata from '.$version->getVersion()); - $package->setAbandoned(true); - if ($data->getReplacementPackage()) { - $package->setReplacementPackage($data->getReplacementPackage()); - } - } + $metadata->setDist([ + 'type' => $data->getDistType(), + 'url' => $data->getDistUrl(), + 'reference' => $data->getDistReference(), + 'shasum' => $data->getDistSha1Checksum(), + ]); } - // handle links - foreach (self::SUPPORTED_LINK_TYPES as $linkType => $opts) { + // Handle links + foreach (self::SUPPORTED_LINK_TYPES as $linkType => $linkOptions) { $links = []; - foreach ($data->{$opts['method']}() as $link) { + /** @var ComposerPackageLink $link */ + foreach ($data->{$linkOptions['method']}() as $link) { $constraint = $link->getPrettyConstraint(); if (str_contains($constraint, ',') && str_contains($constraint, '@')) { $constraint = Preg::replaceCallback('{([><]=?\s*[^@]+?)@([a-z]+)}i', static function ($matches) { @@ -332,108 +346,83 @@ private function updateVersion(Package $package, Version $version, CompletePacka $links[$link->getTarget()] = $constraint; } - /** @var AbstractVersionLink $link */ - foreach ($version->{'get' . $linkType}() as $link) { - $linkPackageName = $link->getLinkedPackageName(); - - // Clear links that have changed/disappeared (for updates) - if (!isset($links[$linkPackageName]) || $links[$linkPackageName] !== $link->getLinkedVersionConstraint()) { - $version->{'get' . $linkType}()->removeElement($link); - $em->remove($link); - } else { - // Clear those that are already set - unset($links[$linkPackageName]); - } - } - foreach ($links as $linkPackageName => $linkPackageConstraint) { - /** @var AbstractVersionLink $link */ - $link = new $opts['entity'](); + /** @var AbstractMetadataLink $link */ + $link = new $linkOptions['entity']($metadata); $link->setLinkedPackageName($linkPackageName); $link->setLinkedVersionConstraint($linkPackageConstraint); - $version->{'add' . $linkType . 'Link'}($link); - $link->setVersion($version); - $em->persist($link); + + $metadata->{'get' . ucfirst($linkType)}()->add($link); } } - // handle suggests + // Handle suggests if ($suggests = $data->getSuggests()) { - foreach ($version->getSuggest() as $link) { - $linkPackageName = $link->getLinkedPackageName(); - // clear links that have changed/disappeared (for updates) - if (!isset($suggests[$linkPackageName]) || $suggests[$linkPackageName] !== $link->getLinkedVersionConstraint()) { - $version->getSuggest()->removeElement($link); - $em->remove($link); - } else { - // clear those that are already set - unset($suggests[$linkPackageName]); - } - } - foreach ($suggests as $linkPackageName => $linkPackageConstraint) { - $link = new VersionSuggestLink(); + $link = new MetadataSuggestLink($metadata); $link->setLinkedPackageName($linkPackageName); $link->setLinkedVersionConstraint($linkPackageConstraint); - $version->addSuggestLink($link); - $link->setVersion($version); - $em->persist($link); - } - } elseif (count($version->getSuggest())) { - // clear existing suggests if present - foreach ($version->getSuggest() as $link) { - $em->remove($link); + + $metadata->getSuggest()->add($link); } - $version->getSuggest()->clear(); } // Handle keywords if ($keywordsData = $data->getKeywords()) { - foreach ($version->getKeywords() as $keyword) { - $keywordName = $keyword->getName(); - // Clear keywords that have disappeared (for updates) - if (!in_array($keywordName, $keywordsData, true)) { - $version->getKeywords()->removeElement($keyword); - $em->remove($keyword); - } else { - // Clear those that are already set - $index = array_search($keywordName, $keywordsData, true); - unset($keywordsData[$index]); - } - } - foreach ($keywordsData as $keywordName) { $keyword = $this->keywordRepository->getByName($keywordName); - $version->addKeyword($keyword); + + $metadata->getKeywords()->add($keyword); } - } elseif (count($version->getKeywords())) { - // Clear existing keywords if present - $version->getKeywords()->clear(); } if ($driver) { - $this->updateReadme($version, $driver); - } else { - $version->setReadme(null); + $metadata->setReadme($this->getReadmeContents($metadata, $driver)); } + + return $metadata; } - private function sanitize(?string $str): ?string + private function sanitize(?string $string): ?string { - if (null === $str) { + if (null === $string || '' === $string) { return null; } - // remove escape chars - $str = Preg::replace("{\x1B(?:\[.)?}u", '', $str); + // Remove escape chars + $string = Preg::replace("{\x1B(?:\[.)?}u", '', $string); + $string = Preg::replace("{[\x01-\x1A]}u", '', $string); - return Preg::replace("{[\x01-\x1A]}u", '', $str); + return $string; } - private function updateReadme(Version $version, VcsDriverInterface $driver): void + private function hasMetadataChanged(?Metadata $currentMetadata, Metadata $metadata): bool + { + $currentData = $currentMetadata?->toComposerArray(); + $data = $metadata->toComposerArray(); + + if (null === $currentData) { + return true; + } + + // Fields that shouldn't trigger a new revision + $excludeFields = ['abandoned', 'default-branch']; + + foreach ($excludeFields as $field) { + unset($currentData[$field], $data[$field]); + } + + // Normalize both arrays for comparison + ksort($currentData); + ksort($data); + + return $currentData !== $data; + } + + private function getReadmeContents(Metadata $metadata, VcsDriverInterface $driver): ?string { try { - $composerInfo = $driver->getComposerInformation($version->getSource()['reference']); + $composerInfo = $driver->getComposerInformation($metadata->getSourceReference()); $readmeFile = is_string($composerInfo['readme'] ?? null) ? $composerInfo['readme'] : 'README.md'; $ext = substr($readmeFile, (int) strrpos($readmeFile, '.')); @@ -443,23 +432,24 @@ private function updateReadme(Version $version, VcsDriverInterface $driver): voi switch ($ext) { case '.txt': - $source = $driver->getFileContent($readmeFile, $version->getSource()['reference']); + $source = $driver->getFileContent($readmeFile, $metadata->getSourceReference()); if (!empty($source)) { - $version->setReadme('
' . htmlspecialchars($source) . '
'); + return '
' . htmlspecialchars($source) . '
'; } break; + case '.markdown': case '.md': - $source = $driver->getFileContent($readmeFile, $version->getSource()['reference']); + $source = $driver->getFileContent($readmeFile, $metadata->getSourceReference()); if (!empty($source)) { $parser = new GithubMarkdown(); $readme = $parser->parse($source); if (!empty($readme)) { - $version->setReadme($this->prepareReadme($readme)); + return $this->prepareReadmeContents($readme); } } @@ -468,9 +458,11 @@ private function updateReadme(Version $version, VcsDriverInterface $driver): voi } catch (\Exception $exception) { throw $exception; // todo handle politely } + + return null; } - private function prepareReadme(string $readme): string + private function prepareReadmeContents(string $readme): string { return $readme; } diff --git a/src/Package/PackageProviderManager.php b/src/Package/PackageProviderManager.php index 6b41074e..262edcbd 100644 --- a/src/Package/PackageProviderManager.php +++ b/src/Package/PackageProviderManager.php @@ -29,7 +29,7 @@ public function dump(Package $package): void usort($versions, [Package::class, 'sortVersions']); foreach ($versions as $version) { - $versionData = $version->toComposerArray(); + $versionData = $version->getCurrentMetadata()->toComposerArray(); if (!$version->isDevelopment()) { $releasePackages[] = $versionData; diff --git a/src/Routing/PackageValueResolver.php b/src/Routing/PackageValueResolver.php index b3c567b3..2afaf18d 100644 --- a/src/Routing/PackageValueResolver.php +++ b/src/Routing/PackageValueResolver.php @@ -55,7 +55,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable $versionName = $request->attributes->get('version'); $versionName = $versionParser->normalize($versionName); - if (null === $version = $this->versionRepository->findOneByNormalizedVersion($package, $versionName)) { + if (null === $version = $this->versionRepository->findOneByNormalizedVersionName($package, $versionName)) { throw new NotFoundHttpException('The package version does not exist.'); } diff --git a/templates/dashboard/packages/package_info.html.twig b/templates/dashboard/packages/package_info.html.twig index 34949cdc..9df245ab 100644 --- a/templates/dashboard/packages/package_info.html.twig +++ b/templates/dashboard/packages/package_info.html.twig @@ -16,23 +16,23 @@
composer require {{ package.name }}
-

{{ version.description }}

+

{{ metadata.description }}

- {% if version.authors|length > 0 %} + {% if metadata.authors|length > 0 %}
- {% for author in version.authors %} + {% for author in metadata.authors %} {% set authorText %}{{ author.name }}{% endset %} {% if author.homepage is defined %}{{ authorText }}{% else %}{{ authorText }}{% endif %} {% endfor %}
{% endif %} - {% if version.keywords|length > 0 %} + {% if metadata.keywords|length > 0 %}
- {% for keyword in version.keywords %} + {% for keyword in metadata.keywords %} {{ keyword.name }} {% endfor %}
@@ -52,8 +52,8 @@ {% if package.browsableRepositoryUrl or package.mirrorRegistry %}
{% endif %} - {% if version.homepage %} - + {% if metadata.homepage %} + {% endif %} {% set packageStatisticsUrl = path('dashboard_packages_statistics', {package: package.name}) %}
{{ 'Installations'|trans }} {{ package.installations.total }}
@@ -94,39 +94,39 @@
-
{{ version.version }}
- {% if version.releasedAt %} -
{{ version.releasedAt.format('Y-m-d H:i') }} UTC
+
{{ version.name }}
+ {% if metadata.releasedAt %} +
{{ metadata.releasedAt.format('Y-m-d H:i') }} UTC
{% endif %}
- {{ _self.linkBlock('Requires', version.require) }} - {{ _self.linkBlock('Requires (dev)', version.devRequire) }} - {{ _self.provideBlock(version.provide) }} - {{ _self.linkBlock('Suggests', version.suggest) }} - {{ _self.linkBlock('Conflicts', version.conflict) }} - {{ _self.linkBlock('Replaces', version.replace) }} + {{ _self.linkBlock('Requires', metadata.require) }} + {{ _self.linkBlock('Requires (dev)', metadata.devRequire) }} + {{ _self.provideBlock(metadata.provide) }} + {{ _self.linkBlock('Suggests', metadata.suggest) }} + {{ _self.linkBlock('Conflicts', metadata.conflict) }} + {{ _self.linkBlock('Replaces', metadata.replace) }}
- {% if version.hasSource() %} + {% if metadata.hasSource() %}
{{ 'Source'|trans }} - {{ version.sourceType }} + {{ metadata.sourceType }}
- {{ version.sourceUrl }} + {{ metadata.sourceUrl }}
- {{ 'Reference'|trans }}: {{ version.sourceReference }} + {{ 'Reference'|trans }}: {{ metadata.sourceReference }}
- {% set browseUrl = version.browsableRepositoryUrl %} + {% set browseUrl = metadata.browsableRepositoryUrl %} {% if browseUrl %} @@ -140,18 +140,18 @@ {% endif %}
- {% if version.readme %} + {% if metadata.readme %}
- {{ version.readme|raw }} + {{ metadata.readme|raw }}
{% endif %} {% endblock %} diff --git a/templates/dashboard/packages/package_versions.html.twig b/templates/dashboard/packages/package_versions.html.twig index 0a8a49b1..c36b06f3 100644 --- a/templates/dashboard/packages/package_versions.html.twig +++ b/templates/dashboard/packages/package_versions.html.twig @@ -58,11 +58,12 @@ {% endmacro %} {% macro versionListItem(version) %} - {% set packageVersionInfoUrl = path('dashboard_packages_version_info', {package: version.package.name, version: version.version}) %} + {% set metadata = version.currentMetadata %} + {% set packageVersionInfoUrl = path('dashboard_packages_version_info', {package: version.packageName, version: version.name}) %}
{{ version.versionTitle }} - {{ version.releasedAt.format('Y-m-d') }} + {{ metadata.releasedAt.format('Y-m-d') }}
{% endmacro %} diff --git a/tests/Helper/MockEntityFactoryTrait.php b/tests/Helper/MockEntityFactoryTrait.php index 944c38d3..3e546466 100644 --- a/tests/Helper/MockEntityFactoryTrait.php +++ b/tests/Helper/MockEntityFactoryTrait.php @@ -37,12 +37,9 @@ protected function createMockUser(bool $mfaEnabled = false): User protected function createMockVersion(Package $package, string $versionName = '1.0.0'): Version { - $version = new Version(); - - $version->setName($package->getName()); - $version->setVersion($versionName); - $version->setNormalizedVersion((new VersionParser())->normalize($versionName)); - $version->setPackage($package); + $version = new Version($package); + $version->setName($versionName); + $version->setNormalizedName((new VersionParser())->normalize($versionName)); $package->getVersions()->add($version); diff --git a/tests/UnitTests/Message/TrackInstallationsHandlerTest.php b/tests/UnitTests/Message/TrackInstallationsHandlerTest.php index 31646199..9ad8d578 100644 --- a/tests/UnitTests/Message/TrackInstallationsHandlerTest.php +++ b/tests/UnitTests/Message/TrackInstallationsHandlerTest.php @@ -36,9 +36,8 @@ public function testInvoke(): void $package = new Package(); $package->setName('foo/bar'); - $version = new Version(); - $version->setPackage($package); - $version->setNormalizedVersion('1.0.0'); + $version = new Version($package); + $version->setNormalizedName('1.0.0'); $this->packageRepository->expects($this->once()) ->method('findOneByName') @@ -46,7 +45,7 @@ public function testInvoke(): void ->willReturn($package); $this->versionRepository->expects($this->once()) - ->method('findOneByNormalizedVersion') + ->method('findOneByNormalizedVersionName') ->with($package, '1.0.0') ->willReturn($version);