xenos / musicbrainz
A PHP library for accessing the MusicBrainz API
Requires
- php: ^8.3
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
- psr/log: ^3.0
- xenos/asklucy: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.90
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.5
- symfony/dom-crawler: ^7.4
- symfony/http-client: ^7.4
Suggests
- guzzlehttp/guzzle: A PSR-18 HTTP client; any other PSR-18 client works as well.
- monolog/monolog: Allows logging of the application flow.
- nyholm/psr7: A PSR-7 implementation with a PSR-17 request factory, which the client needs besides the PSR-18 client.
- symfony/http-client: A PSR-18 HTTP client; any other PSR-18 client works as well.
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-11-02 16:00:59 UTC
README
A typed PHP API client for the MusicBrainz JSON web service: search, lookup and browse requests for the core entities, including genres, tags, ratings and relations. The library is read-only. Requirements: PHP 8.3 or newer, any PSR-18 HTTP client.
Contents
Installation
Install with Git
git clone https://github.com/XenosEleatikos/MusicBrainz.git
Install with Composer
This project is available at Packagist: https://packagist.org/packages/xenos/musicbrainz.
You can use it in your project with the following command:
composer require xenos/musicbrainz
Set up
HTTP client
The library requires PHP 8.3+ and works with any PSR-18 HTTP client together with a PSR-17 request factory. Example with the Symfony HTTP client and Nyholm's PSR-7 implementation:
<?php use MusicBrainz\MusicBrainz; use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Component\HttpClient\Psr18Client; $musicBrainz = new MusicBrainz(new Psr18Client(), new Psr17Factory());
composer require symfony/http-client nyholm/psr7
Guzzle (guzzlehttp/guzzle with guzzlehttp/psr7) or any other PSR-18 client works the same way.
Logger
Optionally, you may set any PSR-3 logger as third constructor argument:
<?php $musicBrainz = new MusicBrainz(new Psr18Client(), new Psr17Factory(), $logger);
The logger receives warnings about data the library does not know yet (a new type, an unknown relationship type) and
unexpected API behavior. Note that the logger is process-wide: value objects are built from plain arrays and log through
MusicBrainz::log(), so the logger of the most recently created MusicBrainz instance that was given a logger is used.
User agent and rate limit
MusicBrainz requires a meaningful user agent and allows one request per second. The client throttles requests automatically and retries on HTTP 503. Please set the name and contact of your application:
<?php $musicBrainz->config()->setUserAgent('MyApp/1.0 ( https://example.com/contact )');
Authentication
Requests for user-tags, user-ratings, user-genres and private collections require an
OAuth2 access token. (HTTP digest authentication is deprecated by
MusicBrainz and not supported.)
<?php $musicBrainz->config()->setAccessToken('my-oauth2-access-token');
The library only sends the token. Obtaining and renewing it is up to your application:
- Register your application at https://musicbrainz.org/account/applications (client ID, client secret, redirect URI).
- Request the scopes you need.
tag,ratingandcollectiongive access to the private tags, ratings and collections of the user (reading and modifying);profileandemailreturn profile data. There are no read-only variants. - Run the OAuth2 authorization code flow with any OAuth2 client. The endpoints are
https://musicbrainz.org/oauth2/authorizeandhttps://musicbrainz.org/oauth2/token. Passaccess_type=offlineto get a refresh token. - Renew the token. Access tokens are short-lived (about one hour), refresh tokens stay valid until the user revokes them.
An expired token makes a request fail with an
ApiException(HTTP 401): refresh the token and callsetAccessToken()again.
A sketch with league/oauth2-client (not a dependency of this library):
<?php use League\OAuth2\Client\Provider\GenericProvider; $provider = new GenericProvider([ 'clientId' => 'my-client-id', 'clientSecret' => 'my-client-secret', 'redirectUri' => 'https://example.com/callback', 'urlAuthorize' => 'https://musicbrainz.org/oauth2/authorize', 'urlAccessToken' => 'https://musicbrainz.org/oauth2/token', 'urlResourceOwnerDetails' => 'https://musicbrainz.org/oauth2/userinfo', 'scopes' => ['collection'], 'scopeSeparator' => ' ', ]); // 1. Send the user to $provider->getAuthorizationUrl(['access_type' => 'offline']) // 2. On the redirect back, exchange the code: $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]); // Store $token->getRefreshToken(), then: $musicBrainz->config()->setAccessToken($token->getToken()); // Later, when the access token has expired: $token = $provider->getAccessToken('refresh_token', ['refresh_token' => $storedRefreshToken]); $musicBrainz->config()->setAccessToken($token->getToken());
The library does not run this flow itself, and it does not refresh tokens automatically.
Custom API Path
By default, all requests are executed against the MusicBrainz API at https://musicbrainz.org/ws/2. You can set another base path (e. g. for your own MusicBrainz installation) as follows:
<?php /** @var \MusicBrainz\MusicBrainz $musicBrainz*/ $musicBrainz->config() ->setApiUrl('https://example.com/ws/2');
Usage
Search Requests
The search API provides methods to search for the MusicBrainz
core entities. For each of these entities there is a search
method taking a specific filter object and a page filter for pagination. The return value is a Page containing
basic information about the matching entities. For more information you have to perform
a lookup request with the MusicBrainz Identifier (MBID) of the entity.
Several criteria of a filter are combined with AND: each one narrows the result (use $filter->matchAny() for OR).
The following example searches for the artist named "Tom Waits" and limits the result list to the top match. The result
is a Page of SearchResult objects, each with the matching entity and its score (0 to 100).
<?php use MusicBrainz\Filter\PageFilter; use MusicBrainz\Filter\Search\ArtistFilter; use MusicBrainz\Filter\Value\Name; $artistFilter = new ArtistFilter; $artistFilter->addArtistName(new Name('Tom Waits')); $pageFilter = new PageFilter(0, 1); $artistList = $musicBrainz->api()->search()->artist($artistFilter, $pageFilter); echo $artistList->totalCount, ' matches', PHP_EOL; // all matches, not only this page foreach ($artistList as $result) { // a Page is iterable, countable and read-only echo $result->score, ' ', $result->entity->name, PHP_EOL; }
Lookup Requests
If you have the MusicBrainz Identifier (MBID) of an entity (through a search request), you can get more information about it by performing a lookup request. For each of the core entities there is a specific lookup method. As first argument you have to pass the MBID. The second argument is a list of additional fields the response object should contain.
The following example looks up for the artist with MBID c3aeb863-7b26-4388-94e8-5a240f2be21b (who is Tom Waits), and requests to append information about his recordings and works to the response.
<?php use MusicBrainz\Supplement\Lookup\ArtistFields; use MusicBrainz\Value\MBID; $fields = (new ArtistFields) ->includeRecordings() ->includeWorks(); $artist = $musicBrainz->api()->lookup()->artist( new MBID('c3aeb863-7b26-4388-94e8-5a240f2be21b'), $fields ); echo $artist->name, ' (', $artist->lifeSpan?->begin, ')', PHP_EOL; foreach ($artist->recordings as $recording) { echo $recording->title, PHP_EOL; }
Browse Requests
Browse requests are a direct lookup of all the entities directly linked to another entity with a given MusicBrainz Identifier (MBID).
The following example looks up for all events directly linked to the artist with the MBID c3aeb863-7b26-4388-94e8-5a240f2be21b (who is Tom Waits). In analogy to normal lookup requests additional fields may be specified for the response, and a page filter may be added for pagination.
<?php use MusicBrainz\Filter\Browse\Relation\Entity\EventRelation; use MusicBrainz\Filter\PageFilter; use MusicBrainz\Supplement\Browse\EventFields; use MusicBrainz\Value\MBID; $eventRelation = EventRelation::artist(new MBID('c3aeb863-7b26-4388-94e8-5a240f2be21b')); $eventFields = (new EventFields) ->includeTags(); $pageFilter = new PageFilter(0, 5); $eventList = $musicBrainz->api()->browse()->event($eventRelation, $eventFields, $pageFilter); foreach ($eventList as $event) { echo $event->name, ' ', $event->lifeSpan?->begin, PHP_EOL; }
Value objects
All results are immutable value objects (final readonly classes) with public, typed properties; there are no getters
and setters. Data the response does not contain is null (a nested object such as $artist->area, a date, a type) or
empty ('' for names, [] for lists), never a dummy object. Dates are Date objects that keep the precision the
database has ("1969", "1969-09", "1969-09-26"). The classes are built from the decoded JSON with X::fromArray();
that factory is not meant to be called by applications.
The rule for missing data: a name, title or disambiguation the web service always delivers is a string ('' if empty);
anything it may leave out is nullable (?string, ?Date, ?Area, ...); lists are never null. The entities implement the
marker interface MusicBrainz\Value\Entity.
Types and unknown values
Types, genders, statuses etc. are value objects with the name as MusicBrainz delivers it, the MBID of the type, and a
kind() returning an enum for the values the library knows:
<?php use MusicBrainz\Value\ArtistTypeKind; $type = $artist->type; // null if the response has no type $type->name; // "Person" (string) $type->id; // "b6e035f4-3ce9-331c-97df-83397230b0df" ArtistTypeKind::Person === $type->kind();
MusicBrainz maintains these lists in its database and extends them from time to time. A value the library does not know
yet is kept in name, kind() returns null, and a warning is sent to your logger.
Relations
Entities that were requested with relationship includes (e.g. includeArtistRelations()) have a RelationList in
$entity->relations (null if relations were not requested). Every relation knows its type (typeName, typeId, and type, the class of the
relationship type, which is null for a type the library does not know yet), its direction, its target entity, its period,
its attributes and its credits:
<?php use MusicBrainz\Value\EntityType; foreach ($artist->relations?->to(EntityType::Artist) ?? [] as $relation) { echo $relation->typeName, ': ', $relation->target->name, PHP_EOL; echo $relation->begin, ' - ', $relation->end, ' ', implode(', ', $relation->attributes), PHP_EOL; }
Errors
Everything the library throws on purpose about requests and input implements MusicBrainz\ExceptionInterface. (Only misuse of
read-only objects, such as writing to a Page, throws an SPL LogicException or OutOfRangeException.)
MusicBrainz\ApiException: the web service answered with an HTTP error; the code is the HTTP status (404 for an unknown MBID, 401 for a missing or invalid token). Responses with HTTP 503 are retried automatically, seeConfig::setMaxRetries().MusicBrainz\TransportException: the HTTP client failed (no connection, timeout).MusicBrainz\ResponseException: the answer was not the expected JSON.MusicBrainz\ConfigurationException: no user agent, or a token is needed and none is set.
These extend MusicBrainz\Exception. MusicBrainz\InvalidArgumentException (an SPL InvalidArgumentException) signals invalid
input such as an MBID that is no UUID; it is a bug in the calling code and not an Exception.
Stability
The public API is what this readme describes: the MusicBrainz entry point and its Config, the lookup, browse and search
methods with their filters, fields and page filter, the value objects with their public properties, and the exceptions.
Everything marked @internal (the fromArray() factories, the HTTP client, the helpers, the map of relationship types, the
static methods of the filters that return Lucene field names) may change in a minor release. The class hierarchy of the
relationship types mirrors MusicBrainz and may change in minor releases too: compare relationships by typeId (the
MBID), which is stable. New cases of the enums (types, statuses, formats) may be added in minor releases. Results and browse relations are immutable;
search filters and fields (inc parameters) are mutable builders that you fill with chained calls. Result classes, the API classes, filters and fields classes are final.
Upgrading from 1.x
Version 2 contains breaking changes, the full list is in the changelog. The most important ones:
- PHP 8.3+ and PSR-18. Replace the HTTP adapters by a PSR-18 client and a PSR-17 request factory:
// 1.x: new MusicBrainz(new GuzzleAdapter(...), ...) // 2.x: $musicBrainz = new MusicBrainz(new Psr18Client(), new Psr17Factory(), $logger);
- Authentication. User name and password (digest authentication) are gone. Use an OAuth2 access token:
$musicBrainz->config()->setAccessToken('...'). - User agent. Set a meaningful one:
$musicBrainz->config()->setUserAgent('MyApp/1.0 ( https://example.org )'). - No getters. Results are readonly value objects with public properties:
$artist->nameinstead of$artist->getArtistName(),$release->title,$page->totalCount,$searchResult->entity. All names are uniform (name,title,sortName,disambiguation). Absent nested objects arenull; lists are plain arrays;Pageis iterable and read-only. Relations areRelationobjects in$entity->relations(see Relations). - Enums and types.
EntityType,DirectionandDataQualityare enums (EntityType::ReleaseGroup). Types likeArtistTypeorGenderhave akind()returning an enum (ArtistTypeKind::Person), see Types and unknown values.ReleaseType/ReleaseTypeListare replaced byPrimaryReleaseTypeand a list ofSecondaryReleaseType. - Browse filters.
setEntityTypeRelease()/setEntityTypeReleaseGroup()are nowrelease()/releaseGroup();CollectionRelation::editor()takes the user name. Browse relations are immutable objects created with a static constructor:ReleaseRelation::artist($mbid)instead of(new ReleaseRelation())->artist($mbid).
Development
See CONTRIBUTING.md. The unit tests run offline against real, recorded responses of the web service.
History and acknowledgements
This library stands on the shoulders of others, and I am grateful for their work:
- Jeff Sherlock started it in 2010, as one of the first PHP clients for the MusicBrainz web service.
- Mike Almond took it over in 2013 and maintained and extended it for years. Without that foundation, this project would not exist.
- Chris Dawson, Chris Boden, Chris Roberts, Jbekker, shrddr, Afterster, Daniel Maly, Nic and papposilene contributed fixes and features between 2012 and 2019.
- Roland Dalmulder added relationship types and fixes in 2023.
Until April 2013 the project was licensed under the GPL version 2; Mike Almond changed the license to MIT then, and it has been MIT since.
Since 2017 the library is maintained by Christoph Hochholzer. Version 2.0 is a complete rewrite, but it builds on what the people above made, and the history of this repository keeps their contributions (the license file keeps the copyright notice of Mike Almond, see LICENSE.md).