gumslone/laravel-package-url

Package URL (purl) for Laravel — parse, build, normalize and validate purls per the spec, plus registry/repository/download URL resolution, ecosystem mapping, and CPE-friendly helpers.

Maintainers

Package info

github.com/gumslone/laravel-package-url

pkg:composer/gumslone/laravel-package-url

Transparency log

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-07-20 17:16 UTC

This package is auto-updated.

Last update: 2026-07-20 17:17:44 UTC


README

tests License: MIT Donate

Package URL (purl) for Laravel — parse, build, normalize and validate purls, plus registry / repository / download URL resolution, vulnerability-database ecosystem mapping, and reverse-parsing from VCS URLs.

The core is a faithful, dependency-light implementation of the purl spec that passes the entire official package-url/purl-spec conformance suite (base spec + all 40+ type definitions) — the same suite packageurl-php and packageurl-python test against — and then goes further with the helpers you actually need in an app.

Installation

composer require gumslone/laravel-package-url

The service provider and Purl facade are auto-discovered.

Core: parse, build, normalize, validate

use Gumslone\PackageUrl\PackageUrl;

$purl = PackageUrl::fromString('pkg:composer/laravel/framework@11.55.0');

$purl->type;       // "composer"
$purl->namespace;  // "laravel"
$purl->name;       // "framework"
$purl->version;    // "11.55.0"
$purl->qualifiers; // []
$purl->subpath;    // null

(string) $purl;    // "pkg:composer/laravel/framework@11.55.0"
$purl->toArray();  // ['type' => 'composer', 'namespace' => 'laravel', ...]

// Build from components (with normalization + validation)
PackageUrl::fromArray([
    'type' => 'PYPI',
    'name' => 'Django_package',
    'version' => '1.11.1',
])->toString(); // "pkg:pypi/django-package@1.11.1"  (type + name normalized)

// Immutable withers (each returns a new normalized PackageUrl)
$purl->withVersion('12.0.0');
$purl->withNamespace('symfony')->withName('console');
$purl->withType('composer');
$purl->withSubpath('src/Console');
$purl->withQualifier('repository_url', 'https://example.org'); // '' removes it
$purl->withoutQualifier('repository_url');
$purl->withoutVersion();

// Read
$purl->qualifier('repository_url');  // ?string
$purl->equals('pkg:composer/laravel/framework@11.55.0'); // true

// Type checks
$purl->isType('composer');   // true (case-insensitive)
$purl->isGeneric();          // false

Normalization follows the spec: the type is lowercased, qualifier keys are lowercased/sorted with empty values dropped, subpaths are cleaned, and type-specific rules apply (e.g. pypi lowercases the name and turns _ into -; github/bitbucket/golang lowercase namespace and name). Invalid purls throw PackageUrlException — including per-type rules such as swift requiring a namespace, vcpkg/otp forbidding one, and chrome-extension id/version formats.

Extended: the Purl facade

use Purl;

Purl::isValid('pkg:npm/left-pad@1.3.0');        // true
Purl::parse('pkg:npm/left-pad@1.3.0');          // PackageUrl
Purl::parseMany([...]);                          // Collection<PackageUrl>, invalid skipped

// Find every purl embedded in a block of text (SBOM, manifest, log, changelog)
Purl::extractAll($text);                         // Collection<PackageUrl>, de-duplicated

Checks

Non-throwing booleans to guard a conversion before you attempt it:

// Type
Purl::isType('pkg:cargo/serde@1.0', 'cargo'); // true
Purl::isGeneric('pkg:generic/tool@1.0');      // true

// Can this purl produce a URL?
Purl::hasRepositoryUrl('pkg:composer/laravel/framework@11.0'); // true
Purl::hasDownloadUrl('pkg:npm/left-pad@1.3.0');                // true
Purl::hasDownloadUrl('pkg:npm/left-pad');                      // false (no version)
Purl::hasRegistryApiUrl('pkg:cargo/serde@1.0');               // true
Purl::isConvertibleToUrl('pkg:npm/left-pad');                 // true (any of the above)

