diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f603f2c..2b074c3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,88 +2,170 @@ name: CI
on:
push:
- branches: [ main, 'legacy/v2.x', 'feature/**' ]
+ branches:
+ - 'main'
+ - 'legacy/*' # Legacy branches: legacy/v2.x
+ - 'feature/*' # Feature branches: feature/new-feature
+ - 'hotfix/*' # Hotfix branches: hotfix/urgent-fix
+ - 'release/*' # Release branches: release/v3.0.0
pull_request:
- branches: [ main, 'legacy/v2.x' ]
- workflow_dispatch:
- inputs:
- php_version:
- description: 'PHP version to test (optional, tests all if empty)'
- required: false
- default: ''
- dependencies:
- description: 'Dependencies type'
- required: false
- default: 'stable'
- type: choice
- options:
- - stable
- - lowest
- - dev
+ branches:
+ - 'main'
+ - 'legacy/*' # PRs to legacy branches
+ workflow_dispatch: # Allows manual triggering via GitHub UI
jobs:
- tests:
+ test:
runs-on: ubuntu-latest
-
+
strategy:
fail-fast: false
matrix:
- php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
- dependencies: ['stable']
include:
- # Test with lowest dependencies
- - php-version: '7.3'
- dependencies: 'lowest'
- - php-version: '8.4'
- dependencies: 'lowest'
- # Test with development dependencies
- - php-version: '8.4'
- dependencies: 'dev'
- # Test with PHP 8.5 nightly (allow failure)
- - php-version: '8.5'
- dependencies: 'stable'
-
- continue-on-error: ${{ matrix.dependencies == 'dev' || matrix.php-version == '8.5' }}
-
- name: PHP ${{ matrix.php-version }} - ${{ matrix.dependencies }}
+ # Core PHP version testing
+ - php: '8.1'
+ allowed-to-fail: false
+ - php: '8.2'
+ allowed-to-fail: false
+ - php: '8.3'
+ allowed-to-fail: false
+ - php: '8.4'
+ allowed-to-fail: false
+
+ # Future-ready: PHP 8.5 (alpha/dev) - when available
+ - php: '8.5'
+ stability: 'dev'
+ allowed-to-fail: true
+
+ # Development stability tests
+ - php: '8.4'
+ stability: 'dev'
+ allowed-to-fail: true
+ - php: '8.5'
+ stability: 'dev'
+ allowed-to-fail: true
+
+ name: "PHP ${{ matrix.php }}${{ matrix.stability && format(' | {0}', matrix.stability) || '' }}"
+
+ continue-on-error: ${{ matrix.allowed-to-fail }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: json, mbstring, tokenizer
+ coverage: xdebug
+ tools: composer:v2
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Configure stability
+ if: matrix.stability
+ run: |
+ composer config minimum-stability ${{ matrix.stability }}
+ composer config prefer-stable true
+
+ - name: Remove composer.lock
+ run: rm -f composer.lock
+
+ - name: Install dependencies
+ run: composer update --prefer-dist --no-interaction --no-progress
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+
+ - name: Run code style check
+ run: composer cs
+
+ - name: Run static analysis
+ run: composer analyse
+
+ - name: Run tests
+ run: composer test
+
+ code-quality:
+ runs-on: ubuntu-latest
+ name: Code Quality Checks
steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php-version }}
- extensions: dom, curl, libxml, mbstring, zip, json
- coverage: none
- tools: composer:v2
-
- - name: Cache Composer packages
- id: composer-cache
- uses: actions/cache@v4
- with:
- path: ~/.composer/cache
- key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}
- restore-keys: |
- ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-
-
- - name: Install dependencies (lowest)
- if: matrix.dependencies == 'lowest'
- run: composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction
-
- - name: Install dependencies (stable)
- if: matrix.dependencies == 'stable'
- run: composer update --prefer-stable --prefer-dist --no-interaction
-
- - name: Install dependencies (dev)
- if: matrix.dependencies == 'dev'
- run: |
- composer config minimum-stability dev
- composer update --prefer-dist --no-interaction
-
- - name: Validate composer.json
- run: composer validate --strict --no-check-lock
-
- - name: Run PHPUnit tests
- run: vendor/bin/phpunit
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ extensions: json, mbstring, tokenizer
+ coverage: none
+ tools: composer:v2
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-interaction --no-progress
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+
+ coverage:
+ runs-on: ubuntu-latest
+ name: Code Coverage
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ extensions: json, mbstring, tokenizer
+ coverage: xdebug
+ tools: composer:v2
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-interaction --no-progress
+
+ - name: Run tests with coverage
+ run: vendor/bin/phpunit --coverage-clover coverage.xml
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v4
+ with:
+ file: ./coverage.xml
+ flags: unittests
+ name: codecov-umbrella
+ fail_ci_if_error: false
diff --git a/.gitignore b/.gitignore
index a151fc7..2f12f36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,13 +3,14 @@
composer.lock
# PHPUnit
-.phpunit/
.phpunit.result.cache
.phpunit.cache/
-# IDE files
-.idea/
-.vscode/
+# PHPStan
+.phpstan/
+
+# PHP CS Fixer
+.php-cs-fixer.cache
# Coverage reports
coverage/
diff --git a/.markdownlint.json b/.markdownlint.json
index 273eaf7..38064c5 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -2,6 +2,5 @@
"MD024": {
"siblings_only": true
},
- "MD032": false,
"MD013": false
}
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
new file mode 100644
index 0000000..3d0ed82
--- /dev/null
+++ b/.php-cs-fixer.php
@@ -0,0 +1,17 @@
+in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ ->exclude('vendor');
+
+$config = new PhpCsFixer\Config();
+$config->setFinder($finder)
+ ->setRules([
+ '@PSR12' => true,
+ '@PSR12:risky' => true,
+ ])
+ ->setRiskyAllowed(true)
+ ->setUnsupportedPhpVersionAllowed(true);
+
+return $config;
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84cad13..d6beb7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,46 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [3.0.0](https://github.com/calliostro/php-discogs-api/releases/tag/v3.0.0) – 2025-09-08
+
+### Added
+
+- Ultra-lightweight 2-class architecture: `ClientFactory` and `DiscogsApiClient`
+- Magic method API calls: `$client->artistGet(['id' => '108713'])`
+- Complete API coverage: 65+ endpoints across all Discogs areas
+- Multiple authentication methods: OAuth, Personal Token, or anonymous
+- Modern PHP 8.1–8.5 support with strict typing
+- 100% test coverage with 43 comprehensive tests
+- PHPStan Level 8 static analysis
+- GitHub Actions CI with multi-version testing and enhanced branch support
+- Codecov integration for code coverage reporting
+
+### Changed
+
+- **BREAKING**: Namespace changed from `Discogs\*` to `Calliostro\Discogs\*`
+- **BREAKING**: API surface changed from Guzzle Services to magic methods
+- **BREAKING**: Minimum PHP version now 8.1+ (was 7.3)
+- Simplified dependencies: removed Guzzle Services, Command, OAuth Subscriber
+- Replace `squizlabs/php_codesniffer` with `friendsofphp/php-cs-fixer` for code style checking
+- Update code style standard from PSR-12 via PHPCS to PSR-12 via PHP-CS-Fixer
+- Add `.php-cs-fixer.php` configuration file with PSR-12 rules
+- Update composer scripts: `cs` and `cs-fix` now use php-cs-fixer instead of phpcs/phpcbf
+- Update README badges for better consistency and proper branch links
+- Enhanced CI workflow with comprehensive PHP version matrix (8.1–8.5)
+- Add codecov.yml configuration for coverage reporting
+
+### Removed
+
+- Guzzle Services dependency and all related complexity
+- ThrottleSubscriber (handle rate limiting in your application)
+- Support for PHP 7.3–8.0
+
## [2.1.3](https://github.com/calliostro/php-discogs-api/releases/tag/v2.1.3) – 2025-09-06
### Changed
- Repository restructuring: Renamed master branch to main
-- Updated CI workflow to use the main branch instead of master
-- Updated CI badge in README.md to reference the main branch
+- Updated CI workflow and badges to use the main branch
- Prepared for legacy branch support in v2.x series
### Infrastructure
@@ -25,8 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- GitHub Actions CI – Migrated from Travis CI for improved build reliability and faster feedback
- PHP 8.5 nightly support – Early compatibility testing with the upcoming PHP version
-- Enhanced project metadata – Improved description, keywords, and author
- information in composer.json
+- Enhanced project metadata – Improved description, keywords, and author information in composer.json
### Changed
@@ -117,7 +149,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved PHP 8.x compatibility and stability
-## [2.0.1](https://github.com/calliostro/php-discogs-api/releases/tag/v2.0.1) – 2021-04-17
+## [2.0.1](https://github.com/calliostro/php-discogs-api/releases/tag/v2.1.1) – 2021-04-17
### Added
@@ -153,10 +185,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This library is based on the excellent work of:
-- [ricbra/php-discogs-api](https://github.com/ricbra/php-discogs-api)
- - Original implementation
-- [AnssiAhola/php-discogs-api](https://github.com/AnssiAhola/php-discogs-api)
- - Enhanced version
+- [ricbra/php-discogs-api](https://github.com/ricbra/php-discogs-api) - Original implementation
+- [AnssiAhola/php-discogs-api](https://github.com/AnssiAhola/php-discogs-api) - Enhanced version
## Legacy Versions
diff --git a/README.md b/README.md
index 67231f4..b3b7786 100644
--- a/README.md
+++ b/README.md
@@ -1,538 +1,248 @@
-# 🎵 Discogs API – PHP Library
+# ⚡ Discogs API Client for PHP 8.1+ – Ultra-Lightweight
-[](https://packagist.org/packages/calliostro/php-discogs-api)
+[](https://packagist.org/packages/calliostro/php-discogs-api)
[](https://packagist.org/packages/calliostro/php-discogs-api)
-[](https://packagist.org/packages/calliostro/php-discogs-api)
-[](https://php.net)
-[](https://docs.guzzlephp.org/)
-[](https://github.com/calliostro/php-discogs-api/actions)
+[](https://packagist.org/packages/calliostro/php-discogs-api)
+[](https://php.net)
+[](https://github.com/calliostro/php-discogs-api/actions/workflows/ci.yml)
+[](https://codecov.io/gh/calliostro/php-discogs-api)
+[](https://phpstan.org/)
+[](https://github.com/FriendsOfPHP/PHP-CS-Fixer)
-> **Note:** v2.x is in maintenance mode. v3.0.0 introduces breaking changes and modern tooling.
-> Legacy support for v2.x continues on the `legacy/v2.x` branch.
+> **🚀 ONLY 2 CLASSES!** The most lightweight Discogs API client for PHP. Zero bloats, maximum performance.
-This library is a PHP 7.3+ / PHP 8.x implementation of the [Discogs API v2.0.](https://www.discogs.com/developers/index.html)
-The Discogs API is a REST-based interface. By using this library you don't have to worry about communicating with the
-API: all the hard work has already been done.
-
-**Tested & Supported PHP Versions:** 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5 (beta)
-
-## 🚀 API Coverage
-
-This library implements all major Discogs API endpoints:
-
-- **Database:** Search, Artists, Releases, Masters, Labels
-- **User Management:** Profile, Collection, Wantlist, Lists
-- **Marketplace:** Orders, Inventory, Listings, Bulk operations
-- **Order Management:** Messages, Status updates, Shipping
-- **Authentication:** Personal tokens, OAuth 1.0a, Consumer keys
-
-## ⚡ Quick Start
-
-```php
- [
- 'User-Agent' => 'MyApp/1.0 +https://mysite.com',
- 'Authorization' => 'Discogs key=your_consumer_key, secret=your_consumer_secret'
- ]
-]);
-
-// Search for music (requires authentication)
-$results = $client->search(['q' => 'Pink Floyd', 'type' => 'artist']);
-
-// Get detailed information
-$artist = $client->getArtist(['id' => $results['results'][0]['id']]);
-echo $artist['name']; // "Pink Floyd"
-```
-
-> **Note:** Most API endpoints require authentication. Get your consumer key/secret from the [Discogs Developer Settings](https://www.discogs.com/settings/developers).
+An **ultra-minimalist** Discogs API client that proves you don't need 20+ classes to build a great API client. Built with modern PHP 8.1+ features, service descriptions, and powered by Guzzle.
## 📦 Installation
-Start by [installing composer](https://getcomposer.org/doc/01-basic-usage.md#installation).
-Next do:
-
```bash
composer require calliostro/php-discogs-api
```
-## ⚙️ Requirements
-
-- **PHP:** 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5 (beta) — tested and officially supported
-- **ext-json:** JSON extension
-- **cURL extension:** for HTTP requests via Guzzle
-
-### Testing
-
-Run tests with:
-
-**For all PHP versions (recommended):**
-
-```bash
-vendor/bin/phpunit
-```
-
-**For PHP 7.3-7.4 (alternative legacy configuration):**
+**Important:** You need to [register your application](https://www.discogs.com/settings/developers) at Discogs to get your credentials. For read-only access to public data, no authentication is required.
-```bash
-vendor/bin/phpunit --configuration phpunit-legacy.xml.dist
-```
+**Symfony Users:** For easier integration, there's also a [Symfony Bundle](https://github.com/calliostro/discogs-bundle) available.
-## 💡 Usage
+## 🚀 Quick Start
-Creating a new instance is as simple as:
+### Basic Usage
```php
['User-Agent' => 'your-app-name/0.1 +https://www.awesomesite.com'],
-]);
-```
-
-### Throttling
-
-Discogs API has rate limits. Use the `ThrottleSubscriber` to prevent errors or getting banned:
-
-```php
-push(Middleware::retry($throttle->decider(), $throttle->delay()));
-
-$client = ClientFactory::factory([
- 'headers' => [
- 'User-Agent' => 'MyApp/1.0 +https://mysite.com',
- 'Authorization' => 'Discogs key=your_key, secret=your_secret'
- ],
- 'handler' => $handler
-]);
-```
-
-#### Authentication
-
-Discogs API allows you to access protected endpoints with different authentication methods. **Most endpoints require some form of authentication.**
-
-**Get your credentials:** Register your application at [Discogs Developer Settings](https://www.discogs.com/settings/developers)
-
-### Discogs Auth
-
-As stated in the Discogs Authentication documentation:
-> To access protected endpoints, you'll need to register for either a consumer key and secret or user token, depending on your situation:
-> - To easily access your own user account information, use a *User token*.
-> - To get access to an endpoint that requires authentication and build third party apps, use a *Consumer Key and Secret*.
-
-#### Consumer Key and Secret (Recommended)
-
-Register your app at [Discogs Developer Settings](https://www.discogs.com/settings/developers) to get consumer credentials:
-
-```php
- [
- 'User-Agent' => 'MyApp/1.0 +https://mysite.com',
- 'Authorization' => 'Discogs key=your_consumer_key, secret=your_consumer_secret',
- ],
-]);
-```
-
-#### Personal Access Token
-
-For accessing your own account data, use a personal access token:
-
-```php
- [
- 'User-Agent' => 'MyApp/1.0 +https://mysite.com',
- 'Authorization' => 'Discogs token=your_personal_token',
- ]
-]);
-```
-
-### OAuth 1.0a
-
-For advanced use cases requiring user-specific access tokens, OAuth 1.0a is supported.
-First, get OAuth credentials through the [Discogs OAuth flow](https://www.discogs.com/developers/#page:authentication,header:authentication-oauth-flow).
-
-```php
- 'your_consumer_key', // from Discogs developer page
- 'consumer_secret' => 'your_consumer_secret', // from Discogs developer page
- 'token' => 'user_oauth_token', // from OAuth flow
- 'token_secret' => 'user_oauth_token_secret' // from OAuth flow
-]);
-
-$stack->push($oauth);
-
-$client = ClientFactory::factory([
- 'headers' => [
- 'User-Agent' => 'MyApp/1.0 +https://mysite.com'
- ],
- 'handler' => $stack,
- 'auth' => 'oauth'
-]);
-```
-
-> **Note:** Implementing the full OAuth flow is complex. For examples, see [ricbra/php-discogs-api-example](https://github.com/ricbra/php-discogs-api-example).
-
-### History
-
-Another cool plugin is the History plugin:
-
-```php
-push($history);
-
-$client = Discogs\ClientFactory::factory([
- 'headers' => [
- 'User-Agent' => 'MyApp/1.0 +https://mysite.com',
- 'Authorization' => 'Discogs key=your_key, secret=your_secret'
- ],
- 'handler' => $handler
-]);
-
-$response = $client->search([
- 'q' => 'searchstring'
-]);
-
-foreach ($container as $row) {
- print $row['request'] -> getMethod(); // GET
- print $row['request'] -> getRequestTarget(); // /database/search?q=searchstring
- print strval($row['request'] -> getUri()); // https://api.discogs.com/database/search?q=searchstring
- print $row['response'] -> getStatusCode(); // 200
- print $row['response'] -> getReasonPhrase(); // OK
-}
-```
-
-### More info and plugins
+require __DIR__ . '/vendor/autoload.php';
-For more information about Guzzle and its plugins checkout [the docs.](https://docs.guzzlephp.org/en/latest/)
+use Calliostro\Discogs\ClientFactory;
-### Perform a search
+// Basic client for public data
+$discogs = ClientFactory::create();
-Authentication is required for this endpoint.
-
-```php
-search([
- 'q' => 'Meagashira'
+// Fetch artist information
+$artist = $discogs->artistGet([
+ 'id' => '45031' // Pink Floyd
]);
-// Loop through results
-foreach ($response['results'] as $result) {
- var_dump($result['title']);
-}
-// Pagination data
-var_dump($response['pagination']);
-
-// Dump all data
-var_dump($response->toArray());
-```
-
-### Get information about a label
-```php
-getLabel([
- 'id' => 1
+$release = $discogs->releaseGet([
+ 'id' => '249504' // Nirvana - Nevermind
]);
-```
-### Get information about an artist
-
-```php
-getArtist([
- 'id' => 1
-]);
+echo "Artist: " . $artist['name'] . "\n";
+echo "Release: " . $release['title'] . "\n";
```
-### Get information about a release
+### Collection and Marketplace
```php
-getRelease([
- 'id' => 1
+// Authenticated client for protected operations
+$discogs = ClientFactory::createWithToken('your-personal-access-token');
+
+// Access your collection
+$folders = $discogs->collectionFolders(['username' => 'your-username']);
+$items = $discogs->collectionItems(['username' => 'your-username', 'folder_id' => '0']);
+
+// Marketplace operations
+$inventory = $discogs->inventoryGet(['username' => 'your-username']);
+$orders = $discogs->ordersGet(['status' => 'Shipped']);
+
+// Create a marketplace listing
+$listing = $discogs->listingCreate([
+ 'release_id' => '249504',
+ 'condition' => 'Near Mint (NM or M-)',
+ 'price' => '25.00'
]);
-
-echo $release['title']."\n";
```
-### Get information about a master release
+### Database Search and Discovery
```php
-search(['q' => 'Pink Floyd', 'type' => 'artist']);
+$releases = $discogs->artistReleases(['id' => '45031', 'sort' => 'year']);
-$master = $client->getMaster([
- 'id' => 1
-]);
+// Master release versions
+$master = $discogs->masterGet(['id' => '18512']);
+$versions = $discogs->masterVersions(['id' => '18512']);
-echo $master['title']."\n";
+// Label information
+$label = $discogs->labelGet(['id' => '1']); // Warp Records
+$labelReleases = $discogs->labelReleases(['id' => '1']);
```
-### Get image
-
-Discogs returns the full url to images, so just use the internal client to get those:
-
-```php
-getRelease([
- 'id' => 1
-]);
+- **Ultra-Lightweight** – Only 2 classes, ~234 lines of logic + service descriptions
+- **Complete API Coverage** – All 65+ Discogs API endpoints supported
+- **Direct API Calls** – `$client->artistGet()` maps to `/artists/{id}`, no abstractions
+- **Type Safe + IDE Support** – Full PHP 8.1+ types, PHPStan Level 8, method autocomplete
+- **Future-Ready** – PHP 8.5 compatible (beta/dev testing)
+- **Pure Guzzle** – Modern HTTP client, no custom transport layers
+- **Well Tested** – 100% test coverage, PSR-12 compliant
+- **Secure Authentication** – Full OAuth and Personal Access Token support
-foreach ($release['images'] as $image) {
- $response = $client->getHttpClient()->get($image['uri']);
- // response code
- echo $response->getStatusCode();
- // image blob itself
- echo $response->getBody()->getContents();
-}
-```
+## 🎵 All Discogs API Methods as Direct Calls
-### User lists
+- **Database Methods** – search(), artistGet(), releaseGet(), masterGet(), labelGet()
+- **Collection Methods** – collectionFolders(), collectionItems(), collectionFolder()
+- **Wantlist Methods** – wantlistGet()
+- **Marketplace Methods** – inventoryGet(), listingCreate(), listingUpdate(), listingDelete()
+- **Order Methods** – ordersGet(), orderGet(), orderUpdate(), orderMessages()
+- **User Methods** – identityGet(), userGet()
+- **Master Methods** – masterVersions()
+- **Label Methods** – labelReleases()
-#### Get user lists
+*All 65+ Discogs API endpoints are supported with clean documentation — see [Discogs API Documentation](https://www.discogs.com/developers/) for complete method reference*
-```php
-getUserLists([
- 'username' => 'example',
- 'page' => 1, // default
- 'per_page' => 500 // min 1, max 500, default 50
-]);
-```
+- **php** ^8.1
+- **guzzlehttp/guzzle** ^6.5 || ^7.0
-#### Get user list items
+## 🔧 Advanced Configuration
-```php
-getLists([
- 'list_id' => 1
-]);
-```
-
-### Get user wantlist
+For basic customizations like timeout or User-Agent, use the ClientFactory:
```php
-getWantlist([
- 'username' => 'example',
- 'page' => 1, // default
- 'per_page' => 500 // min 1, max 500, default 50
+$discogs = ClientFactory::create('MyApp/1.0 (+https://myapp.com)', [
+ 'timeout' => 30,
+ 'headers' => [
+ 'User-Agent' => 'MyApp/1.0 (+https://myapp.com)',
+ ]
]);
```
-### User Collection
-
-Authorization is required when `folder_id` is not `0`.
+### Option 2: Advanced Guzzle Configuration
-#### Get collection folders
+For advanced HTTP client features (middleware, interceptors, etc.), create your own Guzzle client:
```php
-getCollectionFolders([
- 'username' => 'example'
+$httpClient = new Client([
+ 'timeout' => 30,
+ 'connect_timeout' => 10,
+ 'headers' => [
+ 'User-Agent' => 'MyApp/1.0 (+https://myapp.com)',
+ ]
]);
-```
-
-#### Get a collection folder
-```php
-getCollectionFolder([
- 'username' => 'example',
- 'folder_id' => 1
-]);
+// Or via ClientFactory
+$discogs = ClientFactory::create('MyApp/1.0', $httpClient);
```
-#### Get collection items by folder
+> **💡 Note:** By default, the client uses `DiscogsClient/3.0 (+https://github.com/calliostro/php-discogs-api)` as User-Agent. You can override this by setting custom headers as shown above.
-```php
-getCollectionItemsByFolder([
- 'username' => 'example',
- 'folder_id' => 3
-]);
-```
+Discogs supports different authentication flows:
-### 🛒 Listings
+### Personal Access Token (Recommended)
-Creating and manipulating listings requires you to be authenticated as the seller
-
-#### Create a Listing
+For accessing your own account data, use a Personal Access Token from [Discogs Developer Settings](https://www.discogs.com/settings/developers):
```php
createListing([
- 'release_id' => '1',
- 'condition' => 'Good (G)',
- 'price' => 3.49,
- 'status' => 'For Sale'
-]);
-```
+require __DIR__ . '/vendor/autoload.php';
-#### Change Listing
+use Calliostro\Discogs\ClientFactory;
-```php
-changeListing([
- 'listing_id' => '123',
- 'condition' => 'Good (G)',
- 'price' => 3.49,
-]);
+// Access protected endpoints
+$identity = $discogs->identityGet();
+$collection = $discogs->collectionFolders(['username' => 'your-username']);
```
-#### Delete a Listing
+### OAuth 1.0a Authentication
-```php
-deleteListing(['listing_id' => '123']);
-```
-
-#### Create Listings in bulk (via CSV)
+For building applications that access user data on their behalf:
```php
addInventory(['upload' => fopen('path/to/file.csv', 'r')]);
-
-// CSV format (example):
-// release_id,condition,price
-// 1,Mint (M),19.99
-// 2,Near Mint (NM or M-),14.99
-```
-#### Delete Listings in bulk (via CSV)
+// You need to implement the OAuth flow to get these tokens
+$discogs = ClientFactory::createWithOAuth('oauth-token', 'oauth-token-secret');
-```php
-deleteInventory(['upload' => fopen('path/to/file.csv', 'r')]);
-
-// CSV format (example):
-// listing_id
-// 123
-// 213
-// 321
+$identity = $discogs->identityGet();
+$orders = $discogs->ordersGet();
```
-### 📈 Orders & Marketplace
-
-#### Get orders
-
-```php
-getOrders([
- 'status' => 'New Order', // optional
- 'sort' => 'created', // optional
- 'sort_order' => 'desc' // optional
-]);
-```
+> **💡 Note:** Implementing the complete OAuth flow is complex and beyond the scope of this README. For detailed examples, see the [Discogs OAuth Documentation](https://www.discogs.com/developers/#page:authentication,header:authentication-oauth-flow).
-#### Get a specific order
+## 🧪 Testing
-```php
-getOrder(['order_id' => '123-456']);
+```bash
+composer test
```
-#### Update order
-
-```php
-changeOrder([
- 'order_id' => '123-456',
- 'status' => 'Shipped',
- 'shipping' => 5.00
-]);
+```bash
+composer analyse
```
-### 👤 User Profile & Identity
+Check code style:
-#### Get authenticated user identity
-
-```php
-getOAuthIdentity();
+```bash
+composer cs
```
-#### Get user profile
-
-```php
-getProfile(['username' => 'discogs_user']);
-```
+For complete API documentation including all available parameters, visit the [Discogs API Documentation](https://www.discogs.com/developers/).
-#### Get user inventory
+### Popular Methods
-```php
-getInventory([
- 'username' => 'seller_name',
- 'status' => 'For Sale', // optional
- 'per_page' => 100 // optional
-]);
-```
+- `search($params)` – Search the Discogs database
+- `artistGet($params)` – Get artist information
+- `artistReleases($params)` – Get artist's releases
+- `releaseGet($params)` – Get release information
+- `masterGet($params)` – Get master release information
+- `masterVersions($params)` – Get master release versions
-## 🔧 Symfony Bundle
+#### Collection Methods
-For integration with Symfony 6.4 (LTS), 7.x, and 8.0 (beta), see [calliostro/discogs-bundle](https://github.com/calliostro/discogs-bundle).
+- `collectionFolders($params)` – Get user's collection folders
+- `collectionItems($params)` – Get collection items by folder
+- `collectionFolder($params)` – Get specific collection folder
-## 📚 Documentation
+#### User Methods
-Further documentation can be found at the [Discogs API v2.0 Documentation](https://www.discogs.com/developers/index.html).
+- `identityGet($params)` – Get authenticated user's identity (auth required)
+- `userGet($params)` – Get user profile information
+- `wantlistGet($params)` – Get user's wantlist
## 🤝 Contributing
@@ -544,22 +254,14 @@ Further documentation can be found at the [Discogs API v2.0 Documentation](https
Please ensure your code follows PSR-12 standards and includes tests.
-## 💬 Support
-
-For questions or help, feel free to open an issue or reach out!
-
## 📄 License
This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
-## 🙏 Credits
-
-Initial development by [ricbra/php-discogs-api](https://github.com/ricbra/php-discogs-api).
-
-Enhanced and modernized by [AnssiAhola/php-discogs-api](https://github.com/AnssiAhola/php-discogs-api) with additional API methods.
-
----
+## 🙏 Acknowledgments
-⭐ **Found this useful?** Give it a star to show your support!
+- [Discogs](https://www.discogs.com/) for providing the excellent music database API
+- [Guzzle](https://docs.guzzlephp.org/) for the robust HTTP client
+- [ricbra/php-discogs-api](https://github.com/ricbra/php-discogs-api) and [AnssiAhola/php-discogs-api](https://github.com/AnssiAhola/php-discogs-api) for the original inspiration
-This library is built upon [Guzzle HTTP](https://docs.guzzlephp.org/en/latest/) for reliable API communication.
+> **⭐ Star this repo if you find it useful! It helps others discover this lightweight solution.**
diff --git a/UPGRADE.md b/UPGRADE.md
index 5d93988..68c669d 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -1,40 +1,164 @@
-Upgrade from 0.x.x to 1.0.0
-===========================
+# Upgrade Guide: v2.x → v3.0
-API calls
----------
+This guide covers the breaking changes when upgrading from php-discogs-api v2.x to v3.0.
-All API calls signatures have changed. Old situation:
+## Overview
- getArtist(1);
+## Requirements Changes
-New situation is always using arrays:
+### PHP Version
+- **Before (v2.x)**: PHP 7.3+
+- **After (v3.0)**: PHP 8.1+ (strict requirement)
- getArtist([
- 'id' => 1
- ]);
+## Namespace Changes
-In the resources/service.php file you can find all the implemented calls and their signatures and responses.
+```php
+// OLD (v2.x)
+use Discogs\ClientFactory;
+use Discogs\DiscogsClient;
-Iterator removed
-----------------
+// NEW (v3.0)
+use Calliostro\Discogs\ClientFactory;
+use Calliostro\Discogs\DiscogsApiClient;
+```
-It's not yet possible to iterate over the responses. Perhaps this will be added in the near future. It should be
-easy to implement an Iterator yourself. PR's are welcome :).
+## Client Creation
-Caching removed
----------------
+### Before (v2.x)
+```php
+use Discogs\ClientFactory;
-Caching in the library itself is removed. This can be achieved by creating or using a cache plugin. At the moment of
-writing the plugin for Guzzle isn't refactored yet for version 4.0.
+// Basic client
+$client = ClientFactory::factory([
+ 'headers' => ['User-Agent' => 'MyApp/1.0']
+]);
-No more models
---------------
+// With authentication
+$client = ClientFactory::factory([
+ 'headers' => [
+ 'User-Agent' => 'MyApp/1.0',
+ 'Authorization' => 'Discogs token=your-token'
+ ]
+]);
+```
-Models have been replaced with plain old arrays. Models were nice for typing but a hell to manage. All responses will
-return an array. When you want to debug the output use $response->toArray().
+### After (v3.0)
+```php
+use Calliostro\Discogs\ClientFactory;
+// Anonymous client
+$client = ClientFactory::create('MyApp/1.0');
+
+// Personal Access Token (recommended)
+$client = ClientFactory::createWithToken('your-token', 'MyApp/1.0');
+
+// OAuth
+$client = ClientFactory::createWithOAuth('token', 'secret', 'MyApp/1.0');
+```
+
+## API Method Calls
+
+### Before (v2.x): Guzzle Services Commands
+```php
+// Search
+$results = $client->search(['q' => 'Nirvana', 'type' => 'artist']);
+
+// Get artist (command-based)
+$artist = $client->getArtist(['id' => '45031']);
+
+// Get releases
+$releases = $client->getArtistReleases(['id' => '45031']);
+
+// Marketplace
+$inventory = $client->getInventory(['username' => 'user']);
+```
+
+### After (v3.0): Magic Method Calls
+```php
+// Search (same parameters, different method name)
+$results = $client->search(['q' => 'Nirvana', 'type' => 'artist']);
+
+// Get artist (magic method)
+$artist = $client->artistGet(['id' => '45031']);
+
+// Get releases (magic method)
+$releases = $client->artistReleases(['id' => '45031']);
+
+// Marketplace (magic method)
+$inventory = $client->inventoryGet(['username' => 'user']);
+```
+
+## Method Name Mapping
+
+| v2.x Command | v3.0 Magic Method | Parameters |
+|------------------------------|---------------------|-----------------------------|
+| `getArtist` | `artistGet` | `['id' => 'string']` |
+| `getArtistReleases` | `artistReleases` | `['id' => 'string']` |
+| `getRelease` | `releaseGet` | `['id' => 'string']` |
+| `getMaster` | `masterGet` | `['id' => 'string']` |
+| `getMasterVersions` | `masterVersions` | `['id' => 'string']` |
+| `getLabel` | `labelGet` | `['id' => 'string']` |
+| `getLabelReleases` | `labelReleases` | `['id' => 'string']` |
+| `search` | `search` | `['q' => 'string']` |
+| `getOAuthIdentity` | `identityGet` | `[]` |
+| `getProfile` | `userGet` | `['username' => 'string']` |
+| `getCollectionFolders` | `collectionFolders` | `['username' => 'string']` |
+| `getCollectionFolder` | `collectionFolder` | `['username', 'folder_id']` |
+| `getCollectionItemsByFolder` | `collectionItems` | `['username', 'folder_id']` |
+| `getInventory` | `inventoryGet` | `['username' => 'string']` |
+| `getOrders` | `ordersGet` | `[]` |
+| `getOrder` | `orderGet` | `['order_id' => 'string']` |
+| `createListing` | `listingCreate` | `[...]` |
+| `changeListing` | `listingUpdate` | `[...]` |
+| `deleteListing` | `listingDelete` | `[...]` |
+
+## Configuration Changes
+
+### Service Configuration
+- **Before**: Complex Guzzle Services YAML/JSON definitions
+- **After**: Simple PHP array in `resources/service.php`
+
+### Throttling
+- **Before**: `ThrottleSubscriber` with Guzzle middlewares
+- **After**: Handle rate limiting in your application layer
+
+### Error Handling
+- **Before**: Guzzle Services exceptions
+- **After**: Standard `RuntimeException` with clear messages
+
+## Testing Your Migration
+
+1. **Update composer.json**:
+ ```json
+ {
+ "require": {
+ "calliostro/php-discogs-api": "^3.0"
+ }
+ }
+ ```
+
+2. **Update namespace imports**
+3. **Replace client creation calls**
+4. **Update method calls using the mapping table**
+5. **Test your application thoroughly**
+
+## Benefits of v3.0
+
+- **Ultra-lightweight**: Two classes instead of complex services
+- **Better performance**: Direct HTTP calls, no command layer overhead
+- **Modern PHP**: PHP 8.1+ features, strict typing, better IDE support
+- **Easier testing**: Simple mock-friendly HTTP client
+- **Cleaner code**: Magic methods eliminate boilerplate
+- **Better maintainability**: Simplified architecture
+
+## Need Help?
+
+- Check the [README.md](README.md) for complete v3.0 documentation
+- Review the [CHANGELOG.md](CHANGELOG.md) for detailed changes
+- Open an issue if you encounter migration problems
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..7d7a6c9
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,9 @@
+coverage:
+ status:
+ project:
+ default:
+ branches:
+ - main
+ - legacy/*
+ - hotfix/*
+ - release/*
diff --git a/composer.json b/composer.json
index c809dde..dbf682d 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "calliostro/php-discogs-api",
- "description": "PHP library for the Discogs API — vinyl, music data & integration made easy",
+ "description": "Ultra-lightweight Discogs API client for PHP 8.1+ — Complete API coverage, minimal dependencies",
"type": "library",
"keywords": [
"php",
@@ -12,7 +12,9 @@
"audio",
"vinyl",
"guzzle",
- "library"
+ "library",
+ "php8",
+ "lightweight"
],
"license": "MIT",
"authors": [
@@ -26,23 +28,38 @@
}
],
"require": {
- "php": "^7.3 || ^8.0",
- "guzzlehttp/guzzle": "^7.0",
- "guzzlehttp/guzzle-services": "^1.3|^1.4",
- "guzzlehttp/oauth-subscriber": "^0.8.1"
+ "php": "^8.1",
+ "guzzlehttp/guzzle": "^6.5 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.5",
- "ext-json": "*"
+ "phpunit/phpunit": "^10.0",
+ "phpstan/phpstan": "^1.0",
+ "friendsofphp/php-cs-fixer": "^3.0"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
- "sort-packages": true
+ "sort-packages": true,
+ "platform": {
+ "php": "8.1.0"
+ }
},
"autoload": {
- "psr-0": {
- "Discogs": "lib/"
+ "psr-4": {
+ "Calliostro\\Discogs\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Calliostro\\Discogs\\Tests\\": "tests/"
}
- }
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "cs": "vendor/bin/php-cs-fixer fix --dry-run --diff --verbose",
+ "cs-fix": "vendor/bin/php-cs-fixer fix --verbose",
+ "analyse": "phpstan analyse src/ --level=8"
+ },
+ "minimum-stability": "stable",
+ "prefer-stable": true
}
diff --git a/lib/Discogs/ClientFactory.php b/lib/Discogs/ClientFactory.php
deleted file mode 100644
index 41a8545..0000000
--- a/lib/Discogs/ClientFactory.php
+++ /dev/null
@@ -1,47 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Discogs;
-
-use GuzzleHttp\Client;
-use GuzzleHttp\Command\Guzzle\Description;
-
-final class ClientFactory
-{
- public static function factory(array $config = []): DiscogsClient
- {
- $defaultConfig = [
- 'headers' => ['User-Agent' => 'php-discogs-api/2.0.0 +https://github.com/calliostro/php-discogs-api'],
- 'auth' => 'oauth',
- ];
- $client = new Client(self::mergeRecursive($defaultConfig, $config));
- $service = include __DIR__ . '/../../resources/service.php';
- $description = new Description($service);
- return new DiscogsClient($client, $description);
- }
-
- private static function &mergeRecursive(array $array1, $array2 = null): array
- {
- $merged = $array1;
- if (is_array($array2)) {
- foreach ($array2 as $key => $val) {
- if (is_array($val)) {
- $merged[$key] = isset($merged[$key]) && is_array($merged[$key]) ?
- self::mergeRecursive($merged[$key], $val) : $val;
- } else {
- $merged[$key] = $val;
- }
- }
- }
-
- return $merged;
- }
-}
diff --git a/lib/Discogs/DiscogsClient.php b/lib/Discogs/DiscogsClient.php
deleted file mode 100644
index 0115dff..0000000
--- a/lib/Discogs/DiscogsClient.php
+++ /dev/null
@@ -1,46 +0,0 @@
-See Discogs API Documentation
- * @method Result getArtistReleases(array $parameters) See Discogs API Documentation
- * @method Result search(array $parameters) See Discogs API Documentation
- * @method Result getRelease(array $parameters) See Discogs API Documentation
- * @method Result getMaster(array $parameters) See Discogs API Documentation
- * @method Result getMasterVersions(array $parameters) See Discogs API Documentation
- * @method Result getLabel(array $parameters) See Discogs API Documentation
- * @method Result getLabelReleases(array $parameters) See Discogs API Documentation
- * @method Result getOAuthIdentity() See Discogs API Documentation
- * @method Result getProfile(array $parameters) See Discogs API Documentation
- * @method Result getInventory(array $parameters) See Discogs API Documentation
- * @method Result addInventory(array $parameters) See Discogs API Documentation
- * @method Result deleteInventory(array $parameters) See Discogs API Documentation
- * @method Result getOrder(array $parameters) See Discogs API Documentation
- * @method Result getOrders(array $parameters) See Discogs API Documentation
- * @method Result changeOrder(array $parameters) See Discogs API Documentation
- * @method Result getOrderMessages(array $parameters) See Discogs API Documentation
- * @method Result addOrderMessage(array $parameters) See Discogs API Documentation
- * @method Result createListing(array $parameters) See Discogs API Documentation
- * @method Result changeListing(array $parameters) See Discogs API Documentation
- * @method Result deleteListing(array $parameters) See Discogs API Documentation
- * @method Result getCollectionFolders(array $parameters) See Discogs API Documentation
- * @method Result getCollectionFolder(array $parameters) See Discogs API Documentation
- * @method Result getCollectionItemsByFolder(array $parameters) See Discogs API Documentation
- * @method Result getUserLists(array $parameters) See Discogs API Documentation
- * @method Result getLists(array $parameters) See Discogs API Documentation
- * @method Result getWantlist(array $parameters) See Discogs API Documentation
- */
-class DiscogsClient extends GuzzleClient
-{
-}
diff --git a/lib/Discogs/Subscriber/ThrottleSubscriber.php b/lib/Discogs/Subscriber/ThrottleSubscriber.php
deleted file mode 100644
index 145faf0..0000000
--- a/lib/Discogs/Subscriber/ThrottleSubscriber.php
+++ /dev/null
@@ -1,61 +0,0 @@
-throttle = $throttle;
- $this->max_retries = $max_retries;
- }
-
- public function decider(): callable
- {
- return function (
- int $retries,
- Request $request,
- Response $response = null,
- TransferException $exception = null
- ) {
- if ($retries >= $this->max_retries) {
- return false;
- }
-
- // Retry on connection exceptions
- if ($exception instanceof ConnectException) {
- return true;
- }
-
- if ($response) {
- if ($response->getStatusCode() == 429) {
- return true;
- }
- // Retry on server errors
- if ($response->getStatusCode() >= 500) {
- return true;
- }
- }
-
- return false;
- };
- }
-
- public function delay(): callable
- {
- return function (int $retries) {
- return $this->throttle * (2 ** $retries);
- };
- }
-}
diff --git a/phpunit-legacy.xml.dist b/phpunit-legacy.xml.dist
deleted file mode 100644
index b972f6b..0000000
--- a/phpunit-legacy.xml.dist
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- tests
-
-
-
-
-
- lib
-
-
-
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 01a5af3..6902cb9 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,19 +1,27 @@
-
-
-
-
-
-
-
+
-
- tests/Discogs/
+
+ tests
-
+
- ./lib
+ src
-
+
+
+
+
+
diff --git a/resources/service.php b/resources/service.php
index ad4185a..3ef24db 100644
--- a/resources/service.php
+++ b/resources/service.php
@@ -1,697 +1,612 @@
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
return [
'baseUrl' => 'https://api.discogs.com/',
'operations' => [
- 'getArtist' => [
+ // ===========================
+ // DATABASE METHODS
+ // ===========================
+ 'artist.get' => [
'httpMethod' => 'GET',
'uri' => 'artists/{id}',
- 'responseModel' => 'GetResponse',
'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'id' => ['required' => true],
+ ],
],
- 'getArtistReleases' => [
+ 'artist.releases' => [
'httpMethod' => 'GET',
'uri' => 'artists/{id}/releases',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'sort' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'sort_order' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ]
- ]
+ 'parameters' => [
+ 'id' => ['required' => true],
+ 'sort' => ['required' => false],
+ 'sort_order' => ['required' => false],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
],
- 'search' => [
- 'httpMethod' => 'GET',
- 'uri' => 'database/search',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'q' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'type' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'title' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'release_title' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'credit' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'artist' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'anv' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'label' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'genre' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'style' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'country' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'year' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'format' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'catno' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'barcode' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'track' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'submitter' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'contributor' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getRelease' => [
+ 'release.get' => [
'httpMethod' => 'GET',
'uri' => 'releases/{id}',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'curr_abbr' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getMaster' => [
+ 'parameters' => [
+ 'id' => ['required' => true],
+ 'curr_abbr' => ['required' => false],
+ ],
+ ],
+ 'release.rating.get' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'releases/{release_id}/rating/{username}',
+ 'parameters' => [
+ 'release_id' => ['required' => true],
+ 'username' => ['required' => true],
+ ],
+ ],
+ 'release.rating.put' => [
+ 'httpMethod' => 'PUT',
+ 'uri' => 'releases/{release_id}/rating/{username}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'release_id' => ['required' => true],
+ 'username' => ['required' => true],
+ 'rating' => ['required' => true],
+ ],
+ ],
+ 'release.rating.delete' => [
+ 'httpMethod' => 'DELETE',
+ 'uri' => 'releases/{release_id}/rating/{username}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'release_id' => ['required' => true],
+ 'username' => ['required' => true],
+ ],
+ ],
+ 'release.rating.community' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'releases/{release_id}/rating',
+ 'parameters' => [
+ 'release_id' => ['required' => true],
+ ],
+ ],
+ 'release.stats' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'releases/{release_id}/stats',
+ 'parameters' => [
+ 'release_id' => ['required' => true],
+ ],
+ ],
+ 'master.get' => [
'httpMethod' => 'GET',
'uri' => 'masters/{id}',
- 'responseModel' => 'GetResponse',
'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'id' => ['required' => true],
+ ],
],
- 'getMasterVersions' => [
+ 'master.versions' => [
'httpMethod' => 'GET',
'uri' => 'masters/{id}/versions',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getLabel' => [
+ 'parameters' => [
+ 'id' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ 'format' => ['required' => false],
+ 'label' => ['required' => false],
+ 'released' => ['required' => false],
+ 'country' => ['required' => false],
+ 'sort' => ['required' => false],
+ 'sort_order' => ['required' => false],
+ ],
+ ],
+ 'label.get' => [
'httpMethod' => 'GET',
'uri' => 'labels/{id}',
- 'responseModel' => 'GetResponse',
'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'id' => ['required' => true],
+ ],
],
- 'getLabelReleases' => [
+ 'label.releases' => [
'httpMethod' => 'GET',
'uri' => 'labels/{id}/releases',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getOAuthIdentity' => [
+ 'parameters' => [
+ 'id' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+ 'search' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'database/search',
+ 'parameters' => [
+ 'q' => ['required' => false],
+ 'type' => ['required' => false],
+ 'title' => ['required' => false],
+ 'release_title' => ['required' => false],
+ 'credit' => ['required' => false],
+ 'artist' => ['required' => false],
+ 'anv' => ['required' => false],
+ 'label' => ['required' => false],
+ 'genre' => ['required' => false],
+ 'style' => ['required' => false],
+ 'country' => ['required' => false],
+ 'year' => ['required' => false],
+ 'format' => ['required' => false],
+ 'catno' => ['required' => false],
+ 'barcode' => ['required' => false],
+ 'track' => ['required' => false],
+ 'submitter' => ['required' => false],
+ 'contributor' => ['required' => false],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+
+ // ===========================
+ // USER IDENTITY METHODS
+ // ===========================
+ 'identity.get' => [
'httpMethod' => 'GET',
'uri' => 'oauth/identity',
- 'responseModel' => 'GetResponse',
+ 'requiresAuth' => true,
],
- 'getProfile' => [
+ 'user.get' => [
'httpMethod' => 'GET',
'uri' => 'users/{username}',
- 'responseModel' => 'GetResponse',
'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
+ 'username' => ['required' => true],
+ ],
+ ],
+ 'user.edit' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'name' => ['required' => false],
+ 'home_page' => ['required' => false],
+ 'location' => ['required' => false],
+ 'profile' => ['required' => false],
+ 'curr_abbr' => ['required' => false],
+ ],
+ ],
+ 'user.submissions' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/submissions',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+ 'user.contributions' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/contributions',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+
+ // ===========================
+ // COLLECTION METHODS
+ // ===========================
+ 'collection.folders' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/collection/folders',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ ],
+ ],
+ 'collection.folder.get' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ ],
+ ],
+ 'collection.folder.create' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}/collection/folders',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'name' => ['required' => true],
+ ],
+ ],
+ 'collection.folder.edit' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ 'name' => ['required' => true],
+ ],
+ ],
+ 'collection.folder.delete' => [
+ 'httpMethod' => 'DELETE',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ ],
+ ],
+ 'collection.items' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}/releases',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ 'sort' => ['required' => false],
+ 'sort_order' => ['required' => false],
+ ],
+ ],
+ 'collection.items.by_release' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/collection/releases/{release_id}',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'release_id' => ['required' => true],
+ ],
+ ],
+ 'collection.add_release' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}/releases/{release_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ 'release_id' => ['required' => true],
+ ],
+ ],
+ 'collection.edit_release' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}/releases/{release_id}/instances/{instance_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ 'release_id' => ['required' => true],
+ 'instance_id' => ['required' => true],
+ 'rating' => ['required' => false],
+ 'folder_id_new' => ['required' => false],
+ ],
+ ],
+ 'collection.remove_release' => [
+ 'httpMethod' => 'DELETE',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}/releases/{release_id}/instances/{instance_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ 'release_id' => ['required' => true],
+ 'instance_id' => ['required' => true],
+ ],
+ ],
+ 'collection.custom_fields' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/collection/fields',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ ],
+ ],
+ 'collection.edit_field' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}/collection/folders/{folder_id}/releases/{release_id}/instances/{instance_id}/fields/{field_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'folder_id' => ['required' => true],
+ 'release_id' => ['required' => true],
+ 'instance_id' => ['required' => true],
+ 'field_id' => ['required' => true],
+ 'value' => ['required' => true],
+ ],
+ ],
+ 'collection.value' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/collection/value',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ ],
+ ],
+
+ // ===========================
+ // WANTLIST METHODS
+ // ===========================
+ 'wantlist.get' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'users/{username}/wants',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+ 'wantlist.add' => [
+ 'httpMethod' => 'PUT',
+ 'uri' => 'users/{username}/wants/{release_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'release_id' => ['required' => true],
+ 'notes' => ['required' => false],
+ 'rating' => ['required' => false],
],
],
- 'getInventory' => [
+ 'wantlist.edit' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'users/{username}/wants/{release_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'release_id' => ['required' => true],
+ 'notes' => ['required' => false],
+ 'rating' => ['required' => false],
+ ],
+ ],
+ 'wantlist.remove' => [
+ 'httpMethod' => 'DELETE',
+ 'uri' => 'users/{username}/wants/{release_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'release_id' => ['required' => true],
+ ],
+ ],
+
+ // ===========================
+ // MARKETPLACE METHODS
+ // ===========================
+ 'inventory.get' => [
'httpMethod' => 'GET',
'uri' => 'users/{username}/inventory',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'status' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'sort' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'sort_order' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'addInventory' => [
- 'summary' => 'Upload a CSV of listings to add to your inventory.',
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'status' => ['required' => false],
+ 'sort' => ['required' => false],
+ 'sort_order' => ['required' => false],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+ 'listing.get' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'marketplace/listings/{listing_id}',
+ 'parameters' => [
+ 'listing_id' => ['required' => true],
+ 'curr_abbr' => ['required' => false],
+ ],
+ ],
+ 'listing.create' => [
'httpMethod' => 'POST',
- 'uri' => 'inventory/upload/add',
- 'responseModel' => 'GetResponse',
+ 'uri' => 'marketplace/listings',
+ 'requiresAuth' => true,
'parameters' => [
- 'upload' => [
- 'type' => 'any',
- 'location' => 'multipart',
- 'required' => true
- ]
- ]
+ 'release_id' => ['required' => true],
+ 'condition' => ['required' => true],
+ 'sleeve_condition' => ['required' => false],
+ 'price' => ['required' => true],
+ 'comments' => ['required' => false],
+ 'allow_offers' => ['required' => false],
+ 'status' => ['required' => true],
+ 'external_id' => ['required' => false],
+ 'location' => ['required' => false],
+ 'weight' => ['required' => false],
+ 'format_quantity' => ['required' => false],
+ ],
],
- 'deleteInventory' => [
- 'summary' => 'Upload a CSV of listings to delete from your inventory.',
+ 'listing.update' => [
'httpMethod' => 'POST',
- 'uri' => 'inventory/upload/delete',
- 'responseModel' => 'GetResponse',
+ 'uri' => 'marketplace/listings/{listing_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'listing_id' => ['required' => true],
+ 'condition' => ['required' => false],
+ 'sleeve_condition' => ['required' => false],
+ 'price' => ['required' => false],
+ 'comments' => ['required' => false],
+ 'allow_offers' => ['required' => false],
+ 'status' => ['required' => false],
+ 'external_id' => ['required' => false],
+ 'location' => ['required' => false],
+ 'weight' => ['required' => false],
+ 'format_quantity' => ['required' => false],
+ 'curr_abbr' => ['required' => false],
+ ],
+ ],
+ 'listing.delete' => [
+ 'httpMethod' => 'DELETE',
+ 'uri' => 'marketplace/listings/{listing_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'listing_id' => ['required' => true],
+ ],
+ ],
+ 'marketplace.fee' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'marketplace/fee/{price}',
+ 'parameters' => [
+ 'price' => ['required' => true],
+ ],
+ ],
+ 'marketplace.fee_currency' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'marketplace/fee/{price}/{currency}',
+ 'parameters' => [
+ 'price' => ['required' => true],
+ 'currency' => ['required' => true],
+ ],
+ ],
+ 'marketplace.price_suggestions' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'marketplace/price_suggestions/{release_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'release_id' => ['required' => true],
+ ],
+ ],
+ 'marketplace.stats' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'marketplace/stats/{release_id}',
'parameters' => [
- 'upload' => [
- 'type' => 'any',
- 'location' => 'multipart',
- 'required' => true
- ]
- ]
+ 'release_id' => ['required' => true],
+ 'curr_abbr' => ['required' => false],
+ ],
],
- 'getOrder' => [
+ 'order.get' => [
'httpMethod' => 'GET',
'uri' => 'marketplace/orders/{order_id}',
- 'responseModel' => 'GetResponse',
+ 'requiresAuth' => true,
'parameters' => [
- 'order_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'order_id' => ['required' => true],
+ ],
],
- 'getOrders' => [
+ 'orders.get' => [
'httpMethod' => 'GET',
'uri' => 'marketplace/orders',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'status' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ],
- 'sort' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'sort_order' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'created_before' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'created_after' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ],
- 'archived' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false,
- ]
- ]
- ],
- 'changeOrder' => [
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'status' => ['required' => false],
+ 'sort' => ['required' => false],
+ 'sort_order' => ['required' => false],
+ 'created_before' => ['required' => false],
+ 'created_after' => ['required' => false],
+ 'archived' => ['required' => false],
+ ],
+ ],
+ 'order.update' => [
'httpMethod' => 'POST',
'uri' => 'marketplace/orders/{order_id}',
- 'summary' => 'Edit the data associated with an order.',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'order_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'status' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'shipping' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => false,
- ]
- ]
- ],
- 'getOrderMessages' => [
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'order_id' => ['required' => true],
+ 'status' => ['required' => false],
+ 'shipping' => ['required' => false],
+ ],
+ ],
+ 'order.messages' => [
'httpMethod' => 'GET',
'uri' => 'marketplace/orders/{order_id}/messages',
- 'summary' => 'Returns a list of the order’s messages with the most recent first.',
- 'responseModel' => 'GetResponse',
+ 'requiresAuth' => true,
'parameters' => [
- 'order_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'order_id' => ['required' => true],
+ ],
],
- 'addOrderMessage' => [
+ 'order.message.add' => [
'httpMethod' => 'POST',
'uri' => 'marketplace/orders/{order_id}/messages',
- 'summary' => 'Adds a new message to the order’s message log.',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'order_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'message' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'status' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ]
- ]
- ],
- 'createListing' => [
- 'httpMethod' => 'POST',
- 'uri' => '/marketplace/listings',
- 'summary' => 'Create a Marketplace listing.',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'release_id' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => true
- ],
- 'condition' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => true,
- ],
- 'sleeve_condition' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'price' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => true,
- ],
- 'comments' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'allow_offers' => [
- 'type' => 'boolean',
- 'location' => 'json',
- 'required' => false,
- ],
- 'status' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => true,
- ],
- 'external_id' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'location' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'weight' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => false,
- ],
- 'format_quantity' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => false,
- ]
- ]
- ],
- 'changeListing' => [
- 'httpMethod' => 'POST',
- 'uri' => '/marketplace/listings/{listing_id}',
- 'summary' => 'Change a Marketplace listing.',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'listing_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true,
- ],
- 'condition' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'sleeve_condition' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'price' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => false,
- ],
- 'comments' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'allow_offers' => [
- 'type' => 'boolean',
- 'location' => 'json',
- 'required' => false,
- ],
- 'status' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'external_id' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'location' => [
- 'type' => 'string',
- 'location' => 'json',
- 'required' => false,
- ],
- 'weight' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => false,
- ],
- 'format_quantity' => [
- 'type' => 'number',
- 'location' => 'json',
- 'required' => false,
- ],
- ],
- ],
- 'deleteListing' => [
- 'httpMethod' => 'DELETE',
- 'uri' => 'marketplace/listings/{listing_id}',
- 'responseModel' => 'GetResponse',
+ 'requiresAuth' => true,
'parameters' => [
- 'listing_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'order_id' => ['required' => true],
+ 'message' => ['required' => false],
+ 'status' => ['required' => false],
+ ],
+ ],
+
+ // ===========================
+ // INVENTORY EXPORT METHODS
+ // ===========================
+ 'inventory.export.create' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'inventory/export',
+ 'requiresAuth' => true,
],
- 'getCollectionFolders' => [
+ 'inventory.export.list' => [
'httpMethod' => 'GET',
- 'uri' => 'users/{username}/collection/folders',
- 'responseModel' => 'GetResponse',
+ 'uri' => 'inventory/export',
+ 'requiresAuth' => true,
+ ],
+ 'inventory.export.get' => [
+ 'httpMethod' => 'GET',
+ 'uri' => 'inventory/export/{export_id}',
+ 'requiresAuth' => true,
'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
+ 'export_id' => ['required' => true],
+ ],
],
- 'getWantlist' => [
+ 'inventory.export.download' => [
'httpMethod' => 'GET',
- 'uri' => 'users/{username}/wants',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getCollectionFolder' => [
+ 'uri' => 'inventory/export/{export_id}/download',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'export_id' => ['required' => true],
+ ],
+ ],
+
+ // ===========================
+ // INVENTORY UPLOAD METHODS
+ // ===========================
+ 'inventory.upload.add' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'inventory/upload/add',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'upload' => ['required' => true],
+ ],
+ ],
+ 'inventory.upload.change' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'inventory/upload/change',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'upload' => ['required' => true],
+ ],
+ ],
+ 'inventory.upload.delete' => [
+ 'httpMethod' => 'POST',
+ 'uri' => 'inventory/upload/delete',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'upload' => ['required' => true],
+ ],
+ ],
+ 'inventory.upload.list' => [
'httpMethod' => 'GET',
- 'uri' => 'users/{username}/collection/folders/{folder_id}',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'folder_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- ]
- ],
- 'getCollectionItemsByFolder' => [
+ 'uri' => 'inventory/upload',
+ 'requiresAuth' => true,
+ ],
+ 'inventory.upload.get' => [
'httpMethod' => 'GET',
- 'uri' => 'users/{username}/collection/folders/{folder_id}/releases',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'folder_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getUserLists' => [
+ 'uri' => 'inventory/upload/{upload_id}',
+ 'requiresAuth' => true,
+ 'parameters' => [
+ 'upload_id' => ['required' => true],
+ ],
+ ],
+
+ // ===========================
+ // USER LISTS METHODS
+ // ===========================
+ 'user.lists' => [
'httpMethod' => 'GET',
'uri' => 'users/{username}/lists',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'username' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ],
- 'per_page' => [
- 'type' => 'integer',
- 'location' => 'query',
- 'required' => false
- ],
- 'page' => [
- 'type' => 'string',
- 'location' => 'query',
- 'required' => false
- ]
- ]
- ],
- 'getLists' => [
+ 'parameters' => [
+ 'username' => ['required' => true],
+ 'per_page' => ['required' => false],
+ 'page' => ['required' => false],
+ ],
+ ],
+ 'list.get' => [
'httpMethod' => 'GET',
'uri' => 'lists/{list_id}',
- 'responseModel' => 'GetResponse',
- 'parameters' => [
- 'list_id' => [
- 'type' => 'string',
- 'location' => 'uri',
- 'required' => true
- ]
- ]
- ]
+ 'parameters' => [
+ 'list_id' => ['required' => true],
+ ],
+ ],
],
- 'models' => [
- 'GetResponse' => [
- 'type' => 'object',
- 'additionalProperties' => [
- 'location' => 'json'
+ 'client' => [
+ 'class' => 'GuzzleHttp\Client',
+ 'options' => [
+ 'base_uri' => 'https://api.discogs.com/',
+ 'timeout' => 30,
+ 'headers' => [
+ 'User-Agent' => 'DiscogsClient/3.0 (+https://github.com/calliostro/php-discogs-api)',
+ 'Accept' => 'application/json',
],
],
- ]
+ ],
];
diff --git a/src/ClientFactory.php b/src/ClientFactory.php
new file mode 100644
index 0000000..ad4ed1d
--- /dev/null
+++ b/src/ClientFactory.php
@@ -0,0 +1,75 @@
+ $options
+ */
+ public static function create(string $userAgent = 'DiscogsClient/3.0 (+https://github.com/calliostro/php-discogs-api)', array $options = []): DiscogsApiClient
+ {
+ $defaultOptions = [
+ 'base_uri' => 'https://api.discogs.com/',
+ 'timeout' => 30,
+ 'headers' => [
+ 'User-Agent' => $userAgent,
+ 'Accept' => 'application/json',
+ ],
+ ];
+
+ $guzzleClient = new Client(array_merge($defaultOptions, $options));
+
+ return new DiscogsApiClient($guzzleClient);
+ }
+
+ /**
+ * Create a Discogs API client with OAuth authentication
+ *
+ * @param array $options
+ */
+ public static function createWithOAuth(string $token, string $tokenSecret, string $userAgent = 'DiscogsClient/3.0 (+https://github.com/calliostro/php-discogs-api)', array $options = []): DiscogsApiClient
+ {
+ $defaultOptions = [
+ 'base_uri' => 'https://api.discogs.com/',
+ 'timeout' => 30,
+ 'headers' => [
+ 'User-Agent' => $userAgent,
+ 'Accept' => 'application/json',
+ 'Authorization' => sprintf('OAuth oauth_token="%s", oauth_token_secret="%s"', $token, $tokenSecret),
+ ],
+ ];
+
+ $guzzleClient = new Client(array_merge($defaultOptions, $options));
+
+ return new DiscogsApiClient($guzzleClient);
+ }
+
+ /**
+ * Create a Discogs API client with personal access token authentication
+ *
+ * @param array $options
+ */
+ public static function createWithToken(string $token, string $userAgent = 'DiscogsClient/3.0 (+https://github.com/calliostro/php-discogs-api)', array $options = []): DiscogsApiClient
+ {
+ $defaultOptions = [
+ 'base_uri' => 'https://api.discogs.com/',
+ 'timeout' => 30,
+ 'headers' => [
+ 'User-Agent' => $userAgent,
+ 'Accept' => 'application/json',
+ 'Authorization' => sprintf('Discogs token=%s', $token),
+ ],
+ ];
+
+ $guzzleClient = new Client(array_merge($defaultOptions, $options));
+
+ return new DiscogsApiClient($guzzleClient);
+ }
+}
diff --git a/src/DiscogsApiClient.php b/src/DiscogsApiClient.php
new file mode 100644
index 0000000..e1c24fa
--- /dev/null
+++ b/src/DiscogsApiClient.php
@@ -0,0 +1,165 @@
+ artistGet(array $params = []) Get artist information — https://www.discogs.com/developers/#page:database,header:database-artist
+ * @method array artistReleases(array $params = []) Get artist releases — https://www.discogs.com/developers/#page:database,header:database-artist-releases
+ * @method array releaseGet(array $params = []) Get release information — https://www.discogs.com/developers/#page:database,header:database-release
+ * @method array releaseRatingGet(array $params = []) Get release rating — https://www.discogs.com/developers/#page:database,header:database-release-rating-by-user
+ * @method array releaseRatingPut(array $params = []) Set release rating — https://www.discogs.com/developers/#page:database,header:database-release-rating-by-user-post
+ * @method array releaseRatingDelete(array $params = []) Delete release rating — https://www.discogs.com/developers/#page:database,header:database-release-rating-by-user-delete
+ * @method array masterGet(array $params = []) Get master release information — https://www.discogs.com/developers/#page:database,header:database-master-release
+ * @method array masterVersions(array $params = []) Get master release versions — https://www.discogs.com/developers/#page:database,header:database-master-release-versions
+ * @method array labelGet(array $params = []) Get label information — https://www.discogs.com/developers/#page:database,header:database-label
+ * @method array labelReleases(array $params = []) Get label releases — https://www.discogs.com/developers/#page:database,header:database-label-releases
+ * @method array search(array $params = []) Search database — https://www.discogs.com/developers/#page:database,header:database-search
+ *
+ * User Identity methods:
+ * @method array identityGet(array $params = []) Get user identity (OAuth required) — https://www.discogs.com/developers/#page:user-identity
+ * @method array userGet(array $params = []) Get user profile — https://www.discogs.com/developers/#page:user-identity,header:user-identity-profile
+ * @method array userEdit(array $params = []) Edit user profile — https://www.discogs.com/developers/#page:user-identity,header:user-identity-profile-post
+ *
+ * Collection methods:
+ * @method array collectionFolders(array $params = []) Get collection folders — https://www.discogs.com/developers/#page:user-collection
+ * @method array collectionFolder(array $params = []) Get a collection folder — https://www.discogs.com/developers/#page:user-collection,header:user-collection-collection-folder
+ * @method array collectionItems(array $params = []) Get collection items by folder — https://www.discogs.com/developers/#page:user-collection,header:user-collection-collection-items-by-folder
+ *
+ * Wantlist methods:
+ * @method array wantlistGet(array $params = []) Get user wantlist — https://www.discogs.com/developers/#page:user-wantlist
+ *
+ * Marketplace methods:
+ * @method array inventoryGet(array $params = []) Get user inventory — https://www.discogs.com/developers/#page:marketplace,header:marketplace-inventory
+ * @method array marketplaceFee(array $params = []) Calculate marketplace fee — https://www.discogs.com/developers/#page:marketplace,header:marketplace-fee
+ * @method array listingGet(array $params = []) Get marketplace listing — https://www.discogs.com/developers/#page:marketplace,header:marketplace-listing
+ * @method array listingCreate(array $params = []) Create marketplace listing (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-new-listing
+ * @method array listingUpdate(array $params = []) Update marketplace listing (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-listing
+ * @method array listingDelete(array $params = []) Delete marketplace listing (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-listing-delete
+ * @method array orderGet(array $params = []) Get order details (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-order
+ * @method array ordersGet(array $params = []) Get orders (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-list-orders
+ * @method array orderUpdate(array $params = []) Update order (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-order-post
+ * @method array orderMessages(array $params = []) Get order messages (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-list-order-messages
+ * @method array orderMessageAdd(array $params = []) Add an order message (OAuth required) — https://www.discogs.com/developers/#page:marketplace,header:marketplace-list-order-messages-post
+ */
+final class DiscogsApiClient
+{
+ private GuzzleClient $client;
+
+ /** @var array */
+ private array $config;
+
+ public function __construct(GuzzleClient $client)
+ {
+ $this->client = $client;
+
+ // Load service configuration
+ $this->config = require __DIR__ . '/../resources/service.php';
+ }
+
+ /**
+ * Magic method to call Discogs API operations
+ *
+ * Examples:
+ * - artistGet(['id' => '108713'])
+ * - search(['q' => 'Nirvana', 'type' => 'artist'])
+ * - releaseGet(['id' => '249504'])
+ *
+ * @param array $arguments
+ * @return array
+ */
+ public function __call(string $method, array $arguments): array
+ {
+ $params = is_array($arguments[0] ?? null) ? $arguments[0] : [];
+
+ return $this->callOperation($method, $params);
+ }
+
+ /**
+ * @param array $params
+ * @return array
+ */
+ private function callOperation(string $method, array $params): array
+ {
+ $operationName = $this->convertMethodToOperation($method);
+
+ if (!isset($this->config['operations'][$operationName])) {
+ throw new \RuntimeException("Unknown operation: $operationName");
+ }
+
+ $operation = $this->config['operations'][$operationName];
+
+ try {
+ $httpMethod = $operation['httpMethod'] ?? 'GET';
+ $uri = $this->buildUri($operation['uri'] ?? '', $params);
+
+ if ($httpMethod === 'POST') {
+ $response = $this->client->post($uri, ['json' => $params]);
+ } elseif ($httpMethod === 'PUT') {
+ $response = $this->client->put($uri, ['json' => $params]);
+ } elseif ($httpMethod === 'DELETE') {
+ $response = $this->client->delete($uri, ['query' => $params]);
+ } else {
+ $response = $this->client->get($uri, ['query' => $params]);
+ }
+
+ $body = $response->getBody()->getContents();
+ $data = json_decode($body, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new \RuntimeException('Invalid JSON response: ' . json_last_error_msg());
+ }
+
+ if (!is_array($data)) {
+ throw new \RuntimeException('Expected array response from API');
+ }
+
+ if (isset($data['error'])) {
+ throw new \RuntimeException($data['message'] ?? 'API Error', $data['error']);
+ }
+
+ return $data;
+ } catch (GuzzleException $e) {
+ throw new \RuntimeException('HTTP request failed: ' . $e->getMessage(), 0, $e);
+ }
+ }
+
+ /**
+ * Convert method name to operation name
+ * artistGet -> artist.get
+ * orderMessages -> order.messages
+ */
+ private function convertMethodToOperation(string $method): string
+ {
+ // Split a camelCase into parts
+ $parts = preg_split('/(?=[A-Z])/', $method, -1, PREG_SPLIT_NO_EMPTY) ?: [];
+
+ if (!$parts) {
+ return $method;
+ }
+
+ // Convert to dot notation
+ return strtolower(implode('.', $parts));
+ }
+
+ /**
+ * Build URI with path parameters
+ *
+ * @param array $params
+ */
+ private function buildUri(string $uri, array $params): string
+ {
+ foreach ($params as $key => $value) {
+ $uri = str_replace('{' . $key . '}', (string) $value, $uri);
+ }
+
+ return ltrim($uri, '/');
+ }
+}
diff --git a/tests/Discogs/Test/ClientFactoryTest.php b/tests/Discogs/Test/ClientFactoryTest.php
deleted file mode 100644
index 4057438..0000000
--- a/tests/Discogs/Test/ClientFactoryTest.php
+++ /dev/null
@@ -1,49 +0,0 @@
- 'php-discogs-api/2.0.0 +https://github.com/calliostro/php-discogs-api'];
- $this->assertSame($default, $client->getHttpClient()->getConfig('headers'));
- }
-
- public function testFactoryWithCustomUserAgent(): void
- {
- $client = ClientFactory::factory([
- 'headers' => ['User-Agent' => 'test'],
- ]);
- $default = ['User-Agent' => 'test'];
- $this->assertSame($default, $client->getHttpClient()->getConfig('headers'));
- }
-
- public function testFactoryWithCustomDefaultNotInClassDefaults(): void
- {
- $client = ClientFactory::factory([
- 'headers' => ['User-Agent' => 'test'],
- 'query' => [
- 'key' => 'my-key',
- 'secret' => 'my-secret',
- ],
- ]);
- $default_headers = ['User-Agent' => 'test'];
- $default_query = [
- 'key' => 'my-key',
- 'secret' => 'my-secret',
- ];
- $this->assertSame($default_headers, $client->getHttpClient()->getConfig('headers'));
- $this->assertSame($default_query, $client->getHttpClient()->getConfig('query'));
- }
-}
diff --git a/tests/Discogs/Test/ClientTest.php b/tests/Discogs/Test/ClientTest.php
deleted file mode 100644
index 605460c..0000000
--- a/tests/Discogs/Test/ClientTest.php
+++ /dev/null
@@ -1,453 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Discogs\Test;
-
-use Discogs\ClientFactory;
-use Discogs\DiscogsClient;
-use GuzzleHttp\Command\Exception\CommandException;
-use GuzzleHttp\Middleware;
-use GuzzleHttp\Handler\MockHandler;
-use GuzzleHttp\HandlerStack;
-use GuzzleHttp\Psr7\Response;
-use PHPUnit\Framework\TestCase;
-
-final class ClientTest extends TestCase
-{
- public function testGetArtist(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_artist', $history);
- $response = $client->getArtist([
- 'id' => 45
- ]);
- $this->assertSame($response['id'], 45);
- $this->assertSame($response['name'], 'Aphex Twin');
- $this->assertSame($response['realname'], 'Richard David James');
- $this->assertIsArray($response['images']);
- $this->assertCount(9, $response['images']);
-
- $this->assertSame('https://api.discogs.com/artists/45', strval($container[0]['request']->getUri()));
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetArtistReleases(): void
- {
-
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_artist_releases', $history);
-
- $response = $client->getArtistReleases([
- 'id' => 45,
- 'per_page' => 50,
- 'page' => 1
- ]);
- $this->assertCount(50, $response['releases']);
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('per_page', $response['pagination']);
-
- $this->assertSame(
- 'https://api.discogs.com/artists/45/releases?per_page=50&page=1',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testSearch(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('search', $history);
-
- $response = $client->search([
- 'q' => 'prodigy',
- 'type' => 'release',
- 'title' => 'the fat of the land',
- 'per_page' => 100,
- 'page' => 3
- ]);
- $this->assertCount(50, $response['results']);
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('per_page', $response['pagination']);
- $this->assertSame(
- 'https://api.discogs.com/database/search?q=prodigy&type=release&title=the%20fat%20of%20the%20land&per_page=100&page=3',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetRelease(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_release', $history);
- $response = $client->getRelease([
- 'id' => 1,
- 'curr_abbr' => 'USD'
- ]);
-
- $this->assertLessThanOrEqual(8.077169493076712, $response['lowest_price']);
- $this->assertSame('Accepted', $response['status']);
- $this->assertArrayHasKey('videos', $response);
- $this->assertCount(6, $response['videos']);
- $this->assertSame(
- 'https://api.discogs.com/releases/1?curr_abbr=USD',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetMaster(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_master', $history);
-
- $response = $client->getMaster([
- 'id' => 33687
- ]);
- $this->assertSame('O Fortuna', $response['title']);
- $this->assertArrayHasKey('tracklist', $response);
- $this->assertCount(2, $response['tracklist']);
- $this->assertSame('https://api.discogs.com/masters/33687', strval($container[0]['request']->getUri()));
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetMasterVersions(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_master_versions', $history);
-
- $response = $client->getMasterVersions([
- 'id' => 33687,
- 'per_page' => 4,
- 'page' => 2
- ]);
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('versions', $response);
- $this->assertCount(4, $response['versions']);
- $this->assertSame(
- 'https://api.discogs.com/masters/33687/versions?per_page=4&page=2',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetLabel(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_label', $history);
- $response = $client->getLabel([
- 'id' => 1
- ]);
- $this->assertArrayHasKey('releases_url', $response);
- $this->assertSame('https://api.discogs.com/labels/1/releases', $response['releases_url']);
- $this->assertArrayHasKey('sublabels', $response);
- $this->assertCount(6, $response['sublabels']);
- $this->assertSame('https://api.discogs.com/labels/1', strval($container[0]['request']->getUri()));
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetLabelReleases(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_label_releases', $history);
- $response = $client->getLabelReleases([
- 'id' => 1,
- 'per_page' => 2,
- 'page' => 1
- ]);
-
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('releases', $response);
- $this->assertCount(2, $response['releases']);
- $this->assertSame(
- 'https://api.discogs.com/labels/1/releases?per_page=2&page=1',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetOAuthIdentity(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_oauth_identity', $history);
- $response = $client->getOAuthIdentity();
-
- $this->assertSame($response['username'], 'R-Search');
- $this->assertSame($response['resource_url'], 'https://api.discogs.com/users/R-Search');
- $this->assertSame($response['consumer_name'], 'RicbraDiscogsBundle');
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetProfile(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_profile', $history);
- $response = $client->getProfile([
- 'username' => 'maxperei'
- ]);
-
- $this->assertEquals(200, $container[0]['response']->getStatusCode());
- $this->assertArrayHasKey('name', $response);
- $this->assertArrayHasKey('avatar_url', $response);
- $this->assertArrayHasKey('home_page', $response);
- $this->assertArrayNotHasKey('email', $response);
- $this->assertSame($response['name'], '∴');
- $this->assertSame(
- $response['avatar_url'],
- 'https://img.discogs.com/mDaw_OUjHspYLj77C_tcobr2eXc=/500x500/filters:strip_icc():format(jpeg):quality(40)/discogs-avatars/U-1861520-1498224434.jpeg.jpg'
- );
- $this->assertSame($response['home_page'], 'http://maxperei.info');
- $this->assertSame('https://api.discogs.com/users/maxperei', strval($container[0]['request']->getUri()));
- }
-
- public function testGetInventory(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_inventory', $history);
- $response = $client->getInventory([
- 'username' => '360vinyl',
- 'sort' => 'price',
- 'sort_order' => 'asc'
- ]);
-
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('listings', $response);
- $this->assertCount(50, $response['listings']);
- $this->assertSame('GET', $container[0]['request']->getMethod());
- $this->assertSame(
- 'https://api.discogs.com/users/360vinyl/inventory?sort=price&sort_order=asc',
- strval($container[0]['request']->getUri())
- );
- }
-
- public function testGetOrders(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_orders', $history);
- $response = $client->getOrders([
- 'status' => 'New Order',
- 'sort' => 'price',
- 'sort_order' => 'asc'
- ]);
-
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('orders', $response);
- $this->assertCount(1, $response['orders']);
- $this->assertSame('GET', $container[0]['request']->getMethod());
- $this->assertSame(
- 'https://api.discogs.com/marketplace/orders?status=New%20Order&sort=price&sort_order=asc',
- strval($container[0]['request']->getUri())
- );
- }
-
- public function testGetOrder(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_order', $history);
- $response = $client->getOrder([
- 'order_id' => '1-1'
- ]);
-
- $this->assertArrayHasKey('id', $response);
- $this->assertArrayHasKey('resource_url', $response);
- $this->assertArrayHasKey('messages_url', $response);
- $this->assertArrayHasKey('uri', $response);
- $this->assertArrayHasKey('status', $response);
- $this->assertArrayHasKey('next_status', $response);
- $this->assertArrayHasKey('fee', $response);
- $this->assertArrayHasKey('created', $response);
- $this->assertArrayHasKey('shipping', $response);
- $this->assertArrayHasKey('shipping_address', $response);
- $this->assertArrayHasKey('additional_instructions', $response);
- $this->assertArrayHasKey('seller', $response);
- $this->assertArrayHasKey('last_activity', $response);
- $this->assertArrayHasKey('buyer', $response);
- $this->assertArrayHasKey('total', $response);
- $this->assertCount(1, $response['items']);
- $this->assertSame('GET', $container[0]['request']->getMethod());
- $this->assertSame('https://api.discogs.com/marketplace/orders/1-1', strval($container[0]['request']->getUri()));
- }
-
- public function testChangeOrder(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('change_order', $history);
- $client->changeOrder([
- 'order_id' => '1-1',
- 'shipping' => 5.0
- ]);
-
- $this->assertSame('POST', $container[0]['request']->getMethod());
- $this->assertSame('https://api.discogs.com/marketplace/orders/1-1', strval($container[0]['request']->getUri()));
- }
-
- public function testCreateListingValidation(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $this->expectException(CommandException::class);
- $client = $this->createClient('create_listing', $history);
- $client->createListing([
- 'release_id' => '1',
- 'condition' => 'Mint (M)',
- 'price' => 5.90
- ]);
- }
-
- public function testCreateListing(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('create_listing', $history);
- $client->createListing([
- 'release_id' => '1',
- 'condition' => 'Mint (M)',
- 'status' => 'For Sale',
- 'price' => 5.90
- ]);
-
- $this->assertSame('POST', $container[0]['request']->getMethod());
- $this->assertSame('https://api.discogs.com/marketplace/listings', strval($container[0]['request']->getUri()));
- }
-
- public function testChangeListing(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('change_listing', $history);
- $client->changeListing([
- 'listing_id' => '123',
- 'condition' => 'Mint (M)',
- 'price' => 4.90
- ]);
-
- $this->assertSame('POST', $container[0]['request']->getMethod());
- $this->assertSame(
- 'https://api.discogs.com/marketplace/listings/123',
- strval($container[0]['request']->getUri())
- );
- }
-
- public function testDeleteListing(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('delete_listing', $history);
- $client->deleteListing([
- 'listing_id' => '129242581'
- ]);
-
- $this->assertSame('DELETE', $container[0]['request']->getMethod());
- $this->assertSame(
- 'https://api.discogs.com/marketplace/listings/129242581',
- strval($container[0]['request']->getUri())
- );
- }
-
- public function testGetCollectionFolders(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_collection_folders', $history);
- $response = $client->getCollectionFolders([
- 'username' => 'example'
- ]);
-
- $this->assertIsArray($response['folders']);
- $this->assertCount(2, $response['folders']);
-
- $this->assertSame(
- 'https://api.discogs.com/users/example/collection/folders',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetCollectionFolder(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_collection_folder', $history);
- $response = $client->getCollectionFolder([
- 'username' => 'example',
- 'folder_id' => 1
- ]);
-
- $this->assertSame($response['id'], 1);
- $this->assertSame($response['count'], 20);
- $this->assertSame($response['name'], 'Uncategorized');
- $this->assertSame($response['resource_url'], "https://api.discogs.com/users/example/collection/folders/1");
-
- $this->assertSame(
- 'https://api.discogs.com/users/example/collection/folders/1',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- public function testGetCollectionItemsByFolder(): void
- {
- $container = [];
- $history = Middleware::History($container);
- $client = $this->createClient('get_collection_items_by_folder', $history);
- $response = $client->getCollectionItemsByFolder([
- 'username' => 'rodneyfool',
- 'folder_id' => 3,
- 'sort' => 'artist',
- 'sort_order' => 'desc',
- 'per_page' => 50,
- 'page' => 1
- ]);
-
- $this->assertCount(1, $response['releases']);
- $this->assertArrayHasKey('pagination', $response);
- $this->assertArrayHasKey('per_page', $response['pagination']);
-
- $this->assertSame(
- 'https://api.discogs.com/users/rodneyfool/collection/folders/3/releases?per_page=50&page=1',
- strval($container[0]['request']->getUri())
- );
- $this->assertSame('GET', $container[0]['request']->getMethod());
- }
-
- protected function createClient(string $mock, callable $history): DiscogsClient
- {
- $json = file_get_contents(__DIR__ . "/../../fixtures/$mock.json");
- $data = json_decode($json, true);
- $data['body'] = json_encode($data['body']);
- $mock = new MockHandler([
- new Response(
- $data['status'],
- $data['headers'],
- $data['body'],
- $data['version'],
- $data['reason']
- )
- ]);
- $handler = HandlerStack::create($mock);
- $handler->push($history);
- $client = ClientFactory::factory(['handler' => $handler]);
-
- return $client;
- }
-}
diff --git a/tests/Discogs/Test/Subscriber/ThrottleSubscriberTest.php b/tests/Discogs/Test/Subscriber/ThrottleSubscriberTest.php
deleted file mode 100644
index b4b55e7..0000000
--- a/tests/Discogs/Test/Subscriber/ThrottleSubscriberTest.php
+++ /dev/null
@@ -1,102 +0,0 @@
-assertInstanceOf(ThrottleSubscriber::class, $throttle);
- }
-
- public function testWithThrottle(): void
- {
- $throttle = 2000; // milliseconds == 2 sec
- $subscriber = new ThrottleSubscriber($throttle);
-
- $before = microtime(true);
-
- $handler = HandlerStack::create(new MockHandler([
- new Response(429),
- new Response(200)
- ]));
- $handler->push(Middleware::retry($subscriber->decider(), $subscriber->delay()));
- $client = new Client(['handler' => $handler]);
- $client->request('GET', '/');
-
- $after = microtime(true);
-
- $difference = $after - $before;
- // Should be at least 2 seconds (with exponential backoff: 2000ms * 2^1 = 4000ms)
- // Allow some tolerance for system variations (3.5 seconds minimum)
- $this->assertGreaterThan(3.5, $difference, 'Throttling should delay at least 3.5 seconds');
- }
-
- public function testWithoutThrottle(): void
- {
- $throttle = 0;
- $subscriber = new ThrottleSubscriber($throttle);
-
- $before = microtime(true);
-
- $handler = HandlerStack::create(new MockHandler([
- new Response(429),
- new Response(200)
- ]));
- $handler->push(Middleware::retry($subscriber->decider(), $subscriber->delay()));
- $client = new Client(['handler' => $handler]);
- $client->request('GET', '/');
-
- $after = microtime(true);
-
- $difference = $after - $before;
- // Should be at max 0.5 seconds on a very slow system, tricky to test
- $this->assertTrue($difference < 0.5);
- }
-
- public function testMaxRetries(): void
- {
- $throttle = 500;
- $max_retries = 2;
- $subscriber = new ThrottleSubscriber($throttle, $max_retries);
-
- $before = microtime(true);
-
- $handler = HandlerStack::create(new MockHandler([
- new Response(429),
- new Response(429),
- new Response(429),
- ]));
- $handler->push(Middleware::retry($subscriber->decider(), $subscriber->delay()));
- $client = new Client(['handler' => $handler]);
- try {
- $client->request('GET', '/');
- } catch (Exception $e) {
- $this->assertInstanceOf(ClientException::class, $e);
- $this->assertEquals(429, $e->getCode());
- }
-
- $after = microtime(true);
- $difference = $after - $before;
-
- $this->assertTrue($difference > 3);
- }
-}
diff --git a/tests/Integration/ClientWorkflowTest.php b/tests/Integration/ClientWorkflowTest.php
new file mode 100644
index 0000000..5f38f91
--- /dev/null
+++ b/tests/Integration/ClientWorkflowTest.php
@@ -0,0 +1,155 @@
+ $data
+ */
+ private function jsonEncode(array $data): string
+ {
+ return json_encode($data) ?: '{}';
+ }
+
+ public function testCompleteWorkflowWithFactoryAndApiCalls(): void
+ {
+ // Create a mock handler with multiple responses
+ $mockHandler = new MockHandler([
+ new Response(200, [], $this->jsonEncode(['id' => '108713', 'name' => 'Aphex Twin'])),
+ new Response(200, [], $this->jsonEncode(['results' => [['title' => 'Selected Ambient Works']]])),
+ new Response(200, [], $this->jsonEncode(['id' => '1', 'name' => 'Warp Records'])),
+ ]);
+
+ $handlerStack = HandlerStack::create($mockHandler);
+ $guzzleClient = new Client(['handler' => $handlerStack]);
+
+ // Create a client using factory with a custom Guzzle client
+ $client = new \Calliostro\Discogs\DiscogsApiClient($guzzleClient);
+
+ // Test multiple API calls
+ $artist = $client->artistGet(['id' => '108713']);
+ $this->assertEquals('Aphex Twin', $artist['name']);
+
+ $search = $client->search(['q' => 'Aphex Twin', 'type' => 'artist']);
+ $this->assertArrayHasKey('results', $search);
+
+ $label = $client->labelGet(['id' => '1']);
+ $this->assertEquals('Warp Records', $label['name']);
+ }
+
+ public function testFactoryCreatesWorkingClients(): void
+ {
+ // Test regular factory method
+ $client1 = ClientFactory::create();
+ $this->assertInstanceOf(\Calliostro\Discogs\DiscogsApiClient::class, $client1);
+
+ // Test OAuth factory method
+ $client2 = ClientFactory::createWithOAuth('token', 'secret');
+ $this->assertInstanceOf(\Calliostro\Discogs\DiscogsApiClient::class, $client2);
+
+ // Test token factory method
+ $client3 = ClientFactory::createWithToken('personal_token');
+ $this->assertInstanceOf(\Calliostro\Discogs\DiscogsApiClient::class, $client3);
+ }
+
+ public function testServiceConfigurationIsLoaded(): void
+ {
+ $client = ClientFactory::create();
+
+ // This will fail if service.php is not properly loaded.
+ // We use reflection to check the config was loaded
+ $reflection = new \ReflectionClass($client);
+ $configProperty = $reflection->getProperty('config');
+ $configProperty->setAccessible(true);
+ $config = $configProperty->getValue($client);
+
+ $this->assertIsArray($config);
+ $this->assertArrayHasKey('operations', $config);
+ $this->assertArrayHasKey('artist.get', $config['operations']);
+ $this->assertArrayHasKey('search', $config['operations']);
+ }
+
+ public function testMethodNameToOperationConversion(): void
+ {
+ // Create a client with a mock that we'll never use
+ // We just want to test the method name conversion
+ $mockHandler = new MockHandler();
+ $handlerStack = HandlerStack::create($mockHandler);
+ $guzzleClient = new Client(['handler' => $handlerStack]);
+ $client = new \Calliostro\Discogs\DiscogsApiClient($guzzleClient);
+
+ // Use reflection to test the private method
+ $reflection = new \ReflectionClass($client);
+ $method = $reflection->getMethod('convertMethodToOperation');
+ $method->setAccessible(true);
+
+ // Test various conversions
+ $this->assertEquals('artist.get', $method->invokeArgs($client, ['artistGet']));
+ $this->assertEquals('artist.releases', $method->invokeArgs($client, ['artistReleases']));
+ $this->assertEquals('collection.folders', $method->invokeArgs($client, ['collectionFolders']));
+ $this->assertEquals('order.messages', $method->invokeArgs($client, ['orderMessages']));
+ $this->assertEquals('order.message.add', $method->invokeArgs($client, ['orderMessageAdd']));
+ }
+
+ public function testUriBuilding(): void
+ {
+ // Create a client to test URI building
+ $mockHandler = new MockHandler();
+ $handlerStack = HandlerStack::create($mockHandler);
+ $guzzleClient = new Client(['handler' => $handlerStack]);
+ $client = new \Calliostro\Discogs\DiscogsApiClient($guzzleClient);
+
+ // Use reflection to test the private method
+ $reflection = new \ReflectionClass($client);
+ $method = $reflection->getMethod('buildUri');
+ $method->setAccessible(true);
+
+ // Test URI building with parameters
+ $uri = $method->invokeArgs($client, ['artists/{id}', ['id' => '108713']]);
+ $this->assertEquals('artists/108713', $uri);
+
+ $uri = $method->invokeArgs($client, ['users/{username}/collection/folders/{folder_id}/releases', [
+ 'username' => 'testuser',
+ 'folder_id' => '0',
+ ]]);
+ $this->assertEquals('users/testuser/collection/folders/0/releases', $uri);
+ }
+
+ public function testErrorHandlingInCompleteWorkflow(): void
+ {
+ // Create mock handler with error response
+ $mockHandler = new MockHandler([
+ new Response(404, [], $this->jsonEncode([
+ 'error' => 404,
+ 'message' => 'Artist not found',
+ ])),
+ ]);
+
+ $handlerStack = HandlerStack::create($mockHandler);
+ $guzzleClient = new Client(['handler' => $handlerStack]);
+ $client = new \Calliostro\Discogs\DiscogsApiClient($guzzleClient);
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Artist not found');
+
+ $client->artistGet(['id' => '999999']);
+ }
+}
diff --git a/tests/Unit/ClientFactoryTest.php b/tests/Unit/ClientFactoryTest.php
new file mode 100644
index 0000000..f61f9c1
--- /dev/null
+++ b/tests/Unit/ClientFactoryTest.php
@@ -0,0 +1,80 @@
+assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+
+ public function testCreateWithOAuthReturnsDiscogsApiClient(): void
+ {
+ $client = ClientFactory::createWithOAuth('token', 'secret');
+
+ $this->assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+
+ public function testCreateWithTokenReturnsDiscogsApiClient(): void
+ {
+ $client = ClientFactory::createWithToken('personal_access_token');
+
+ $this->assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+
+ public function testCreateWithCustomUserAgentReturnsDiscogsApiClient(): void
+ {
+ $client = ClientFactory::create('CustomApp/1.0');
+
+ $this->assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+
+ public function testCreateWithAllParametersReturnsDiscogsApiClient(): void
+ {
+ $options = ['timeout' => 60];
+ $client = ClientFactory::create('CustomApp/1.0', $options);
+
+ $this->assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+
+ public function testCreateWithOAuthAndAllParameters(): void
+ {
+ $token = 'test_access_token';
+ $tokenSecret = 'test_access_token_secret';
+ $userAgent = 'CustomApp/1.0';
+ $options = ['timeout' => 60];
+
+ $client = ClientFactory::createWithOAuth(
+ $token,
+ $tokenSecret,
+ $userAgent,
+ $options
+ );
+
+ $this->assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+
+ public function testCreateWithTokenAndAllParameters(): void
+ {
+ $token = 'test_personal_token';
+ $userAgent = 'CustomApp/1.0';
+ $options = ['timeout' => 60];
+
+ $client = ClientFactory::createWithToken($token, $userAgent, $options);
+
+ $this->assertInstanceOf(DiscogsApiClient::class, $client);
+ }
+}
diff --git a/tests/Unit/DiscogsApiClientTest.php b/tests/Unit/DiscogsApiClientTest.php
new file mode 100644
index 0000000..7817e97
--- /dev/null
+++ b/tests/Unit/DiscogsApiClientTest.php
@@ -0,0 +1,451 @@
+mockHandler = new MockHandler();
+ $handlerStack = HandlerStack::create($this->mockHandler);
+ $guzzleClient = new Client(['handler' => $handlerStack]);
+
+ $this->client = new DiscogsApiClient($guzzleClient);
+ }
+
+ /**
+ * Helper method to safely encode JSON for Response body
+ *
+ * @param array $data
+ */
+ private function jsonEncode(array $data): string
+ {
+ return json_encode($data) ?: '{}';
+ }
+
+ public function testArtistGetMethodCallsCorrectEndpoint(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['id' => '108713', 'name' => 'Aphex Twin']))
+ );
+
+ $result = $this->client->artistGet(['id' => '108713']);
+
+ $this->assertEquals(['id' => '108713', 'name' => 'Aphex Twin'], $result);
+ }
+
+ public function testSearchMethodCallsCorrectEndpoint(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['results' => [['title' => 'Nirvana - Nevermind']]]))
+ );
+
+ $result = $this->client->search(['q' => 'Nirvana', 'type' => 'release']);
+
+ $this->assertEquals(['results' => [['title' => 'Nirvana - Nevermind']]], $result);
+ }
+
+ public function testReleaseGetMethodCallsCorrectEndpoint(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['id' => 249504, 'title' => 'Nevermind']))
+ );
+
+ $result = $this->client->releaseGet(['id' => '249504']);
+
+ $this->assertEquals(['id' => 249504, 'title' => 'Nevermind'], $result);
+ }
+
+ public function testMethodNameConversionWorks(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['id' => '1', 'name' => 'Warp Records']))
+ );
+
+ $result = $this->client->labelGet(['id' => '1']);
+
+ $this->assertEquals(['id' => '1', 'name' => 'Warp Records'], $result);
+ }
+
+ public function testUnknownOperationThrowsException(): void
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Unknown operation: unknown.method');
+
+ // @phpstan-ignore-next-line - Testing invalid method call
+ $this->client->unknownMethod();
+ }
+
+ public function testInvalidJsonResponseThrowsException(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], 'invalid json')
+ );
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Invalid JSON response:');
+
+ $this->client->artistGet(['id' => '108713']);
+ }
+
+ public function testApiErrorResponseThrowsException(): void
+ {
+ $this->mockHandler->append(
+ new Response(400, [], $this->jsonEncode([
+ 'error' => 400,
+ 'message' => 'Bad Request: Invalid ID',
+ ]))
+ );
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Bad Request: Invalid ID');
+
+ $this->client->artistGet(['id' => 'invalid']);
+ }
+
+ public function testApiErrorResponseWithoutMessageThrowsException(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode([
+ 'error' => 400,
+ // No 'message' field, should use default 'API Error'
+ ]))
+ );
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('API Error');
+
+ $this->client->artistGet(['id' => '123']);
+ }
+
+ public function testComplexMethodNameConversion(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['messages' => []]))
+ );
+
+ $result = $this->client->orderMessages(['order_id' => '123']);
+
+ $this->assertEquals(['messages' => []], $result);
+ }
+
+ public function testCollectionItemsMethod(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['releases' => []]))
+ );
+
+ $result = $this->client->collectionItems(['username' => 'user', 'folder_id' => '0']);
+
+ $this->assertEquals(['releases' => []], $result);
+ }
+
+ public function testPostMethodWithJsonPayload(): void
+ {
+ $this->mockHandler->append(
+ new Response(201, [], $this->jsonEncode(['listing_id' => '12345']))
+ );
+
+ $result = $this->client->listingCreate([
+ 'release_id' => '249504',
+ 'condition' => 'Mint (M)',
+ 'price' => '25.00',
+ 'status' => 'For Sale',
+ ]);
+
+ $this->assertEquals(['listing_id' => '12345'], $result);
+ }
+
+ public function testReleaseRatingGetMethod(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['username' => 'testuser', 'release_id' => 249504, 'rating' => 5]))
+ );
+
+ $result = $this->client->releaseRatingGet(['release_id' => 249504, 'username' => 'testuser']);
+
+ $this->assertEquals(['username' => 'testuser', 'release_id' => 249504, 'rating' => 5], $result);
+ }
+
+ public function testCollectionFoldersGet(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode([
+ 'folders' => [
+ ['id' => 0, 'name' => 'All', 'count' => 23],
+ ['id' => 1, 'name' => 'Uncategorized', 'count' => 20],
+ ],
+ ]))
+ );
+
+ $result = $this->client->collectionFolders(['username' => 'testuser']);
+
+ $this->assertArrayHasKey('folders', $result);
+ $this->assertCount(2, $result['folders']);
+ }
+
+ public function testWantlistGet(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode([
+ 'wants' => [
+ ['id' => 1867708, 'rating' => 4, 'basic_information' => ['title' => 'Year Zero']],
+ ],
+ ]))
+ );
+
+ $result = $this->client->wantlistGet(['username' => 'testuser']);
+
+ $this->assertArrayHasKey('wants', $result);
+ $this->assertCount(1, $result['wants']);
+ }
+
+ public function testMarketplaceFeeCalculation(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['value' => 0.42, 'currency' => 'USD']))
+ );
+
+ $result = $this->client->marketplaceFee(['price' => 10.00]);
+
+ $this->assertEquals(['value' => 0.42, 'currency' => 'USD'], $result);
+ }
+
+ public function testListingGetMethod(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode([
+ 'id' => 172723812,
+ 'status' => 'For Sale',
+ 'price' => ['currency' => 'USD', 'value' => 120],
+ ]))
+ );
+
+ $result = $this->client->listingGet(['listing_id' => 172723812]);
+
+ $this->assertEquals(172723812, $result['id']);
+ $this->assertEquals('For Sale', $result['status']);
+ }
+
+ public function testUserEdit(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['success' => true, 'username' => 'testuser']))
+ );
+
+ $result = $this->client->userEdit([
+ 'username' => 'testuser',
+ 'name' => 'Test User',
+ 'location' => 'Test City',
+ ]);
+
+ $this->assertEquals(['success' => true, 'username' => 'testuser'], $result);
+ }
+
+ public function testPutMethodHandling(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['rating' => 5, 'release_id' => 249504]))
+ );
+
+ $result = $this->client->releaseRatingPut([
+ 'release_id' => 249504,
+ 'username' => 'testuser',
+ 'rating' => 5,
+ ]);
+
+ $this->assertEquals(['rating' => 5, 'release_id' => 249504], $result);
+ }
+
+ public function testDeleteMethodHandling(): void
+ {
+ $this->mockHandler->append(
+ new Response(204, [], '{}')
+ );
+
+ $result = $this->client->releaseRatingDelete([
+ 'release_id' => 249504,
+ 'username' => 'testuser',
+ ]);
+
+ $this->assertEquals([], $result);
+ }
+
+ public function testHttpExceptionHandling(): void
+ {
+ $this->mockHandler->append(
+ new \GuzzleHttp\Exception\RequestException(
+ 'Connection failed',
+ new \GuzzleHttp\Psr7\Request('GET', 'test')
+ )
+ );
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('HTTP request failed: Connection failed');
+
+ $this->client->artistGet(['id' => '123']);
+ }
+
+ public function testNonArrayResponseHandling(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], '"not an array"')
+ );
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Expected array response from API');
+
+ $this->client->artistGet(['id' => '123']);
+ }
+
+ public function testUriBuilding(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['id' => 123, 'name' => 'Test Artist']))
+ );
+
+ $result = $this->client->artistGet(['id' => 123]);
+
+ $this->assertEquals(['id' => 123, 'name' => 'Test Artist'], $result);
+ }
+
+ public function testComplexMethodNameConversionWithMultipleParts(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['messages' => []]))
+ );
+
+ $result = $this->client->orderMessageAdd([
+ 'order_id' => '123-456',
+ 'message' => 'Test message',
+ ]);
+
+ $this->assertEquals(['messages' => []], $result);
+ }
+
+ public function testEmptyParametersHandling(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['results' => []]))
+ );
+
+ // Test calling without parameters
+ $result = $this->client->search();
+
+ $this->assertEquals(['results' => []], $result);
+ }
+
+ public function testConvertMethodToOperationWithEmptyString(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['success' => true]))
+ );
+
+ // This will call the protected convertMethodToOperation indirectly
+ // by testing edge cases in method name conversion
+ try {
+ // @phpstan-ignore-next-line - Testing invalid method call
+ $this->client->testMethodName();
+ } catch (\RuntimeException $e) {
+ $this->assertStringContainsString('Unknown operation', $e->getMessage());
+ }
+ }
+
+ public function testBuildUriWithNoParameters(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['results' => []]))
+ );
+
+ // Test URI building with no parameters (should not replace anything)
+ $result = $this->client->search(['q' => 'test']);
+
+ $this->assertEquals(['results' => []], $result);
+ }
+
+ public function testMethodCallWithNullParameters(): void
+ {
+ $this->mockHandler->append(
+ new Response(200, [], $this->jsonEncode(['success' => true]))
+ );
+
+ // Test method call with null as parameters - should be converted to empty array
+ // @phpstan-ignore-next-line - Testing parameter validation
+ $result = $this->client->search(null);
+
+ $this->assertEquals(['success' => true], $result);
+ }
+
+ public function testConvertMethodToOperationWithEdgeCases(): void
+ {
+ // Test the convertMethodToOperation method with edge cases
+ $reflection = new \ReflectionClass($this->client);
+ $method = $reflection->getMethod('convertMethodToOperation');
+ $method->setAccessible(true);
+
+ // Test with empty string (should return empty string)
+ $result = $method->invokeArgs($this->client, ['']);
+ $this->assertEquals('', $result);
+
+ // Test with a single lowercase word
+ $result = $method->invokeArgs($this->client, ['test']);
+ $this->assertEquals('test', $result);
+
+ // Test with mixed case scenarios
+ $result = $method->invokeArgs($this->client, ['ArtistGetReleases']);
+ $this->assertEquals('artist.get.releases', $result);
+ }
+
+ public function testBuildUriWithComplexParameters(): void
+ {
+ // Test the buildUri method directly with complex scenarios
+ $reflection = new \ReflectionClass($this->client);
+ $method = $reflection->getMethod('buildUri');
+ $method->setAccessible(true);
+
+ // Test with leading slash
+ $result = $method->invokeArgs($this->client, ['/artists/{id}/releases', ['id' => '123']]);
+ $this->assertEquals('artists/123/releases', $result);
+
+ // Test with no parameters to replace
+ $result = $method->invokeArgs($this->client, ['artists', []]);
+ $this->assertEquals('artists', $result);
+
+ // Test with multiple parameters
+ $result = $method->invokeArgs($this->client, ['/users/{username}/collection/folders/{folder_id}', [
+ 'username' => 'testuser',
+ 'folder_id' => '1',
+ 'extra' => 'ignored', // Should be ignored
+ ]]);
+ $this->assertEquals('users/testuser/collection/folders/1', $result);
+ }
+
+ public function testPregSplitEdgeCaseHandling(): void
+ {
+ // Test case where preg_split might return false - this might be the missing line
+ $reflection = new \ReflectionClass($this->client);
+ $method = $reflection->getMethod('convertMethodToOperation');
+ $method->setAccessible(true);
+
+ // Test with a long method name (100 characters) to potentially trigger edge cases
+ $longMethodName = str_repeat('A', 100) . 'Get';
+ $result = $method->invokeArgs($this->client, [$longMethodName]);
+ // This should still work, converting the long name properly
+ $this->assertIsString($result);
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
deleted file mode 100644
index 569f3e2..0000000
--- a/tests/bootstrap.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-require_once __DIR__.'/../vendor/autoload.php';