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.
Requires
- php: ^8.2
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
This package is auto-updated.
Last update: 2026-07-20 17:17:44 UTC
README
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
gumslone/laravel-purl2cpe— resolve a purl to its NVD CPE (vendor/product) from the curated scanoss/purl2cpe database.
Testing
composer install
composer test
The suite runs the bundled official conformance data under tests/data.
Credits
- The purl specification and conformance suite: package-url/purl-spec (MIT).
Support
If this package saves you time, consider supporting its development:
License
MIT. See LICENSE. Bundled test fixtures are from package-url/purl-spec, also MIT.