Thanks for considering a contribution! This guide covers the local workflow and the bar this project holds itself to.
You will need the following tools installed on your system to run the full suite of checks:
- PHP 8.3+ and Composer
- Lefthook: Git hooks manager.
- markdownlint-cli2: Markdown linter.
- yamllint: YAML linter.
- Qlty: Code quality toolkit.
On macOS, you can install most of these via Homebrew:
brew install lefthook markdownlint-cli2 yamllint
curl -L https://qlty.sh/get | shgit clone https://github.com/otherguy/php-currency-api.git
cd php-currency-api
composer install
lefthook installcomposer check runs the same gates as CI (Pint, PHPStan, Rector, PHPUnit). It should be green before you push.
| Command | What it does |
|---|---|
composer test |
PHPUnit |
composer test:coverage |
PHPUnit with coverage (requires pcov or Xdebug) |
composer lint |
Pint check (read-only) |
composer lint:fix |
Pint apply |
composer analyse |
PHPStan at level: max |
composer rector |
Rector dry-run |
composer rector:fix |
Rector apply |
composer check |
All four PHP checks in order |
markdownlint-cli2 |
Lint all Markdown files |
yamllint . |
Lint all YAML files |
qlty check |
Run Qlty quality checks |
- PHP 8.3+,
declare(strict_types=1)in every file. - PSR-12 with 4-space indentation. Pint enforces this; don't fight it.
- Real types over
@vardocblocks. Constructor property promotion is preferred when it reads naturally. - Names describe behavior, not implementation. No
Manager,Wrapper,Helperunless it genuinely is one. - Comments are evergreen — explain why the code looks weird, not what it does, and never reference past versions of the code.
-
Tests live under
tests/, namespacedOtherguy\Currency\Tests\…. -
Use PHPUnit
#[Test]attributes (no/** @test */). -
HTTP is mocked with
tests/Support/MockHttpClient.php, an in-process PSR-18 double. Build drivers viatests/Support/DriverHarness.php:$harness = new DriverHarness(); $harness->http->enqueue(JsonResponse::ok('{"success":true,"rates":{"EUR":0.92}}')); $driver = $harness->make('fixerio'); $result = $driver->accessKey('key')->from('USD')->to('EUR')->get(); $this->assertSame('0.92', (string) $result->rate(Currency::EUR)); $this->assertStringContainsString('access_key=key', $harness->http->lastRequest()->getUri()->getQuery());
-
Coverage target: ≥ 98% on
src/. New code without tests is unlikely to be merged. -
Tests must exercise real code paths. Don't write tests that only verify mock behavior.
composer test:coverage (and the vendor/bin/phpunit invocation in CI) needs a coverage driver loaded — without one, the suite reports No tests executed! because phpunit.xml has failOnWarning="true". Two options:
-
pcov (recommended — faster, coverage-only):
brew install shivammathur/extensions/pcov@8.5 # match your PHP version # or, if shivammathur tap is unreachable: pecl install pcov
-
Xdebug (richer features, slower):
pecl install xdebug
Verify with php -m | grep -iE 'pcov|xdebug'. CI installs Xdebug on the PHP 8.3 leg via shivammathur/setup-php.
- PHPStan runs at
level: max. If you hit a genuinemixedfrom upstream JSON, prefer narrowing with assertions or specific type guards. The existingignoreErrorsblock inphpstan.neonis scoped tosrc/Drivers/*.phpfor unverifiable provider responses — please don't widen it. - Rector checks are advisory in CI but blocking on PR. If Rector suggests a rewrite that loses meaning, exclude the rule rather than ignoring the diff.
See the driver guide in the root README for the full walkthrough. The short version:
- Extend
BaseCurrencyDriver, set$apiURL,$protocol, default$baseCurrency. - Implement
get(),historical(),convert()against the provider's endpoints. - Override
apiRequest()only if the provider's error envelope differs from raw HTTP failures. - Register in
DriverFactory's built-in map (or expose viaregister()for third-party drivers). - Add tests under
tests/Drivers/usingDriverHarnessandMockHttpClient.
-
composer checkis green locally. - New behavior has tests.
- Public API changes are documented in
README.mdand listed inCHANGELOG.mdunder## [Unreleased]. - BC breaks include a README upgrade note with a before/after snippet.
- Commit messages are descriptive (
fix:/feat:/chore:prefixes are welcome but not required).
- Bump the version in the relevant
CHANGELOG.mdheading and move[Unreleased]items under it. - Tag:
git tag -s vX.Y.Z -m "Release X.Y.Z". - Push:
git push origin main --tags. - Create a GitHub release pasting the changelog entry.
- Packagist auto-syncs; verify the new version appears.
When filing a bug, please include:
- PHP version (
php -v). - Library version (
composer show otherguy/php-currency-api). - The PSR-18 client you're using.
- A minimal reproduction (driver, fluent chain, observed vs. expected).
- The full exception trace if any.
Thanks again — this library is healthier with every contribution.