// Can this URL be turned into a purl?
Purl::isConvertibleDownloadUrl('https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz'); // true
Purl::isConvertibleUrl('git@github.com:laravel/framework.git'); // true (also accepts repo/SSH URLs)

Registry, repository & download URLs

Purl::repositoryUrl('pkg:composer/laravel/framework@11.55.0');
// https://packagist.org/packages/laravel/framework

Purl::repositoryUrl('pkg:pypi/django@5.0');
// https://pypi.org/project/django/5.0/

Purl::registryApiUrl('pkg:npm/left-pad@1.3.0');
// https://registry.npmjs.org/left-pad

Purl::downloadUrl('pkg:npm/@babel/core@7.0.0');
// https://registry.npmjs.org/@babel/core/-/core-7.0.0.tgz

Purl::downloadUrl('pkg:maven/org.apache.commons/commons-lang3@3.14.0');
// https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar

Coverage matches (and extends) packageurl-python's purl2url:

  • repositoryUrl — cargo, bitbucket, github, gitlab, gem, cran, npm, pypi, composer, nuget, hackage, golang, cocoapods, maven (+ hex, pub, conda).
  • downloadUrl — cargo, gem, npm, maven, hackage, nuget, github, gitlab, bitbucket, hex, golang, pub, swift, luarocks, conda, alpm, deb, apk.

A download_url qualifier always takes precedence. Distro artifact URLs (deb/apk/alpm/conda) read the relevant qualifiers (arch, repo, channel, …).

Vulnerability-database ecosystem mapping

Purl::osvEcosystem('pkg:pypi/django@5.0');       // "PyPI"       (OSV.dev)
Purl::osvEcosystem('pkg:composer/laravel/framework@11.0'); // "Packagist"
Purl::githubEcosystem('pkg:npm/left-pad@1.3.0'); // "NPM"        (GitHub Advisories)

Reverse-parse from a URL

From a VCS repository URL:

Purl::fromRepositoryUrl('https://github.com/laravel/framework');
// pkg:github/laravel/framework

Purl::fromRepositoryUrl('git@github.com:laravel/framework.git');
// pkg:github/laravel/framework

Purl::fromRepositoryUrl('https://github.com/laravel/framework/tree/v11.0.0');
// pkg:github/laravel/framework@v11.0.0

From a download / registry URL — the inverse of downloadUrl():

Purl::fromDownloadUrl('https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz');
// pkg:npm/left-pad@1.3.0

Purl::fromDownloadUrl('https://files.pythonhosted.org/packages/ab/cd/Django-5.0-py3-none-any.whl');
// pkg:pypi/django@5.0

Purl::fromDownloadUrl('https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar');
// pkg:maven/org.apache.commons/commons-lang3@3.14.0

Purl::fromDownloadUrl('https://github.com/laravel/framework/archive/refs/tags/v11.0.0.tar.gz');
// pkg:github/laravel/framework@v11.0.0

// Try both, whichever matches
Purl::fromUrl($anyPackageOrRepoUrl);

Recognition covers (and extends) packageurl-python's url2purl: npm (incl. yarnpkg mirrors), PyPI (CDN files, /packages/source, project/JSON API), RubyGems, crates.io, NuGet, Maven, Go (proxy + pkg.go.dev), Hex, Composer/Packagist, pub.dev, CocoaPods, Hackage, LuaRocks, CRAN, SourceForge and Google Code archives (→ pkg:generic), and the full GitHub / GitLab / Bitbucket surface — web, REST API (api.github.com/repos), codeload.github.com, archive/release/download, and raw/blob/tree/ commit refs. Returns null for anything unrecognised.

Pairs well with

Testing

composer install
composer test

The suite runs the bundled official conformance data under tests/data.

Credits

Support

If this package saves you time, consider supporting its development:

Buy Me A Coffee

Donate

License

MIT. See LICENSE. Bundled test fixtures are from package-url/purl-spec, also MIT.