mirror of
https://github.com/ivuorinen/business-data-fetcher.git
synced 2026-03-12 10:58:55 +00:00
feat!: migrate to Valinor DTOs, add v3 YTJ API client, modernize tooling (#13)
* feat!: moved v1 api under src/v1 BREAKING CHANGE: please update your namespaces if you use any of the classes directly or composer doesn't like the namespace change. * feat!: migrate from spatie/dto to valinor, add pest and rector BREAKING CHANGE: replaced spatie/data-transfer-object with cuyz/valinor, upgraded phpcs to v4, added pestphp/pest and rector/rector. Removed ivuorinen/markdowndocs due to symfony/console conflict. * feat: add shared HTTP AbstractClient with Valinor mapper Base class providing Guzzle HTTP client and Valinor TreeMapper with allowSuperfluousKeys() for PRH API response hydration. * refactor!: migrate v1 DTOs to final readonly with Valinor hydration BREAKING CHANGE: all v1 DTOs are now final readonly classes with constructor promotion. Traits are method-only (properties removed). HasVersion trait deleted. BusinessDataFetcher extends AbstractClient. * feat: add v3 YTJ API client with DTOs New client for PRH opendata-ytj-api v3 with full DTO coverage matching the v3 OpenAPI schema. * test: add Pest test suite for v1 and v3 Unit tests for all v1 DTOs, traits, and BusinessDataFetcher client. Unit tests for v3 Client and Company DTO hydration. Includes JSON fixtures for realistic response testing. * chore: add Rector config with deadCode, codeQuality, typeDeclarations * ci: update workflows to use ivuorinen/actions, pin SHAs, add PHP 8.4 Migrated from ivuorinen/.github reusable workflows to ivuorinen/actions composite actions with pinned commit SHAs. Added PHP 8.4 to test matrix. Updated renovate config preset path. Added labels.yml for sync-labels workflow. * chore: clean up .gitignore for PHP-only project Removed irrelevant entries for Node.js, Next.js, Nuxt, Laravel, Vagrant, Android/Crashlytics, CMake, and other unused ecosystems. * docs: update README, add CHANGELOG, CONTRIBUTING, and CLAUDE.md Rewrote README with v1/v3 usage examples and badges. Added CHANGELOG, CONTRIBUTING guide, and CLAUDE.md project instructions. Added .claude/ settings for Claude Code integration. * chore(deps): update composer.lock * chore: add CaptainHook with conventional commits and secrets detection Added captainhook/captainhook, captainhook/hook-installer, captainhook/secrets, and ramsey/conventional-commits. Hooks configured: - pre-commit: secrets check, lint-fix, test - commit-msg: conventional commit validation - post-change: composer install on lock/json changes * fix(deps): regenerate composer.lock with PHP 8.2 compatibility Downgraded symfony packages from v8 to v7.4 to support the full PHP 8.2/8.3/8.4 test matrix. * fix: address PR #13 code review feedback - Pin captainhook versions (^5.0, ^1.0) instead of wildcard - Add ext-json to composer.json require block - Fix v3 Client base URI to avoid duplicate path prefix - Handle null language in HasLanguage trait - Remove empty-string defaults from structural DTO fields - Remove branch-specific section from CLAUDE.md - Fix vendor path deny pattern in .claude/settings.json - Use phpcbf directly in post-edit lint hook - Add null register test case - Cast json_encode in ClientTest for type safety * chore(deps): regenerate composer.lock with PHP 8.2 * fix: address PR #13 code review feedback (round 2) - Downgrade stale.yml permissions from contents:write to contents:read - Remove nullable arrays in BisCompanyDetails (PRH API always returns arrays) - Mark 3 additional breaking changes in CHANGELOG.md - Extract API_PREFIX constant in v3 Client to reduce path duplication * fix(ci): resolve merge conflict markers in composer workflow * fix(ci): clean up pr-lint workflow * chore: add MegaLinter configuration * docs: add PHPDoc to HTTP layer classes * docs: add PHPDoc to v1 DTOs, traits, and exceptions * docs: add PHPDoc to v3 DTOs and exceptions * fix(ci): use Composer download cache instead of vendor cache Caching vendor/ can inject stale binaries that pass unexpected arguments (e.g. --cache-directory) to Pest. Switch to caching ~/.composer/cache which only stores download archives. * fix(ci): add permissions to PR lint job MegaLinter needs write access to issues, pull-requests, and statuses to post results. Add explicit permissions block to the lint job. * fix: add source directory to phpcs.xml MegaLinter runs phpcs in project mode which relies on phpcs.xml to know which directories to scan. Add <file>src</file> so it finds code. * fix(deps): add SARIF and phpstan extension-installer packages Add phpstan/extension-installer, jbelien/phpstan-sarif-formatter, and bartlett/sarif-php-converters so MegaLinter can produce SARIF output from phpstan. * fix(ci): disable JSON_PRETTIER and configure markdownlint - Disable JSON_PRETTIER linter in MegaLinter (not needed) - Add .markdownlint.json disabling MD041 (first-line-heading false positive on YAML frontmatter) - Wrap bare URLs in README.md with angle brackets (MD034) * fix: add phpunit.xml.dist with cacheDirectory Prevents Pest's Cache plugin from injecting --cache-directory argument in CI, which caused PHPUnit to misparse it as a config file. * fix(ci): fix MegaLinter config Remove JSON_PRETTIER and PHP_PHPSTAN from ENABLE_LINTERS to resolve conflicts with DISABLE_LINTERS and missing SARIF extension. Add .claude/ to FILTER_REGEX_EXCLUDE. * fix: resolve markdownlint errors Disable MD013 (line length), add blank lines around headings/lists in CHANGELOG.md, fix table separator spacing in README.md. * fix: harden v3 Client error handling and URL encoding - Make BusinessLine::$code required (no empty-string default) - URL-encode businessId in v1 client to prevent path injection - Catch \JsonException alongside RequestException in v3 Client searchCompanies() and getPostCodes() since getJson() can throw it * fix: pin dependency versions and enable workflow YAML linting - Pin phpstan/extension-installer, jbelien/phpstan-sarif-formatter, and bartlett/sarif-php-converters to ^1.0 instead of wildcard - Remove .github/ from MegaLinter FILTER_REGEX_EXCLUDE so YAML linters can check workflow files * fix: resolve yamllint errors in GitHub YAML files - Add missing document start marker to labels.yml - Fix step indentation in composer.yml workflow * fix: resolve markdownlint errors and disable YAML_PRETTIER * chore: add yamllint configuration
This commit is contained in:
31
.claude/settings.json
Normal file
31
.claude/settings.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash -c 'input=$(cat); fp=$(echo \"$input\" | jq -r \".tool_input.file_path\"); if [[ \"$fp\" == *composer.lock ]] || [[ \"$fp\" == vendor/* ]] || [[ \"$fp\" == */vendor/* ]]; then echo \"{\\\"decision\\\":\\\"deny\\\",\\\"reason\\\":\\\"Do not edit $fp directly\\\"}\" >&2; exit 2; fi'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash -c 'input=$(cat); fp=$(echo \"$input\" | jq -r \".tool_input.file_path\"); if [[ \"$fp\" == *.php ]]; then cd \"$CLAUDE_PROJECT_DIR\" && php vendor/bin/phpcbf --standard=PSR12 \"$fp\" 2>/dev/null; fi; true'"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash -c 'input=$(cat); fp=$(echo \"$input\" | jq -r \".tool_input.file_path\"); if [[ \"$fp\" == *.php ]]; then cd \"$CLAUDE_PROJECT_DIR\" && composer phpstan 2>&1 | tail -5; fi; true'",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
13
.claude/skills/check-quality/SKILL.md
Normal file
13
.claude/skills/check-quality/SKILL.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: check-quality
|
||||
description: Run lint, phpstan, and tests in sequence, fix any issues found
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Run the full quality check pipeline:
|
||||
1. `composer lint` — fix any PSR-12 violations with `composer lint-fix`
|
||||
2. `composer phpstan` — fix any static analysis issues
|
||||
3. `composer test` — fix any failing tests
|
||||
|
||||
After each step, if issues are found, fix them before proceeding to the next step.
|
||||
Do not commit. Report results when done.
|
||||
16
.claude/skills/gen-test/SKILL.md
Normal file
16
.claude/skills/gen-test/SKILL.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: gen-test
|
||||
description: Generate a Pest test for a PHP class following project conventions
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Generate a Pest test file for the specified class.
|
||||
|
||||
Rules:
|
||||
- Place tests in `tests/Unit/` mirroring the src/ structure
|
||||
- Use Pest syntax (test(), expect(), describe())
|
||||
- For DTOs: test construction with valid data, verify all readonly properties
|
||||
- For traits: test each method the trait provides
|
||||
- For clients: mock Guzzle responses, test Valinor hydration
|
||||
- Follow existing test patterns in tests/Unit/
|
||||
- Read existing tests first to match style
|
||||
15
.claude/skills/new-dto/SKILL.md
Normal file
15
.claude/skills/new-dto/SKILL.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: new-dto
|
||||
description: Create a new final readonly DTO class with Valinor-compatible constructor
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Create a new DTO class following project conventions:
|
||||
- `final readonly class` with constructor promotion
|
||||
- Place in `src/v1/Dto/` or `src/v3/Dto/` based on API version
|
||||
- All properties as constructor-promoted readonly parameters
|
||||
- Use appropriate PHP types (string, int, ?string for nullable)
|
||||
- Apply relevant traits (HasSource, HasLanguage, etc.) if applicable
|
||||
- Namespace: `Ivuorinen\BusinessDataFetcher\{v1|v3}\Dto`
|
||||
- Generate a corresponding Pest test in tests/Unit/
|
||||
- Read existing DTOs first to match style
|
||||
28
.github/labels.yml
vendored
Normal file
28
.github/labels.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
- name: bug
|
||||
color: d73a4a
|
||||
description: Something isn't working
|
||||
|
||||
- name: enhancement
|
||||
color: a2eeef
|
||||
description: New feature or request
|
||||
|
||||
- name: documentation
|
||||
color: 0075ca
|
||||
description: Improvements or additions to documentation
|
||||
|
||||
- name: dependencies
|
||||
color: 0366d6
|
||||
description: Pull requests that update a dependency file
|
||||
|
||||
- name: breaking-change
|
||||
color: e11d48
|
||||
description: Breaking change to public API
|
||||
|
||||
- name: maintenance
|
||||
color: fbca04
|
||||
description: Code maintenance and refactoring
|
||||
|
||||
- name: ci
|
||||
color: 7057ff
|
||||
description: Continuous integration and tooling
|
||||
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>ivuorinen/.github:renovate-config"]
|
||||
"extends": ["github>ivuorinen/renovate-config"]
|
||||
}
|
||||
|
||||
47
.github/workflows/composer.yml
vendored
47
.github/workflows/composer.yml
vendored
@@ -18,33 +18,36 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.2', '8.3']
|
||||
php-versions: ['8.2', '8.3', '8.4']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate --strict
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate --strict
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
|
||||
with:
|
||||
path: vendor
|
||||
key: ${{ runner.os }}-php-${{ matrix.php-versions }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-php-${{ matrix.php-versions }}-
|
||||
${{ runner.os }}-php-
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: ~/.composer/cache
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php-versions }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php-versions }}-
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
|
||||
# Docs: https://getcomposer.org/doc/articles/scripts.md
|
||||
- name: Run linting
|
||||
run: composer lint
|
||||
|
||||
# - name: Run test suite
|
||||
# run: composer run-script test
|
||||
- name: Run static analysis
|
||||
run: composer phpstan
|
||||
|
||||
- name: Run test suite
|
||||
run: composer test
|
||||
|
||||
17
.github/workflows/pr-lint.yml
vendored
17
.github/workflows/pr-lint.yml
vendored
@@ -2,12 +2,19 @@
|
||||
name: PR Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [master, main]
|
||||
# Remove the line above to run when pushing to master
|
||||
pull_request:
|
||||
branches: [master, main]
|
||||
|
||||
jobs:
|
||||
SuperLinter:
|
||||
uses: ivuorinen/.github/.github/workflows/pr-lint.yml@main
|
||||
lint:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ivuorinen/actions/pr-lint@d1af04260d903f572ee953cc790ff7c1410709a6 # v2026.03.05
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
14
.github/workflows/release-drafter.yml
vendored
14
.github/workflows/release-drafter.yml
vendored
@@ -1,10 +1,18 @@
|
||||
---
|
||||
name: Release Drafter
|
||||
name: Release Monthly
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
Draft:
|
||||
uses: ivuorinen/.github/.github/workflows/sync-labels.yml@main
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ivuorinen/actions/release-monthly@d1af04260d903f572ee953cc790ff7c1410709a6 # v2026.03.05
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
13
.github/workflows/stale.yml
vendored
13
.github/workflows/stale.yml
vendored
@@ -8,12 +8,15 @@ on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # only for delete-branch option
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: ivuorinen/actions/stale@main
|
||||
- uses: ivuorinen/actions/stale@d1af04260d903f572ee953cc790ff7c1410709a6 # v2026.03.05
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
8
.github/workflows/sync-labels.yml
vendored
8
.github/workflows/sync-labels.yml
vendored
@@ -18,4 +18,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
SyncLabels:
|
||||
uses: ivuorinen/.github/.github/workflows/sync-labels.yml@main
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ivuorinen/actions/sync-labels@d1af04260d903f572ee953cc790ff7c1410709a6 # v2026.03.05
|
||||
with:
|
||||
labels: .github/labels.yml
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
124
.gitignore
vendored
124
.gitignore
vendored
@@ -1,90 +1,49 @@
|
||||
.php-cs-fixer.cache
|
||||
.php-cs-fixer.php
|
||||
# PHP / Composer
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# PHP CS Fixer
|
||||
.php-cs-fixer.cache
|
||||
.php-cs-fixer.php
|
||||
|
||||
# PHPUnit / Pest
|
||||
.phpunit.result.cache
|
||||
.phpunit.cache
|
||||
/app/phpunit.xml
|
||||
/phpunit.xml
|
||||
|
||||
# Build artifacts
|
||||
/build/
|
||||
logs
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
lib-cov
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
.grunt
|
||||
bower_components
|
||||
.lock-wscript
|
||||
build/Release
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
web_modules/
|
||||
*.tsbuildinfo
|
||||
.npm
|
||||
.eslintcache
|
||||
.stylelintcache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
.node_repl_history
|
||||
*.tgz
|
||||
.yarn-integrity
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.cache
|
||||
.parcel-cache
|
||||
.next
|
||||
out
|
||||
.nuxt
|
||||
dist
|
||||
.cache/
|
||||
.vuepress/dist
|
||||
.temp
|
||||
.docusaurus
|
||||
.serverless/
|
||||
.fusebox/
|
||||
.dynamodb/
|
||||
.tern-port
|
||||
.vscode-test
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
.env*.local
|
||||
|
||||
# OS / Editor
|
||||
.DS_Store
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
!*.svg
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
*~
|
||||
[._]*.un~
|
||||
|
||||
# Vim
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
.netrwhist
|
||||
*~
|
||||
tags
|
||||
[._]*.un~
|
||||
|
||||
# JetBrains (PHPStorm)
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/**/aws.xml
|
||||
.idea/**/contentModel.xml
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
@@ -92,44 +51,13 @@ tags
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
cmake-build-*/
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
out/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
.idea/replstate.xml
|
||||
.idea/sonarlint/
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
.idea/httpRequests
|
||||
.idea/caches/build_file_checksums.ser
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
bootstrap/compiled.php
|
||||
app/storage/
|
||||
public/storage
|
||||
public/hot
|
||||
public_html/storage
|
||||
public_html/hot
|
||||
storage/*.key
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
/coverage
|
||||
/.next/
|
||||
/out/
|
||||
/build
|
||||
.DS_Store
|
||||
*.pem
|
||||
.env*.local
|
||||
.vercel
|
||||
next-env.d.ts
|
||||
|
||||
# Claude Code
|
||||
.claude/settings.local.json
|
||||
|
||||
4
.markdownlint.json
Normal file
4
.markdownlint.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"MD013": false,
|
||||
"MD041": false
|
||||
}
|
||||
1
.markdownlintignore
Normal file
1
.markdownlintignore
Normal file
@@ -0,0 +1 @@
|
||||
docs.md
|
||||
26
.mega-linter.yml
Normal file
26
.mega-linter.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
APPLY_FIXES: all
|
||||
PARALLEL: true
|
||||
VALIDATE_ALL_CODEBASE: true
|
||||
GITHUB_STATUS_REPORTER: true
|
||||
SARIF_REPORTER: true
|
||||
IGNORE_GENERATED_FILES: true
|
||||
PRINT_ALPACA: false
|
||||
SHOW_SKIPPED_LINTERS: false
|
||||
SHOW_ELAPSED_TIME: false
|
||||
FILEIO_REPORTER: false
|
||||
|
||||
ENABLE_LINTERS:
|
||||
- YAML_YAMLLINT
|
||||
- MARKDOWN_MARKDOWNLINT
|
||||
- PHP_PHPCS
|
||||
|
||||
DISABLE_LINTERS:
|
||||
- REPOSITORY_DEVSKIM
|
||||
- JSON_PRETTIER
|
||||
- YAML_PRETTIER
|
||||
|
||||
PHP_PHPCS_CLI_LINT_MODE: project
|
||||
PHP_PHPCS_ARGUMENTS: "--warning-severity=0"
|
||||
|
||||
FILTER_REGEX_EXCLUDE: "(vendor/|node_modules/|\\.git/|\\.claude/|composer\\.lock|package-lock\\.json)"
|
||||
20
.yamllint.yml
Normal file
20
.yamllint.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
comments:
|
||||
require-starting-space: true
|
||||
ignore-shebangs: true
|
||||
min-spaces-from-content: 1
|
||||
line-length:
|
||||
max: 120
|
||||
allow-non-breakable-words: true
|
||||
allow-non-breakable-inline-mappings: false
|
||||
truthy:
|
||||
check-keys: false
|
||||
brackets:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 1
|
||||
|
||||
ignore: |
|
||||
vendor/
|
||||
40
CHANGELOG.md
Normal file
40
CHANGELOG.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
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.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- v3 API client (`Ivuorinen\BusinessDataFetcher\v3\Client`) with support for:
|
||||
- Company search (`searchCompanies()`)
|
||||
- Code list descriptions (`getDescription()`)
|
||||
- Postal code lookup (`getPostCodes()`)
|
||||
- Full company ZIP download (`getAllCompanies()`)
|
||||
- v3 DTOs based on the official OpenAPI schema
|
||||
- Pest test suite with 57 tests covering traits, DTOs, and both API clients
|
||||
- Shared HTTP infrastructure (`AbstractClient`, `HttpClientFactory`)
|
||||
- PHP 8.4 added to CI matrix
|
||||
- CI pipeline now runs linting, static analysis, and tests
|
||||
- `phpunit.xml` configuration
|
||||
- `.github/labels.yml` for label sync workflow
|
||||
|
||||
### Changed
|
||||
|
||||
- **BREAKING:** Replaced `spatie/data-transfer-object` with `cuyz/valinor` for DTO hydration
|
||||
- **BREAKING:** All v1 DTOs are now `final readonly` classes with constructor promotion
|
||||
- **BREAKING:** Removed `$sourceText` and `$authorityText` computed properties from traits; use `getSourceText()` and `getAuthorityString()` methods instead
|
||||
- **BREAKING:** `BusinessDataFetcher` constructor now accepts an optional `?Client` parameter for testability
|
||||
- Upgraded `squizlabs/php_codesniffer` from v3 to v4
|
||||
- **BREAKING:** `BusinessDataFetcher` now extends `AbstractClient`
|
||||
- **BREAKING:** Traits are now method-only (no properties or constructors)
|
||||
- **BREAKING:** Removed empty `HasVersion` trait (version is a regular constructor property)
|
||||
|
||||
### Removed
|
||||
|
||||
- `spatie/data-transfer-object` dependency
|
||||
- `ivuorinen/markdowndocs` dependency (incompatible with Pest's symfony/console requirement)
|
||||
- `composer docs` script (pending markdowndocs update)
|
||||
45
CLAUDE.md
Normal file
45
CLAUDE.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
PHP library for fetching Finnish business data from the PRH (Finnish Patent and Registration Office) open data API. Supports both v1 (BIS) and v3 (YTJ) APIs.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
composer lint # PSR-12 linting (phpcs v4) + Rector dry-run
|
||||
composer lint-fix # Auto-fix PSR-12 violations (phpcbf) + Rector apply
|
||||
composer rector # Run Rector standalone
|
||||
composer phpstan # Static analysis (level 9)
|
||||
composer test # Pest test suite
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
- **v1 Entry point**: `src/BusinessDataFetcher.php` — extends `AbstractClient`, fetches from `/bis/v1`
|
||||
- **v3 Entry point**: `src/v3/Client.php` — extends `AbstractClient`, fetches from `/opendata-ytj-api/v3`
|
||||
- **Shared HTTP**: `src/Http/AbstractClient.php` — base class with Guzzle + Valinor mapper
|
||||
- **v1 DTOs**: `src/v1/Dto/` — `final readonly` classes with Valinor-compatible constructors
|
||||
- **v3 DTOs**: `src/v3/Dto/` — `final readonly` classes matching the v3 OpenAPI schema
|
||||
- **Traits**: `src/v1/Traits/` — method-only helpers (`HasSource`, `HasLanguage`, `HasRegister`, `HasAuthority`, `HasChange`)
|
||||
- **Exceptions**: `src/v1/Exceptions/`, `src/v3/Exceptions/`
|
||||
- **Tests**: `tests/Unit/` — Pest tests for traits, DTOs, and both clients
|
||||
|
||||
## Conventions
|
||||
|
||||
- PHP 8.2+
|
||||
- PSR-12 coding standard (squizlabs/php_codesniffer v4)
|
||||
- Rector (`rector.php`): `deadCode`, `codeQuality`, `typeDeclarations` sets — no `codingStyle` (phpcs handles that)
|
||||
- PHPStan level 9
|
||||
- Valinor v1 for DTO hydration with `allowSuperfluousKeys()`
|
||||
- All DTOs are `final readonly` with constructor promotion
|
||||
- Traits are method-only (no properties)
|
||||
- Namespace: `Ivuorinen\BusinessDataFetcher`
|
||||
|
||||
## CI/CD
|
||||
|
||||
- GitHub Actions workflows in `.github/workflows/`
|
||||
- Reusable composite actions from `ivuorinen/actions` (not `ivuorinen/.github`)
|
||||
- Renovate config: `.github/renovate.json` extends `github>ivuorinen/renovate-config`
|
||||
- `pinact run -u -v --fix` — pin all action refs to commit SHAs after editing workflows
|
||||
- PHP matrix: 8.2, 8.3, 8.4
|
||||
47
CONTRIBUTING.md
Normal file
47
CONTRIBUTING.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Contributing
|
||||
|
||||
## Requirements
|
||||
|
||||
- PHP 8.2+
|
||||
- Composer
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ivuorinen/business-data-fetcher.git
|
||||
cd business-data-fetcher
|
||||
composer install
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Running checks
|
||||
|
||||
```bash
|
||||
composer lint # PSR-12 linting
|
||||
composer lint-fix # Auto-fix PSR-12 violations
|
||||
composer phpstan # Static analysis (level 9)
|
||||
composer test # Pest test suite
|
||||
```
|
||||
|
||||
### Branch Strategy
|
||||
|
||||
- `main` — stable release branch
|
||||
- `feat/*` — feature branches
|
||||
- `fix/*` — bugfix branches
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
1. Create a feature or fix branch from `main`
|
||||
2. Make your changes
|
||||
3. Ensure all checks pass: `composer lint && composer phpstan && composer test`
|
||||
4. Open a PR against `main`
|
||||
5. PRs require passing CI before merge
|
||||
|
||||
## Code Conventions
|
||||
|
||||
- PSR-12 coding standard
|
||||
- PHPStan level 9 strict analysis
|
||||
- All DTOs are `final readonly` classes with constructor promotion
|
||||
- Use Valinor for hydration (not manual array mapping)
|
||||
- Write Pest tests for new functionality
|
||||
111
README.md
111
README.md
@@ -1,36 +1,112 @@
|
||||
# Business Data Fetcher
|
||||
|
||||
This is an API client to Finnish Patent and Registration
|
||||
Office's (PRH) Business Information System (BIS) v1.
|
||||
[](https://github.com/ivuorinen/business-data-fetcher/actions/workflows/composer.yml)
|
||||
|
||||
Use it to get company data from the Business Information System by Business ID.
|
||||
PHP library for fetching Finnish business data from the PRH (Finnish Patent and Registration Office) open data API.
|
||||
|
||||
Supports both **v1** (BIS) and **v3** (YTJ) APIs.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
composer install ivuorinen/business-data-fetcher
|
||||
composer require ivuorinen/business-data-fetcher
|
||||
```
|
||||
|
||||
## Usage example
|
||||
## Usage
|
||||
|
||||
### v1 API (BIS)
|
||||
|
||||
Fetch company details by Business ID:
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
use Ivuorinen\BusinessDataFetcher\BusinessDataFetcher;
|
||||
|
||||
$client = new Ivuorinen\BusinessDataFetcher\BusinessDataFetcher();
|
||||
try {
|
||||
$results = $client->getBusinessInformation('1639413-9');
|
||||
print_r($results);
|
||||
} catch (\GuzzleHttp\Exception\GuzzleException $e) {
|
||||
var_dump($e);
|
||||
$client = new BusinessDataFetcher();
|
||||
$results = $client->getBusinessInformation('1639413-9');
|
||||
|
||||
foreach ($results as $company) {
|
||||
echo $company->name . "\n";
|
||||
echo $company->businessId . "\n";
|
||||
|
||||
foreach ($company->names as $name) {
|
||||
echo $name->name . ' (' . $name->getLanguageString() . ")\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data source
|
||||
### v3 API (YTJ)
|
||||
|
||||
All models are transcribed from PRH Open Data portal. You can find the examples
|
||||
and models descriptions, among other details and live API query tool following
|
||||
this link: https://avoindata.prh.fi/ytj_en.html
|
||||
Search companies with multiple filters:
|
||||
|
||||
```php
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Client;
|
||||
|
||||
$client = new Client();
|
||||
|
||||
// Search by name
|
||||
$result = $client->searchCompanies(name: 'Example');
|
||||
echo "Found {$result->totalResults} companies\n";
|
||||
|
||||
foreach ($result->companies as $company) {
|
||||
echo $company->businessId->value . ' - ';
|
||||
echo $company->names[0]->name . "\n";
|
||||
}
|
||||
|
||||
// Search by Business ID
|
||||
$result = $client->searchCompanies(businessId: '1639413-9');
|
||||
|
||||
// Search with multiple filters
|
||||
$result = $client->searchCompanies(
|
||||
location: 'Helsinki',
|
||||
companyForm: 'OY',
|
||||
mainBusinessLine: '62010',
|
||||
);
|
||||
|
||||
// Get code list descriptions
|
||||
$description = $client->getDescription('REK', 'en');
|
||||
|
||||
// Get postal codes
|
||||
$postCodes = $client->getPostCodes('fi');
|
||||
|
||||
// Download all companies as ZIP
|
||||
$stream = $client->getAllCompanies();
|
||||
file_put_contents('companies.zip', $stream->getContents());
|
||||
```
|
||||
|
||||
## v1 vs v3 API Comparison
|
||||
|
||||
| Feature | v1 (BIS) | v3 (YTJ) |
|
||||
| --------- | ---------- | ---------- |
|
||||
| Base URL | `/bis/v1` | `/opendata-ytj-api/v3` |
|
||||
| Lookup | By Business ID only | Search by name, location, form, etc. |
|
||||
| Company form | String code | Structured with descriptions |
|
||||
| Addresses | Flat structure | Nested with PostOffice objects |
|
||||
| Code lists | N/A | `/description` endpoint |
|
||||
| Postal codes | N/A | `/post_codes` endpoint |
|
||||
| Bulk download | N/A | `/all_companies` ZIP |
|
||||
|
||||
## Migration from v1 DTOs
|
||||
|
||||
If upgrading from the Spatie DTO version:
|
||||
|
||||
- DTOs are now `final readonly` classes (no `->toArray()`)
|
||||
- `$sourceText` / `$authorityText` properties removed; use `getSourceText()` / `getAuthorityString()` methods
|
||||
- `HasVersion` trait removed; `$version` is a regular constructor property
|
||||
- `BusinessDataFetcher` constructor accepts an optional Guzzle `Client` for testing
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
composer lint # PSR-12 linting
|
||||
composer lint-fix # Auto-fix violations
|
||||
composer phpstan # Static analysis (level 9)
|
||||
composer test # Pest test suite
|
||||
```
|
||||
|
||||
## Data Source
|
||||
|
||||
- v1: <https://avoindata.prh.fi/ytj_en.html>
|
||||
- v3: <https://avoindata.prh.fi/fi/ytj/swagger-ui>
|
||||
|
||||
## Notice of Liability
|
||||
|
||||
@@ -40,4 +116,3 @@ of this library are providing this without compensation and cannot be held respo
|
||||
## License
|
||||
|
||||
[MIT licensed](LICENSE.md)
|
||||
|
||||
|
||||
69
captainhook.json
Normal file
69
captainhook.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"pre-commit": {
|
||||
"enabled": true,
|
||||
"actions": [
|
||||
{
|
||||
"action": "\\CaptainHook\\App\\Hook\\Diff\\Action\\BlockSecrets",
|
||||
"options": {},
|
||||
"conditions": []
|
||||
},
|
||||
{
|
||||
"action": "composer lint-fix",
|
||||
"options": {},
|
||||
"conditions": []
|
||||
},
|
||||
{
|
||||
"action": "composer test",
|
||||
"options": {},
|
||||
"conditions": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"commit-msg": {
|
||||
"enabled": true,
|
||||
"actions": [
|
||||
{
|
||||
"action": "\\Ramsey\\CaptainHook\\ValidateConventionalCommit",
|
||||
"options": {},
|
||||
"conditions": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pre-push": {
|
||||
"enabled": false,
|
||||
"actions": []
|
||||
},
|
||||
"post-commit": {
|
||||
"enabled": false,
|
||||
"actions": []
|
||||
},
|
||||
"post-merge": {
|
||||
"enabled": false,
|
||||
"actions": []
|
||||
},
|
||||
"post-checkout": {
|
||||
"enabled": false,
|
||||
"actions": []
|
||||
},
|
||||
"post-rewrite": {
|
||||
"enabled": false,
|
||||
"actions": []
|
||||
},
|
||||
"post-change": {
|
||||
"enabled": true,
|
||||
"actions": [
|
||||
{
|
||||
"action": "composer install",
|
||||
"options": {},
|
||||
"conditions": [
|
||||
{
|
||||
"exec": "CaptainHook.FileChanged.Any",
|
||||
"args": [
|
||||
["composer.json", "composer.lock"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,51 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"spatie/data-transfer-object": "^3.9"
|
||||
"cuyz/valinor": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Ivuorinen\\BusinessDataFetcher\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Ivuorinen\\BusinessDataFetcher\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "^4.0",
|
||||
"ivuorinen/markdowndocs": "^4.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"pestphp/pest": "^3.0",
|
||||
"rector/rector": "^2.3",
|
||||
"captainhook/captainhook": "^5.0",
|
||||
"captainhook/hook-installer": "^1.0",
|
||||
"ramsey/conventional-commits": "^1.7",
|
||||
"captainhook/secrets": "^0.9.7",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"jbelien/phpstan-sarif-formatter": "^1.0",
|
||||
"bartlett/sarif-php-converters": "^1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "php vendor/bin/phpdoc-md generate src > docs.md",
|
||||
"lint": "php vendor/bin/phpcs --standard=PSR12 src",
|
||||
"lint-fix": "php vendor/bin/phpcbf --standard=PSR12 src",
|
||||
"phpstan": "php vendor/bin/phpstan analyse"
|
||||
"lint": [
|
||||
"php vendor/bin/phpcs --standard=PSR12 src",
|
||||
"php vendor/bin/rector --dry-run"
|
||||
],
|
||||
"lint-fix": [
|
||||
"php vendor/bin/phpcbf --standard=PSR12 src",
|
||||
"php vendor/bin/rector"
|
||||
],
|
||||
"rector": "php vendor/bin/rector",
|
||||
"phpstan": "php vendor/bin/phpstan analyse",
|
||||
"test": "php vendor/bin/pest"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"captainhook/hook-installer": true,
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4580
composer.lock
generated
4580
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@ require_once "vendor/autoload.php";
|
||||
$client = new Ivuorinen\BusinessDataFetcher\BusinessDataFetcher();
|
||||
try {
|
||||
$results = $client->getBusinessInformation("1639413-9");
|
||||
print_r($results);
|
||||
// Convert to JSON
|
||||
echo json_encode($results, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
|
||||
} catch (Exception $e) {
|
||||
var_dump($e);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ foreach ($classes as $className => $vars) {
|
||||
// Get name of the class from filename and split CamelCase to words.
|
||||
$classNameString = $className;
|
||||
$classNameString = str_replace("Bis", "", $classNameString);
|
||||
$classNameString = preg_replace('/(?<!^)[A-Z]/', ' $0', $classNameString);
|
||||
$classNameString = (string)preg_replace('/(?<!^)[A-Z]/', ' $0', $classNameString);
|
||||
$classNameString = ucwords($classNameString);
|
||||
|
||||
$usesHeader = [
|
||||
@@ -125,7 +125,7 @@ foreach ($classes as $className => $vars) {
|
||||
}
|
||||
|
||||
if (!empty($traits)) {
|
||||
$usesHeader[] = "use Ivuorinen\BusinessDataFetcher\Traits;";
|
||||
$usesHeader[] = "use Ivuorinen\BusinessDataFetcher\\v1\Traits;";
|
||||
}
|
||||
|
||||
$usesString = implode("\n", $usesHeader);
|
||||
@@ -134,7 +134,7 @@ foreach ($classes as $className => $vars) {
|
||||
|
||||
$file = "<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
namespace Ivuorinen\BusinessDataFetcher\\v1\Dto;
|
||||
$usesString
|
||||
|
||||
/**
|
||||
@@ -181,7 +181,9 @@ class $className extends DataTransferObject
|
||||
if (!empty($files)) {
|
||||
echo "Generating files:\n";
|
||||
|
||||
$dtoDir = sprintf('%s%s%s%s%s', dirname(__FILE__, 2), DS, 'src', DS, 'Dto');
|
||||
// Set the directory for the DTO classes.
|
||||
$dtoDir = implode(DS, [dirname(__FILE__, 2), 'src', 'v1', 'Dto']);
|
||||
|
||||
foreach ($files as $className => $file) {
|
||||
$filePath = sprintf('%s%s%s.php', $dtoDir, DS, $className);
|
||||
echo $filePath . "\n";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="PHP_CodeSniffer">
|
||||
<description>PSR12</description>
|
||||
<file>src</file>
|
||||
<rule ref="PSR12">
|
||||
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
|
||||
<exclude name="PSR12.Operators.OperatorSpacing"/>
|
||||
|
||||
18
phpunit.xml.dist
Normal file
18
phpunit.xml.dist
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
16
rector.php
Normal file
16
rector.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
|
||||
return RectorConfig::configure()
|
||||
->withPaths([
|
||||
__DIR__ . '/src',
|
||||
])
|
||||
->withPhpSets(php82: true)
|
||||
->withPreparedSets(
|
||||
deadCode: true,
|
||||
codeQuality: true,
|
||||
typeDeclarations: true,
|
||||
);
|
||||
@@ -2,47 +2,37 @@
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Ivuorinen\BusinessDataFetcher\Dto\BisCompanyDetails;
|
||||
use Ivuorinen\BusinessDataFetcher\Exceptions\ApiResponseErrorException;
|
||||
use Ivuorinen\BusinessDataFetcher\Http\AbstractClient;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyDetails;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Exceptions\ApiResponseErrorException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Fetches and returns business data from avoindata
|
||||
*/
|
||||
class BusinessDataFetcher
|
||||
/** Client for the PRH BIS v1 API (Finnish business data). */
|
||||
class BusinessDataFetcher extends AbstractClient
|
||||
{
|
||||
/**
|
||||
* @var \GuzzleHttp\Client
|
||||
*/
|
||||
private Client $httpClient;
|
||||
|
||||
/**
|
||||
* BusinessDataFetcher constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
/** @inheritDoc */
|
||||
protected function getBaseUri(): string
|
||||
{
|
||||
$this->httpClient = new Client([
|
||||
'base_uri' => 'https://avoindata.prh.fi',
|
||||
'timeout' => 2,
|
||||
]);
|
||||
return 'https://avoindata.prh.fi';
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
protected function getTimeout(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Business Information.
|
||||
*
|
||||
* @return BisCompanyDetails[] $response_data
|
||||
* @return BisCompanyDetails[]
|
||||
* @throws \Exception|\GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
public function getBusinessInformation(string $businessId): array
|
||||
{
|
||||
// Set request variables
|
||||
$requestUrl = '/bis/v1';
|
||||
|
||||
// Get the business data
|
||||
try {
|
||||
$uri = $requestUrl . '/' . $businessId;
|
||||
$uri = '/bis/v1/' . rawurlencode($businessId);
|
||||
$response = $this->httpClient->get($uri);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
@@ -52,7 +42,7 @@ class BusinessDataFetcher
|
||||
);
|
||||
}
|
||||
|
||||
$response_data = $this->parseResponse($response);
|
||||
return $this->parseResponse($response);
|
||||
} catch (RequestException $exception) {
|
||||
throw new ApiResponseErrorException(
|
||||
$exception->getMessage(),
|
||||
@@ -60,8 +50,6 @@ class BusinessDataFetcher
|
||||
$exception
|
||||
);
|
||||
}
|
||||
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,8 +57,7 @@ class BusinessDataFetcher
|
||||
*
|
||||
* @return BisCompanyDetails[]
|
||||
* @throws \JsonException
|
||||
* @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties
|
||||
* @throws \Ivuorinen\BusinessDataFetcher\Exceptions\ApiResponseErrorException
|
||||
* @throws ApiResponseErrorException
|
||||
*/
|
||||
public function parseResponse(ResponseInterface $response): array
|
||||
{
|
||||
@@ -88,7 +75,7 @@ class BusinessDataFetcher
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($data['results'])) {
|
||||
if (!isset($data['results']) || !is_array($data['results'])) {
|
||||
throw new ApiResponseErrorException(
|
||||
'Invalid response data',
|
||||
$response->getStatusCode()
|
||||
@@ -98,7 +85,7 @@ class BusinessDataFetcher
|
||||
$results = [];
|
||||
|
||||
foreach ($data['results'] as $result) {
|
||||
$results[] = new BisCompanyDetails($result);
|
||||
$results[] = $this->mapper->map(BisCompanyDetails::class, $result);
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Address
|
||||
*/
|
||||
class BisAddress extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Care of address
|
||||
*/
|
||||
public ?string $careOf = null;
|
||||
|
||||
/**
|
||||
* Street address
|
||||
*/
|
||||
public ?string $street = null;
|
||||
|
||||
/**
|
||||
* ZIP code
|
||||
*/
|
||||
public ?string $postCode = null;
|
||||
|
||||
/**
|
||||
* City of address
|
||||
*/
|
||||
public ?string $city = null;
|
||||
|
||||
/**
|
||||
* Type of address, 1 for street address, 2 for postal address
|
||||
*/
|
||||
public int $type;
|
||||
|
||||
/**
|
||||
* Two letter country code
|
||||
*/
|
||||
public ?string $country = null;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Business Id Change
|
||||
*/
|
||||
class BisCompanyBusinessIdChange extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
use Traits\HasChange;
|
||||
|
||||
/**
|
||||
* Description of reason
|
||||
*/
|
||||
public string $description = '';
|
||||
|
||||
/**
|
||||
* Reason code
|
||||
*/
|
||||
public string $reason = '';
|
||||
|
||||
/**
|
||||
* Date of Business ID change
|
||||
*/
|
||||
public ?string $changeDate = null;
|
||||
|
||||
/**
|
||||
* Old Business ID
|
||||
*/
|
||||
public string $oldBusinessId = '';
|
||||
|
||||
/**
|
||||
* New Business ID
|
||||
*/
|
||||
public string $newBusinessId = '';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Business Line
|
||||
*/
|
||||
class BisCompanyBusinessLine extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Zero for main line of business, positive for others
|
||||
*/
|
||||
public int $order;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Name of line of business
|
||||
*/
|
||||
public string $name = '';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Contact Detail
|
||||
*/
|
||||
class BisCompanyContactDetail extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Value of contact detail
|
||||
*/
|
||||
public string $value = '';
|
||||
|
||||
/**
|
||||
* Type of contact detail
|
||||
*/
|
||||
public string $type = '';
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Spatie\DataTransferObject\Attributes\CastWith;
|
||||
use Spatie\DataTransferObject\Casters;
|
||||
|
||||
/**
|
||||
* Company Details
|
||||
*/
|
||||
class BisCompanyDetails extends DataTransferObject
|
||||
{
|
||||
/**
|
||||
* Primary company name and translations
|
||||
* @var BisCompanyName[] $names
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyName::class)]
|
||||
public array $names = [];
|
||||
|
||||
/**
|
||||
* Auxiliary company name and translations
|
||||
* @var ?BisCompanyName[] $auxiliaryNames
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyName::class)]
|
||||
public ?array $auxiliaryNames = [];
|
||||
|
||||
/**
|
||||
* Company's street and postal addresses
|
||||
* @var ?BisAddress[] $addresses
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisAddress::class)]
|
||||
public ?array $addresses = [];
|
||||
|
||||
/**
|
||||
* Company form and translations
|
||||
* @var ?BisCompanyForm[] $companyForms
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyForm::class)]
|
||||
public ?array $companyForms = [];
|
||||
|
||||
/**
|
||||
* Bankruptcy, liquidation or restructuring proceedings
|
||||
* @var ?BisCompanyLiquidation[] $liquidations
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyLiquidation::class)]
|
||||
public ?array $liquidations = [];
|
||||
|
||||
/**
|
||||
* Company's lines of business and translations
|
||||
* @var ?BisCompanyBusinessLine[] $businessLines
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyBusinessLine::class)]
|
||||
public ?array $businessLines = [];
|
||||
|
||||
/**
|
||||
* Company's language(s)
|
||||
* @var ?BisCompanyLanguage[] $languages
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyLanguage::class)]
|
||||
public ?array $languages = [];
|
||||
|
||||
/**
|
||||
* Company's place of registered office and its translations
|
||||
* @var ?BisCompanyRegisteredOffice[] $registeredOffices
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyRegisteredOffice::class)]
|
||||
public ?array $registeredOffices = [];
|
||||
|
||||
/**
|
||||
* Company's contact details and translations
|
||||
* @var ?BisCompanyContactDetail[] $contactDetails
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyContactDetail::class)]
|
||||
public ?array $contactDetails = [];
|
||||
|
||||
/**
|
||||
* Company's registered entries
|
||||
* @var ?BisCompanyRegisteredEntry[] $registeredEntries
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyRegisteredEntry::class)]
|
||||
public ?array $registeredEntries = [];
|
||||
|
||||
/**
|
||||
* Company's Business ID changes
|
||||
* @var ?BisCompanyBusinessIdChange[] $businessIdChanges
|
||||
*/
|
||||
#[CastWith(Casters\ArrayCaster::class, itemType: BisCompanyBusinessIdChange::class)]
|
||||
public ?array $businessIdChanges = [];
|
||||
|
||||
/**
|
||||
* Business ID
|
||||
*/
|
||||
public string $businessId = '';
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Company form
|
||||
*/
|
||||
public ?string $companyForm = null;
|
||||
|
||||
/**
|
||||
* A URI for more details, if details aren't already included
|
||||
*/
|
||||
public ?string $detailsUri = null;
|
||||
|
||||
/**
|
||||
* Primary company name
|
||||
*/
|
||||
public string $name = '';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Form
|
||||
*/
|
||||
class BisCompanyForm extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Name of company form
|
||||
*/
|
||||
public string $name = '';
|
||||
|
||||
/**
|
||||
* Type of company form
|
||||
*/
|
||||
public ?string $type = null;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Language
|
||||
*/
|
||||
class BisCompanyLanguage extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Name of language
|
||||
*/
|
||||
public string $name = '';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Liquidation
|
||||
*/
|
||||
class BisCompanyLiquidation extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Bankruptcy, liquidation or restructuring proceedings
|
||||
*/
|
||||
public string $name = '';
|
||||
|
||||
/**
|
||||
* Type of liquidation
|
||||
*/
|
||||
public string $type = '';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Name
|
||||
*/
|
||||
class BisCompanyName extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Zero for primary company name, other for translations of the primary company name and auxiliary company names
|
||||
*/
|
||||
public int $order;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Company name
|
||||
*/
|
||||
public string $name = '';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Registered Entry
|
||||
*/
|
||||
class BisCompanyRegisteredEntry extends DataTransferObject
|
||||
{
|
||||
use Traits\HasAuthority;
|
||||
use Traits\HasLanguage;
|
||||
use Traits\HasRegister;
|
||||
|
||||
/**
|
||||
* Description of entry
|
||||
*/
|
||||
public string $description = '';
|
||||
|
||||
/**
|
||||
* Zero for common entries, one for ‘Unregistered’ and two for ‘Registered’
|
||||
*/
|
||||
public int $status;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Dto;
|
||||
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
/**
|
||||
* Company Registered Office
|
||||
*/
|
||||
class BisCompanyRegisteredOffice extends DataTransferObject
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasVersion;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
/**
|
||||
* Zero for primary place of registered office, positive for others
|
||||
*/
|
||||
public int $order;
|
||||
|
||||
/**
|
||||
* Date of registration
|
||||
*/
|
||||
public string $registrationDate = '';
|
||||
|
||||
/**
|
||||
* Ending date of registration
|
||||
*/
|
||||
public ?string $endDate = null;
|
||||
|
||||
/**
|
||||
* Name of place of registered office
|
||||
*/
|
||||
public string $name = '';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ApiResponseErrorException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class UnexpectedValueException extends Exception
|
||||
{
|
||||
}
|
||||
73
src/Http/AbstractClient.php
Normal file
73
src/Http/AbstractClient.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Http;
|
||||
|
||||
use CuyZ\Valinor\Mapper\TreeMapper;
|
||||
use CuyZ\Valinor\MapperBuilder;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
/**
|
||||
* Base HTTP client for PRH API communication.
|
||||
*
|
||||
* Provides shared Guzzle HTTP client and Valinor mapper instances
|
||||
* for concrete API client implementations.
|
||||
*/
|
||||
abstract class AbstractClient
|
||||
{
|
||||
protected Client $httpClient;
|
||||
|
||||
protected TreeMapper $mapper;
|
||||
|
||||
/** Initialize HTTP client and Valinor mapper. */
|
||||
public function __construct(?Client $httpClient = null)
|
||||
{
|
||||
$this->httpClient = $httpClient ?? HttpClientFactory::create(
|
||||
$this->getBaseUri(),
|
||||
$this->getTimeout()
|
||||
);
|
||||
|
||||
$this->mapper = (new MapperBuilder())
|
||||
->allowSuperfluousKeys()
|
||||
->mapper();
|
||||
}
|
||||
|
||||
/** Get the base URI for the API. */
|
||||
abstract protected function getBaseUri(): string;
|
||||
|
||||
/** Get the HTTP request timeout in seconds. */
|
||||
protected function getTimeout(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a GET request and decode the JSON response.
|
||||
*
|
||||
* @param array<string, mixed> $query
|
||||
* @return array<mixed>
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
protected function getJson(string $uri, array $query = []): array
|
||||
{
|
||||
$options = [];
|
||||
if ($query !== []) {
|
||||
$options['query'] = $query;
|
||||
}
|
||||
|
||||
$response = $this->httpClient->get($uri, $options);
|
||||
|
||||
$data = json_decode(
|
||||
$response->getBody()->getContents(),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new \JsonException('Response is not a valid JSON object or array');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
18
src/Http/HttpClientFactory.php
Normal file
18
src/Http/HttpClientFactory.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Http;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
/** Factory for creating pre-configured Guzzle HTTP clients. */
|
||||
class HttpClientFactory
|
||||
{
|
||||
/** Create a Guzzle client with the given base URI and timeout. */
|
||||
public static function create(string $baseUri, int $timeout = 10): Client
|
||||
{
|
||||
return new Client([
|
||||
'base_uri' => $baseUri,
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
trait HasLanguage
|
||||
{
|
||||
/**
|
||||
* @see getLanguageString()
|
||||
* @var string|null $language Two letter language code
|
||||
* (e.g. 'fi', 'sv', 'en')
|
||||
*/
|
||||
public ?string $language;
|
||||
|
||||
/**
|
||||
* Get the language code as a string.
|
||||
*/
|
||||
public function getLanguageString(): string
|
||||
{
|
||||
return match ($this->language) {
|
||||
'fi' => 'finnish',
|
||||
'en' => 'english',
|
||||
'sv' => 'swedish',
|
||||
default => 'unknown:' . $this->language,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
trait HasSource
|
||||
{
|
||||
/**
|
||||
* Source of the information.
|
||||
*
|
||||
* source (integer, optional):
|
||||
* - Zero for common,
|
||||
* - one for Finnish Patent and Registration Office,
|
||||
* - two for Tax Administration or
|
||||
* - three for Business Information System
|
||||
*
|
||||
* Use `getSourceText()` to get the text representation.
|
||||
*
|
||||
* @see getSourceText()
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public ?int $source;
|
||||
|
||||
public function getSourceText(): string
|
||||
{
|
||||
return match ($this->source) {
|
||||
0 => 'common',
|
||||
1 => 'Finnish Patent and Registration Office',
|
||||
2 => 'Tax Administration',
|
||||
3 => 'Business Information System',
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Traits;
|
||||
|
||||
trait HasVersion
|
||||
{
|
||||
/**
|
||||
* One for current version and >1 for historical contact details.
|
||||
*/
|
||||
public int $version = 0;
|
||||
}
|
||||
27
src/v1/Dto/BisAddress.php
Normal file
27
src/v1/Dto/BisAddress.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a postal or visiting address from the BIS v1 API. */
|
||||
final readonly class BisAddress
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public int $type = 0,
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public ?string $careOf = null,
|
||||
public ?string $street = null,
|
||||
public ?string $postCode = null,
|
||||
public ?string $city = null,
|
||||
public ?string $country = null,
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
25
src/v1/Dto/BisCompanyBusinessIdChange.php
Normal file
25
src/v1/Dto/BisCompanyBusinessIdChange.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a business ID change event (e.g. fusion, division). */
|
||||
final readonly class BisCompanyBusinessIdChange
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
use Traits\HasChange;
|
||||
|
||||
public function __construct(
|
||||
public string $description = '',
|
||||
public string $reason = '',
|
||||
public ?string $changeDate = null,
|
||||
public string $oldBusinessId = '',
|
||||
public string $newBusinessId = '',
|
||||
public ?int $source = null,
|
||||
public ?string $language = null,
|
||||
public string|int|null $change = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
23
src/v1/Dto/BisCompanyBusinessLine.php
Normal file
23
src/v1/Dto/BisCompanyBusinessLine.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company's line of business (industry classification). */
|
||||
final readonly class BisCompanyBusinessLine
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public int $order = 0,
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $name = '',
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
23
src/v1/Dto/BisCompanyContactDetail.php
Normal file
23
src/v1/Dto/BisCompanyContactDetail.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company contact detail (phone, email, website, etc.). */
|
||||
final readonly class BisCompanyContactDetail
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $value = '',
|
||||
public string $type = '',
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
40
src/v1/Dto/BisCompanyDetails.php
Normal file
40
src/v1/Dto/BisCompanyDetails.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
/** Top-level DTO for a company record from the BIS v1 API. */
|
||||
final readonly class BisCompanyDetails
|
||||
{
|
||||
/**
|
||||
* @param list<BisCompanyName> $names
|
||||
* @param list<BisCompanyName> $auxiliaryNames
|
||||
* @param list<BisAddress> $addresses
|
||||
* @param list<BisCompanyForm> $companyForms
|
||||
* @param list<BisCompanyLiquidation> $liquidations
|
||||
* @param list<BisCompanyBusinessLine> $businessLines
|
||||
* @param list<BisCompanyLanguage> $languages
|
||||
* @param list<BisCompanyRegisteredOffice> $registeredOffices
|
||||
* @param list<BisCompanyContactDetail> $contactDetails
|
||||
* @param list<BisCompanyRegisteredEntry> $registeredEntries
|
||||
* @param list<BisCompanyBusinessIdChange> $businessIdChanges
|
||||
*/
|
||||
public function __construct(
|
||||
public string $businessId = '',
|
||||
public string $registrationDate = '',
|
||||
public ?string $companyForm = null,
|
||||
public ?string $detailsUri = null,
|
||||
public string $name = '',
|
||||
public array $names = [],
|
||||
public array $auxiliaryNames = [],
|
||||
public array $addresses = [],
|
||||
public array $companyForms = [],
|
||||
public array $liquidations = [],
|
||||
public array $businessLines = [],
|
||||
public array $languages = [],
|
||||
public array $registeredOffices = [],
|
||||
public array $contactDetails = [],
|
||||
public array $registeredEntries = [],
|
||||
public array $businessIdChanges = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
23
src/v1/Dto/BisCompanyForm.php
Normal file
23
src/v1/Dto/BisCompanyForm.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company's legal form (e.g. Ltd, cooperative). */
|
||||
final readonly class BisCompanyForm
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $name = '',
|
||||
public ?string $type = null,
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
22
src/v1/Dto/BisCompanyLanguage.php
Normal file
22
src/v1/Dto/BisCompanyLanguage.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company's registered language. */
|
||||
final readonly class BisCompanyLanguage
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $name = '',
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
23
src/v1/Dto/BisCompanyLiquidation.php
Normal file
23
src/v1/Dto/BisCompanyLiquidation.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company liquidation or bankruptcy entry. */
|
||||
final readonly class BisCompanyLiquidation
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $name = '',
|
||||
public string $type = '',
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
23
src/v1/Dto/BisCompanyName.php
Normal file
23
src/v1/Dto/BisCompanyName.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company name or auxiliary name entry. */
|
||||
final readonly class BisCompanyName
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public int $order = 0,
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $name = '',
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
24
src/v1/Dto/BisCompanyRegisteredEntry.php
Normal file
24
src/v1/Dto/BisCompanyRegisteredEntry.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a registration entry in a public register. */
|
||||
final readonly class BisCompanyRegisteredEntry
|
||||
{
|
||||
use Traits\HasAuthority;
|
||||
use Traits\HasLanguage;
|
||||
use Traits\HasRegister;
|
||||
|
||||
public function __construct(
|
||||
public string $description = '',
|
||||
public int $status = 0,
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public int $authority = 0,
|
||||
public ?string $language = null,
|
||||
public int|null $register = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
23
src/v1/Dto/BisCompanyRegisteredOffice.php
Normal file
23
src/v1/Dto/BisCompanyRegisteredOffice.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Dto;
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Represents a company's registered office (domicile). */
|
||||
final readonly class BisCompanyRegisteredOffice
|
||||
{
|
||||
use Traits\HasSource;
|
||||
use Traits\HasLanguage;
|
||||
|
||||
public function __construct(
|
||||
public int $order = 0,
|
||||
public string $registrationDate = '',
|
||||
public ?string $endDate = null,
|
||||
public string $name = '',
|
||||
public ?int $source = null,
|
||||
public int $version = 0,
|
||||
public ?string $language = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
10
src/v1/Exceptions/ApiResponseErrorException.php
Normal file
10
src/v1/Exceptions/ApiResponseErrorException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/** Thrown when the BIS v1 API returns an error response. */
|
||||
class ApiResponseErrorException extends Exception
|
||||
{
|
||||
}
|
||||
10
src/v1/Exceptions/UnexpectedValueException.php
Normal file
10
src/v1/Exceptions/UnexpectedValueException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/** Thrown when the BIS v1 API returns an unexpected value. */
|
||||
class UnexpectedValueException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Traits;
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Provides a human-readable authority name from the authority code. */
|
||||
trait HasAuthority
|
||||
{
|
||||
/**
|
||||
* @see getChangeString()
|
||||
* @var int $authority What authority the change is related to.
|
||||
*/
|
||||
public int $authority;
|
||||
|
||||
/**
|
||||
* Get the name of the authority.
|
||||
*/
|
||||
/** Get the authority name (e.g. "Tax Administration"). */
|
||||
public function getAuthorityString(): string
|
||||
{
|
||||
return match ($this->authority) {
|
||||
@@ -1,19 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Traits;
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Provides a human-readable description of a business ID change reason. */
|
||||
trait HasChange
|
||||
{
|
||||
/**
|
||||
* @see getChangeString()
|
||||
* @var string|int|null $change Change as a string or integer.
|
||||
* Models claim this is an integer, but it can also be a string.
|
||||
*/
|
||||
public string|int|null $change;
|
||||
|
||||
/**
|
||||
* Get the description string of the change.
|
||||
*/
|
||||
/** Get the change reason description (e.g. "Fusion", "Division"). */
|
||||
public function getChangeString(): string
|
||||
{
|
||||
return match ($this->change) {
|
||||
22
src/v1/Traits/HasLanguage.php
Normal file
22
src/v1/Traits/HasLanguage.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Provides a human-readable language name from a language code. */
|
||||
trait HasLanguage
|
||||
{
|
||||
/** Get the language name (e.g. "finnish", "english", "swedish"). */
|
||||
public function getLanguageString(): string
|
||||
{
|
||||
if ($this->language === null) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
return match ($this->language) {
|
||||
'fi' => 'finnish',
|
||||
'en' => 'english',
|
||||
'sv' => 'swedish',
|
||||
default => 'unknown:' . $this->language,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\Traits;
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Provides a human-readable register name from the register code. */
|
||||
trait HasRegister
|
||||
{
|
||||
/**
|
||||
* @see getRegisterString()
|
||||
* @var int|null $register What register the change is related to.
|
||||
*/
|
||||
public int|null $register;
|
||||
|
||||
/**
|
||||
* Get the name of the register.
|
||||
*/
|
||||
/** Get the register name (e.g. "Trade Register", "VAT Register"). */
|
||||
public function getRegisterString(): string
|
||||
{
|
||||
return match ($this->register) {
|
||||
19
src/v1/Traits/HasSource.php
Normal file
19
src/v1/Traits/HasSource.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v1\Traits;
|
||||
|
||||
/** Provides a human-readable data source name from the source code. */
|
||||
trait HasSource
|
||||
{
|
||||
/** Get the data source name (e.g. "Tax Administration", "PRH"). */
|
||||
public function getSourceText(): string
|
||||
{
|
||||
return match ($this->source) {
|
||||
0 => 'common',
|
||||
1 => 'Finnish Patent and Registration Office',
|
||||
2 => 'Tax Administration',
|
||||
3 => 'Business Information System',
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
}
|
||||
124
src/v3/Client.php
Normal file
124
src/v3/Client.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Ivuorinen\BusinessDataFetcher\Http\AbstractClient;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Dto\CompanySearchResult;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Dto\PostCodeEntry;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Exceptions\V3ApiException;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/** Client for the PRH YTJ v3 API (Finnish business data). */
|
||||
class Client extends AbstractClient
|
||||
{
|
||||
private const API_PREFIX = '/opendata-ytj-api/v3';
|
||||
|
||||
/** @inheritDoc */
|
||||
protected function getBaseUri(): string
|
||||
{
|
||||
return 'https://avoindata.prh.fi';
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for companies.
|
||||
*
|
||||
* @throws V3ApiException
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
public function searchCompanies(
|
||||
?string $name = null,
|
||||
?string $businessId = null,
|
||||
?string $location = null,
|
||||
?string $companyForm = null,
|
||||
?string $mainBusinessLine = null,
|
||||
?string $registrationDateStart = null,
|
||||
?string $registrationDateEnd = null,
|
||||
?string $postCode = null,
|
||||
?string $businessIdRegistrationStart = null,
|
||||
?string $businessIdRegistrationEnd = null,
|
||||
?int $page = null,
|
||||
): CompanySearchResult {
|
||||
$query = array_filter([
|
||||
'name' => $name,
|
||||
'businessId' => $businessId,
|
||||
'location' => $location,
|
||||
'companyForm' => $companyForm,
|
||||
'mainBusinessLine' => $mainBusinessLine,
|
||||
'registrationDateStart' => $registrationDateStart,
|
||||
'registrationDateEnd' => $registrationDateEnd,
|
||||
'postCode' => $postCode,
|
||||
'businessIdRegistrationStart' => $businessIdRegistrationStart,
|
||||
'businessIdRegistrationEnd' => $businessIdRegistrationEnd,
|
||||
'page' => $page,
|
||||
], fn (string|int|null $v): bool => $v !== null);
|
||||
|
||||
try {
|
||||
$data = $this->getJson(self::API_PREFIX . '/companies', $query);
|
||||
return $this->mapper->map(CompanySearchResult::class, $data);
|
||||
} catch (RequestException | \JsonException $e) {
|
||||
throw new V3ApiException($e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve code list description.
|
||||
*
|
||||
* @throws V3ApiException
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
public function getDescription(string $code, string $lang = 'en'): string
|
||||
{
|
||||
try {
|
||||
$response = $this->httpClient->get(self::API_PREFIX . '/description', [
|
||||
'query' => ['code' => $code, 'lang' => $lang],
|
||||
]);
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
} catch (RequestException $e) {
|
||||
throw new V3ApiException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve postal code details.
|
||||
*
|
||||
* @return PostCodeEntry[]
|
||||
* @throws V3ApiException
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
public function getPostCodes(string $lang = 'en'): array
|
||||
{
|
||||
try {
|
||||
$data = $this->getJson(self::API_PREFIX . '/post_codes', ['lang' => $lang]);
|
||||
|
||||
$results = [];
|
||||
foreach ($data as $entry) {
|
||||
$results[] = $this->mapper->map(PostCodeEntry::class, $entry);
|
||||
}
|
||||
|
||||
return $results;
|
||||
} catch (RequestException | \JsonException $e) {
|
||||
throw new V3ApiException($e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all companies as a ZIP download stream.
|
||||
*
|
||||
* @throws V3ApiException
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
public function getAllCompanies(): StreamInterface
|
||||
{
|
||||
try {
|
||||
$response = $this->httpClient->get(self::API_PREFIX . '/all_companies', [
|
||||
'stream' => true,
|
||||
]);
|
||||
|
||||
return $response->getBody();
|
||||
} catch (RequestException $e) {
|
||||
throw new V3ApiException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/v3/Dto/Address.php
Normal file
28
src/v3/Dto/Address.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a postal or visiting address from the YTJ v3 API. */
|
||||
final readonly class Address
|
||||
{
|
||||
/**
|
||||
* @param list<PostOffice> $postOffices
|
||||
*/
|
||||
public function __construct(
|
||||
public int $type = 0,
|
||||
public string $source = '',
|
||||
public ?string $street = null,
|
||||
public ?string $postCode = null,
|
||||
public array $postOffices = [],
|
||||
public ?string $postOfficeBox = null,
|
||||
public ?string $buildingNumber = null,
|
||||
public ?string $entrance = null,
|
||||
public ?string $apartmentNumber = null,
|
||||
public ?string $apartmentIdSuffix = null,
|
||||
public ?string $co = null,
|
||||
public ?string $country = null,
|
||||
public ?string $freeAddressLine = null,
|
||||
public ?string $registrationDate = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
14
src/v3/Dto/BusinessId.php
Normal file
14
src/v3/Dto/BusinessId.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a Finnish business ID (Y-tunnus) with registration metadata. */
|
||||
final readonly class BusinessId
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $source = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
19
src/v3/Dto/BusinessLine.php
Normal file
19
src/v3/Dto/BusinessLine.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a company's line of business (industry classification) from the YTJ v3 API. */
|
||||
final readonly class BusinessLine
|
||||
{
|
||||
/**
|
||||
* @param list<DescriptionEntry> $descriptions
|
||||
*/
|
||||
public function __construct(
|
||||
public string $code,
|
||||
public array $descriptions = [],
|
||||
public ?string $typeCodeSet = null,
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $source = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
32
src/v3/Dto/Company.php
Normal file
32
src/v3/Dto/Company.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Top-level DTO for a company record from the YTJ v3 API. */
|
||||
final readonly class Company
|
||||
{
|
||||
/**
|
||||
* @param list<RegisterName> $names
|
||||
* @param list<CompanyForm> $companyForms
|
||||
* @param list<CompanySituation> $companySituations
|
||||
* @param list<RegisteredEntry> $registeredEntries
|
||||
* @param list<Address> $addresses
|
||||
*/
|
||||
public function __construct(
|
||||
public BusinessId $businessId,
|
||||
public string $tradeRegisterStatus = '',
|
||||
public string $lastModified = '',
|
||||
public ?EuId $euId = null,
|
||||
public array $names = [],
|
||||
public ?BusinessLine $mainBusinessLine = null,
|
||||
public ?Website $website = null,
|
||||
public array $companyForms = [],
|
||||
public array $companySituations = [],
|
||||
public array $registeredEntries = [],
|
||||
public array $addresses = [],
|
||||
public ?string $status = null,
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $endDate = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
20
src/v3/Dto/CompanyForm.php
Normal file
20
src/v3/Dto/CompanyForm.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a company's legal form (e.g. Ltd, cooperative) from the YTJ v3 API. */
|
||||
final readonly class CompanyForm
|
||||
{
|
||||
/**
|
||||
* @param list<DescriptionEntry> $descriptions
|
||||
*/
|
||||
public function __construct(
|
||||
public string $type = '',
|
||||
public string $source = '',
|
||||
public int $version = 0,
|
||||
public array $descriptions = [],
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $endDate = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
16
src/v3/Dto/CompanySearchResult.php
Normal file
16
src/v3/Dto/CompanySearchResult.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Paginated search result containing matched companies from the YTJ v3 API. */
|
||||
final readonly class CompanySearchResult
|
||||
{
|
||||
/**
|
||||
* @param list<Company> $companies
|
||||
*/
|
||||
public function __construct(
|
||||
public int $totalResults = 0,
|
||||
public array $companies = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
15
src/v3/Dto/CompanySituation.php
Normal file
15
src/v3/Dto/CompanySituation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a company's operational situation (e.g. active, dissolved). */
|
||||
final readonly class CompanySituation
|
||||
{
|
||||
public function __construct(
|
||||
public string $type,
|
||||
public string $source,
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $endDate = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
13
src/v3/Dto/DescriptionEntry.php
Normal file
13
src/v3/Dto/DescriptionEntry.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a localized description with a language code. */
|
||||
final readonly class DescriptionEntry
|
||||
{
|
||||
public function __construct(
|
||||
public string $languageCode = '',
|
||||
public string $description = '',
|
||||
) {
|
||||
}
|
||||
}
|
||||
13
src/v3/Dto/EuId.php
Normal file
13
src/v3/Dto/EuId.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a company's EU VAT identification number. */
|
||||
final readonly class EuId
|
||||
{
|
||||
public function __construct(
|
||||
public string $value = '',
|
||||
public ?string $source = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
16
src/v3/Dto/PostCodeEntry.php
Normal file
16
src/v3/Dto/PostCodeEntry.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a Finnish postal code with associated city and municipality. */
|
||||
final readonly class PostCodeEntry
|
||||
{
|
||||
public function __construct(
|
||||
public string $postCode = '',
|
||||
public string $city = '',
|
||||
public bool $active = true,
|
||||
public string $languageCode = '',
|
||||
public ?string $municipalityCode = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
14
src/v3/Dto/PostOffice.php
Normal file
14
src/v3/Dto/PostOffice.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a localized post office name within an address. */
|
||||
final readonly class PostOffice
|
||||
{
|
||||
public function __construct(
|
||||
public string $city,
|
||||
public string $languageCode,
|
||||
public ?string $municipalityCode = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
17
src/v3/Dto/RegisterName.php
Normal file
17
src/v3/Dto/RegisterName.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a company name registered in a specific register. */
|
||||
final readonly class RegisterName
|
||||
{
|
||||
public function __construct(
|
||||
public string $name = '',
|
||||
public string $type = '',
|
||||
public string $source = '',
|
||||
public int $version = 0,
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $endDate = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
20
src/v3/Dto/RegisteredEntry.php
Normal file
20
src/v3/Dto/RegisteredEntry.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a registration entry in a public register from the YTJ v3 API. */
|
||||
final readonly class RegisteredEntry
|
||||
{
|
||||
/**
|
||||
* @param list<DescriptionEntry> $descriptions
|
||||
*/
|
||||
public function __construct(
|
||||
public string $type = '',
|
||||
public string $register = '',
|
||||
public string $authority = '',
|
||||
public array $descriptions = [],
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $endDate = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
14
src/v3/Dto/Website.php
Normal file
14
src/v3/Dto/Website.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Dto;
|
||||
|
||||
/** Represents a company's registered website URL. */
|
||||
final readonly class Website
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public ?string $registrationDate = null,
|
||||
public ?string $source = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
10
src/v3/Exceptions/V3ApiException.php
Normal file
10
src/v3/Exceptions/V3ApiException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Ivuorinen\BusinessDataFetcher\v3\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/** Thrown when the YTJ v3 API returns an error response. */
|
||||
class V3ApiException extends Exception
|
||||
{
|
||||
}
|
||||
112
tests/Fixtures/sample-response.json
Normal file
112
tests/Fixtures/sample-response.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"type": "fi.prh.opendata.bis",
|
||||
"version": "1",
|
||||
"totalResults": -1,
|
||||
"resultsFrom": 0,
|
||||
"previousResultsUri": null,
|
||||
"nextResultsUri": null,
|
||||
"exceptionNoticeUri": null,
|
||||
"results": [
|
||||
{
|
||||
"businessId": "1639413-9",
|
||||
"registrationDate": "2001-04-03",
|
||||
"companyForm": "OY",
|
||||
"detailsUri": null,
|
||||
"name": "Oy Example Ab",
|
||||
"names": [
|
||||
{
|
||||
"order": 0,
|
||||
"version": 1,
|
||||
"registrationDate": "2015-01-19",
|
||||
"endDate": null,
|
||||
"name": "Oy Example Ab",
|
||||
"source": 0,
|
||||
"language": "fi"
|
||||
}
|
||||
],
|
||||
"auxiliaryNames": [],
|
||||
"addresses": [
|
||||
{
|
||||
"careOf": null,
|
||||
"street": "Testitie 1",
|
||||
"postCode": "00100",
|
||||
"type": 1,
|
||||
"city": "HELSINKI",
|
||||
"country": "FI",
|
||||
"source": 0,
|
||||
"version": 1,
|
||||
"registrationDate": "2019-04-30",
|
||||
"endDate": null,
|
||||
"language": "fi"
|
||||
}
|
||||
],
|
||||
"companyForms": [
|
||||
{
|
||||
"version": 1,
|
||||
"registrationDate": "2001-04-03",
|
||||
"endDate": null,
|
||||
"name": "Osakeyhtiö",
|
||||
"type": "OY",
|
||||
"source": 0,
|
||||
"language": "fi"
|
||||
}
|
||||
],
|
||||
"liquidations": [],
|
||||
"businessLines": [
|
||||
{
|
||||
"order": 0,
|
||||
"version": 1,
|
||||
"registrationDate": "2017-12-31",
|
||||
"endDate": null,
|
||||
"name": "Computer programming activities",
|
||||
"source": 2,
|
||||
"language": "en"
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"version": 1,
|
||||
"registrationDate": "2001-04-03",
|
||||
"endDate": null,
|
||||
"name": "Finnish",
|
||||
"source": 0,
|
||||
"language": "en"
|
||||
}
|
||||
],
|
||||
"registeredOffices": [
|
||||
{
|
||||
"order": 0,
|
||||
"version": 1,
|
||||
"registrationDate": "2001-04-03",
|
||||
"endDate": null,
|
||||
"name": "HELSINKI",
|
||||
"source": 0,
|
||||
"language": "fi"
|
||||
}
|
||||
],
|
||||
"contactDetails": [
|
||||
{
|
||||
"version": 1,
|
||||
"registrationDate": "2019-04-30",
|
||||
"endDate": null,
|
||||
"language": "fi",
|
||||
"value": "www.example.fi",
|
||||
"type": "Website",
|
||||
"source": 0
|
||||
}
|
||||
],
|
||||
"registeredEntries": [
|
||||
{
|
||||
"authority": 2,
|
||||
"register": 1,
|
||||
"status": 2,
|
||||
"registrationDate": "2001-06-01",
|
||||
"endDate": null,
|
||||
"description": "Registered",
|
||||
"language": "en"
|
||||
}
|
||||
],
|
||||
"businessIdChanges": []
|
||||
}
|
||||
]
|
||||
}
|
||||
101
tests/Fixtures/v3-search-response.json
Normal file
101
tests/Fixtures/v3-search-response.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"totalResults": 1,
|
||||
"companies": [
|
||||
{
|
||||
"businessId": {
|
||||
"value": "1639413-9",
|
||||
"registrationDate": "2001-04-03",
|
||||
"source": "YTJ"
|
||||
},
|
||||
"euId": {
|
||||
"value": "FIFOO123456",
|
||||
"source": "YTJ"
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"name": "Oy Example Ab",
|
||||
"type": "TOIM",
|
||||
"registrationDate": "2015-01-19",
|
||||
"endDate": null,
|
||||
"version": 1,
|
||||
"source": "YTJ"
|
||||
}
|
||||
],
|
||||
"mainBusinessLine": {
|
||||
"code": "62010",
|
||||
"descriptions": [
|
||||
{
|
||||
"languageCode": "3",
|
||||
"description": "Computer programming activities"
|
||||
}
|
||||
],
|
||||
"typeCodeSet": "TOL2008",
|
||||
"registrationDate": "2017-12-31",
|
||||
"source": "VERO"
|
||||
},
|
||||
"website": {
|
||||
"url": "www.example.fi",
|
||||
"registrationDate": "2019-04-30",
|
||||
"source": "YTJ"
|
||||
},
|
||||
"companyForms": [
|
||||
{
|
||||
"type": "OY",
|
||||
"descriptions": [
|
||||
{
|
||||
"languageCode": "1",
|
||||
"description": "Osakeyhtiö"
|
||||
},
|
||||
{
|
||||
"languageCode": "3",
|
||||
"description": "Limited company"
|
||||
}
|
||||
],
|
||||
"registrationDate": "2001-04-03",
|
||||
"endDate": null,
|
||||
"version": 1,
|
||||
"source": "YTJ"
|
||||
}
|
||||
],
|
||||
"companySituations": [],
|
||||
"registeredEntries": [
|
||||
{
|
||||
"type": "REK",
|
||||
"descriptions": [
|
||||
{
|
||||
"languageCode": "3",
|
||||
"description": "Registered"
|
||||
}
|
||||
],
|
||||
"registrationDate": "2001-06-01",
|
||||
"endDate": null,
|
||||
"register": "1",
|
||||
"authority": "2"
|
||||
}
|
||||
],
|
||||
"addresses": [
|
||||
{
|
||||
"type": 1,
|
||||
"street": "Testitie 1",
|
||||
"postCode": "00100",
|
||||
"postOffices": [
|
||||
{
|
||||
"city": "HELSINKI",
|
||||
"languageCode": "1",
|
||||
"municipalityCode": "091"
|
||||
}
|
||||
],
|
||||
"co": null,
|
||||
"country": "FI",
|
||||
"registrationDate": "2019-04-30",
|
||||
"source": "YTJ"
|
||||
}
|
||||
],
|
||||
"tradeRegisterStatus": "2",
|
||||
"status": "ACTIVE",
|
||||
"registrationDate": "2001-04-03",
|
||||
"endDate": null,
|
||||
"lastModified": "2023-06-15T10:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
tests/Pest.php
Normal file
9
tests/Pest.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Case
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// uses(Tests\TestCase::class)->in('Feature');
|
||||
52
tests/Unit/BusinessDataFetcherTest.php
Normal file
52
tests/Unit/BusinessDataFetcherTest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Ivuorinen\BusinessDataFetcher\BusinessDataFetcher;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyDetails;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Exceptions\ApiResponseErrorException;
|
||||
|
||||
function createFetcherWithMock(Response ...$responses): BusinessDataFetcher
|
||||
{
|
||||
$mock = new MockHandler($responses);
|
||||
$client = new Client(['handler' => HandlerStack::create($mock)]);
|
||||
return new BusinessDataFetcher($client);
|
||||
}
|
||||
|
||||
it('returns BisCompanyDetails array on success', function () {
|
||||
$json = file_get_contents(__DIR__ . '/../Fixtures/sample-response.json');
|
||||
$fetcher = createFetcherWithMock(new Response(200, [], $json));
|
||||
|
||||
$results = $fetcher->getBusinessInformation('1639413-9');
|
||||
|
||||
expect($results)->toBeArray()
|
||||
->and($results)->toHaveCount(1)
|
||||
->and($results[0])->toBeInstanceOf(BisCompanyDetails::class)
|
||||
->and($results[0]->businessId)->toBe('1639413-9');
|
||||
});
|
||||
|
||||
it('throws on non-200 status code', function () {
|
||||
$fetcher = createFetcherWithMock(
|
||||
new Response(500, [], '{"error": "Internal Server Error"}')
|
||||
);
|
||||
|
||||
$fetcher->getBusinessInformation('1639413-9');
|
||||
})->throws(ApiResponseErrorException::class);
|
||||
|
||||
it('throws on malformed JSON', function () {
|
||||
$fetcher = createFetcherWithMock(
|
||||
new Response(200, [], 'not-json')
|
||||
);
|
||||
|
||||
$fetcher->getBusinessInformation('1639413-9');
|
||||
})->throws(JsonException::class);
|
||||
|
||||
it('throws when results key is missing', function () {
|
||||
$fetcher = createFetcherWithMock(
|
||||
new Response(200, [], '{"type": "test"}')
|
||||
);
|
||||
|
||||
$fetcher->getBusinessInformation('1639413-9');
|
||||
})->throws(ApiResponseErrorException::class);
|
||||
43
tests/Unit/v1/Dto/BisCompanyDetailsTest.php
Normal file
43
tests/Unit/v1/Dto/BisCompanyDetailsTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use CuyZ\Valinor\MapperBuilder;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyDetails;
|
||||
|
||||
it('hydrates from sample JSON fixture', function () {
|
||||
$json = json_decode(
|
||||
file_get_contents(__DIR__ . '/../../../Fixtures/sample-response.json'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
$mapper = (new MapperBuilder())
|
||||
->allowSuperfluousKeys()
|
||||
->mapper();
|
||||
|
||||
$result = $mapper->map(BisCompanyDetails::class, $json['results'][0]);
|
||||
|
||||
expect($result)
|
||||
->toBeInstanceOf(BisCompanyDetails::class)
|
||||
->and($result->businessId)->toBe('1639413-9')
|
||||
->and($result->registrationDate)->toBe('2001-04-03')
|
||||
->and($result->companyForm)->toBe('OY')
|
||||
->and($result->name)->toBe('Oy Example Ab')
|
||||
->and($result->names)->toHaveCount(1)
|
||||
->and($result->names[0]->name)->toBe('Oy Example Ab')
|
||||
->and($result->names[0]->getLanguageString())->toBe('finnish')
|
||||
->and($result->names[0]->getSourceText())->toBe('common')
|
||||
->and($result->addresses)->toHaveCount(1)
|
||||
->and($result->addresses[0]->street)->toBe('Testitie 1')
|
||||
->and($result->addresses[0]->postCode)->toBe('00100')
|
||||
->and($result->addresses[0]->city)->toBe('HELSINKI')
|
||||
->and($result->companyForms)->toHaveCount(1)
|
||||
->and($result->companyForms[0]->name)->toBe('Osakeyhtiö')
|
||||
->and($result->businessLines)->toHaveCount(1)
|
||||
->and($result->businessLines[0]->name)->toBe('Computer programming activities')
|
||||
->and($result->registeredEntries)->toHaveCount(1)
|
||||
->and($result->registeredEntries[0]->getAuthorityString())->toBe('Finnish Patent and Registration Office')
|
||||
->and($result->registeredEntries[0]->getRegisterString())->toBe('Trade Register')
|
||||
->and($result->contactDetails)->toHaveCount(1)
|
||||
->and($result->contactDetails[0]->value)->toBe('www.example.fi');
|
||||
});
|
||||
87
tests/Unit/v1/Dto/DtoHydrationTest.php
Normal file
87
tests/Unit/v1/Dto/DtoHydrationTest.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
use CuyZ\Valinor\MapperBuilder;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisAddress;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyBusinessIdChange;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyBusinessLine;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyContactDetail;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyForm;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyLanguage;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyLiquidation;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyName;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyRegisteredEntry;
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Dto\BisCompanyRegisteredOffice;
|
||||
|
||||
function valinorMapper(): CuyZ\Valinor\Mapper\TreeMapper
|
||||
{
|
||||
return (new MapperBuilder())
|
||||
->allowSuperfluousKeys()
|
||||
->mapper();
|
||||
}
|
||||
|
||||
it('hydrates all DTOs from arrays', function (string $class, array $data, string $property, mixed $expected) {
|
||||
$dto = valinorMapper()->map($class, $data);
|
||||
expect($dto)->toBeInstanceOf($class)
|
||||
->and($dto->$property)->toBe($expected);
|
||||
})->with([
|
||||
'BisAddress' => [
|
||||
BisAddress::class,
|
||||
['type' => 1, 'street' => 'Katu 1', 'postCode' => '00100', 'city' => 'HELSINKI', 'source' => 0, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'street',
|
||||
'Katu 1',
|
||||
],
|
||||
'BisCompanyName' => [
|
||||
BisCompanyName::class,
|
||||
['order' => 0, 'name' => 'Test Oy', 'source' => 1, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'name',
|
||||
'Test Oy',
|
||||
],
|
||||
'BisCompanyForm' => [
|
||||
BisCompanyForm::class,
|
||||
['name' => 'Osakeyhtiö', 'type' => 'OY', 'source' => 0, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'type',
|
||||
'OY',
|
||||
],
|
||||
'BisCompanyBusinessLine' => [
|
||||
BisCompanyBusinessLine::class,
|
||||
['order' => 0, 'name' => 'IT', 'source' => 2, 'version' => 1, 'language' => 'en', 'registrationDate' => '2020-01-01'],
|
||||
'name',
|
||||
'IT',
|
||||
],
|
||||
'BisCompanyLanguage' => [
|
||||
BisCompanyLanguage::class,
|
||||
['name' => 'Finnish', 'source' => 0, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'language',
|
||||
'fi',
|
||||
],
|
||||
'BisCompanyRegisteredEntry' => [
|
||||
BisCompanyRegisteredEntry::class,
|
||||
['authority' => 2, 'register' => 1, 'status' => 2, 'description' => 'Registered', 'language' => 'en', 'registrationDate' => '2020-01-01'],
|
||||
'description',
|
||||
'Registered',
|
||||
],
|
||||
'BisCompanyLiquidation' => [
|
||||
BisCompanyLiquidation::class,
|
||||
['name' => 'Bankruptcy', 'type' => 'K', 'source' => 0, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'name',
|
||||
'Bankruptcy',
|
||||
],
|
||||
'BisCompanyRegisteredOffice' => [
|
||||
BisCompanyRegisteredOffice::class,
|
||||
['order' => 0, 'name' => 'Helsinki', 'source' => 0, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'name',
|
||||
'Helsinki',
|
||||
],
|
||||
'BisCompanyContactDetail' => [
|
||||
BisCompanyContactDetail::class,
|
||||
['value' => 'www.test.fi', 'type' => 'Website', 'source' => 0, 'version' => 1, 'language' => 'fi', 'registrationDate' => '2020-01-01'],
|
||||
'value',
|
||||
'www.test.fi',
|
||||
],
|
||||
'BisCompanyBusinessIdChange' => [
|
||||
BisCompanyBusinessIdChange::class,
|
||||
['description' => 'Changed', 'reason' => '1', 'oldBusinessId' => '1234567-8', 'newBusinessId' => '8765432-1', 'source' => 0, 'language' => 'fi', 'change' => 5],
|
||||
'oldBusinessId',
|
||||
'1234567-8',
|
||||
],
|
||||
]);
|
||||
30
tests/Unit/v1/Traits/HasAuthorityTest.php
Normal file
30
tests/Unit/v1/Traits/HasAuthorityTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits\HasAuthority;
|
||||
|
||||
function makeAuthorityObject(int $authority): object
|
||||
{
|
||||
return new class ($authority) {
|
||||
use HasAuthority;
|
||||
|
||||
public function __construct(public int $authority)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('returns Tax Administration for authority 1', function () {
|
||||
expect(makeAuthorityObject(1)->getAuthorityString())->toBe('Tax Administration');
|
||||
});
|
||||
|
||||
it('returns Finnish Patent and Registration Office for authority 2', function () {
|
||||
expect(makeAuthorityObject(2)->getAuthorityString())->toBe('Finnish Patent and Registration Office');
|
||||
});
|
||||
|
||||
it('returns Population Register for authority 3', function () {
|
||||
expect(makeAuthorityObject(3)->getAuthorityString())->toBe('Population Register');
|
||||
});
|
||||
|
||||
it('returns unknown for unknown authority', function () {
|
||||
expect(makeAuthorityObject(99)->getAuthorityString())->toBe('unknown:99');
|
||||
});
|
||||
41
tests/Unit/v1/Traits/HasChangeTest.php
Normal file
41
tests/Unit/v1/Traits/HasChangeTest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits\HasChange;
|
||||
|
||||
function makeChangeObject(string|int|null $change): object
|
||||
{
|
||||
return new class ($change) {
|
||||
use HasChange;
|
||||
|
||||
public function __construct(public string|int|null $change)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('maps all integer change codes correctly', function (int $code, string $expected) {
|
||||
expect(makeChangeObject($code)->getChangeString())->toBe($expected);
|
||||
})->with([
|
||||
[2, 'Business ID removal'],
|
||||
[3, 'Combining of double IDs'],
|
||||
[5, 'ID changed'],
|
||||
[44, 'Fusion'],
|
||||
[45, 'Operator continuing VAT activities'],
|
||||
[46, 'Relation to predecessor'],
|
||||
[47, 'Division'],
|
||||
[48, 'Bankruptcy relationship'],
|
||||
[49, 'Operations continued by a private trader'],
|
||||
[57, 'Partial division'],
|
||||
]);
|
||||
|
||||
it('maps FUU string to Fusion', function () {
|
||||
expect(makeChangeObject('FUU')->getChangeString())->toBe('Fusion');
|
||||
});
|
||||
|
||||
it('maps DIF string to Division', function () {
|
||||
expect(makeChangeObject('DIF')->getChangeString())->toBe('Division');
|
||||
});
|
||||
|
||||
it('returns unknown for unrecognized change', function () {
|
||||
expect(makeChangeObject(999)->getChangeString())->toBe('unknown:999');
|
||||
});
|
||||
30
tests/Unit/v1/Traits/HasLanguageTest.php
Normal file
30
tests/Unit/v1/Traits/HasLanguageTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits\HasLanguage;
|
||||
|
||||
function makeLanguageObject(?string $language): object
|
||||
{
|
||||
return new class ($language) {
|
||||
use HasLanguage;
|
||||
|
||||
public function __construct(public ?string $language)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('returns finnish for fi', function () {
|
||||
expect(makeLanguageObject('fi')->getLanguageString())->toBe('finnish');
|
||||
});
|
||||
|
||||
it('returns english for en', function () {
|
||||
expect(makeLanguageObject('en')->getLanguageString())->toBe('english');
|
||||
});
|
||||
|
||||
it('returns swedish for sv', function () {
|
||||
expect(makeLanguageObject('sv')->getLanguageString())->toBe('swedish');
|
||||
});
|
||||
|
||||
it('returns unknown for unrecognized language', function () {
|
||||
expect(makeLanguageObject('de')->getLanguageString())->toBe('unknown:de');
|
||||
});
|
||||
35
tests/Unit/v1/Traits/HasRegisterTest.php
Normal file
35
tests/Unit/v1/Traits/HasRegisterTest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits\HasRegister;
|
||||
|
||||
function makeRegisterObject(?int $register): object
|
||||
{
|
||||
return new class ($register) {
|
||||
use HasRegister;
|
||||
|
||||
public function __construct(public ?int $register)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('maps all register codes correctly', function (int $code, string $expected) {
|
||||
expect(makeRegisterObject($code)->getRegisterString())->toBe($expected);
|
||||
})->with([
|
||||
[1, 'Trade Register'],
|
||||
[2, 'Register of Foundations'],
|
||||
[3, 'Register of Associations'],
|
||||
[4, 'Tax Administration'],
|
||||
[5, 'Prepayment Register'],
|
||||
[6, 'VAT Register'],
|
||||
[7, 'Employer Register'],
|
||||
[8, 'Register of bodies liable for tax on insurance premiums'],
|
||||
]);
|
||||
|
||||
it('returns unknown for unrecognized register', function () {
|
||||
expect(makeRegisterObject(99)->getRegisterString())->toBe('unknown:99');
|
||||
});
|
||||
|
||||
it('returns unknown for null register', function () {
|
||||
expect(makeRegisterObject(null)->getRegisterString())->toBe('unknown:');
|
||||
});
|
||||
34
tests/Unit/v1/Traits/HasSourceTest.php
Normal file
34
tests/Unit/v1/Traits/HasSourceTest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Ivuorinen\BusinessDataFetcher\v1\Traits\HasSource;
|
||||
|
||||
function makeSourceObject(?int $source): object
|
||||
{
|
||||
return new class ($source) {
|
||||
use HasSource;
|
||||
|
||||
public function __construct(public ?int $source)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('returns common for source 0', function () {
|
||||
expect(makeSourceObject(0)->getSourceText())->toBe('common');
|
||||
});
|
||||
|
||||
it('returns Finnish Patent and Registration Office for source 1', function () {
|
||||
expect(makeSourceObject(1)->getSourceText())->toBe('Finnish Patent and Registration Office');
|
||||
});
|
||||
|
||||
it('returns Tax Administration for source 2', function () {
|
||||
expect(makeSourceObject(2)->getSourceText())->toBe('Tax Administration');
|
||||
});
|
||||
|
||||
it('returns Business Information System for source 3', function () {
|
||||
expect(makeSourceObject(3)->getSourceText())->toBe('Business Information System');
|
||||
});
|
||||
|
||||
it('returns empty string for unknown source', function () {
|
||||
expect(makeSourceObject(99)->getSourceText())->toBe('');
|
||||
});
|
||||
73
tests/Unit/v3/ClientTest.php
Normal file
73
tests/Unit/v3/ClientTest.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Client as V3Client;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Dto\CompanySearchResult;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Dto\PostCodeEntry;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Exceptions\V3ApiException;
|
||||
|
||||
function createV3ClientWithMock(Response ...$responses): V3Client
|
||||
{
|
||||
$mock = new MockHandler($responses);
|
||||
$client = new Client(['handler' => HandlerStack::create($mock)]);
|
||||
return new V3Client($client);
|
||||
}
|
||||
|
||||
it('searches companies successfully', function () {
|
||||
$json = file_get_contents(__DIR__ . '/../../Fixtures/v3-search-response.json');
|
||||
$client = createV3ClientWithMock(new Response(200, [], $json));
|
||||
|
||||
$result = $client->searchCompanies(name: 'Example');
|
||||
|
||||
expect($result)
|
||||
->toBeInstanceOf(CompanySearchResult::class)
|
||||
->and($result->totalResults)->toBe(1)
|
||||
->and($result->companies)->toHaveCount(1)
|
||||
->and($result->companies[0]->businessId->value)->toBe('1639413-9')
|
||||
->and($result->companies[0]->names[0]->name)->toBe('Oy Example Ab')
|
||||
->and($result->companies[0]->tradeRegisterStatus)->toBe('2')
|
||||
->and($result->companies[0]->addresses[0]->street)->toBe('Testitie 1');
|
||||
});
|
||||
|
||||
it('throws V3ApiException on request failure for search', function () {
|
||||
$client = createV3ClientWithMock(new Response(500, [], '{"message":"error"}'));
|
||||
$client->searchCompanies(name: 'Test');
|
||||
})->throws(V3ApiException::class);
|
||||
|
||||
it('retrieves description as plain text', function () {
|
||||
$client = createV3ClientWithMock(new Response(200, [], 'Trade Register'));
|
||||
$result = $client->getDescription('REK', 'en');
|
||||
|
||||
expect($result)->toBe('Trade Register');
|
||||
});
|
||||
|
||||
it('throws V3ApiException on description failure', function () {
|
||||
$client = createV3ClientWithMock(new Response(400, [], '{"message":"bad request"}'));
|
||||
$client->getDescription('INVALID');
|
||||
})->throws(V3ApiException::class);
|
||||
|
||||
it('retrieves post codes', function () {
|
||||
$json = (string) json_encode([
|
||||
['postCode' => '00100', 'city' => 'HELSINKI', 'active' => true, 'languageCode' => '1', 'municipalityCode' => '091'],
|
||||
['postCode' => '00200', 'city' => 'HELSINKI', 'active' => true, 'languageCode' => '1', 'municipalityCode' => '091'],
|
||||
]);
|
||||
$client = createV3ClientWithMock(new Response(200, [], $json));
|
||||
|
||||
$results = $client->getPostCodes('fi');
|
||||
|
||||
expect($results)->toHaveCount(2)
|
||||
->and($results[0])->toBeInstanceOf(PostCodeEntry::class)
|
||||
->and($results[0]->postCode)->toBe('00100')
|
||||
->and($results[0]->city)->toBe('HELSINKI')
|
||||
->and($results[1]->postCode)->toBe('00200');
|
||||
});
|
||||
|
||||
it('returns stream for all companies download', function () {
|
||||
$client = createV3ClientWithMock(new Response(200, [], 'zip-content'));
|
||||
$stream = $client->getAllCompanies();
|
||||
|
||||
expect($stream->getContents())->toBe('zip-content');
|
||||
});
|
||||
53
tests/Unit/v3/Dto/CompanyTest.php
Normal file
53
tests/Unit/v3/Dto/CompanyTest.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use CuyZ\Valinor\MapperBuilder;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Dto\Company;
|
||||
use Ivuorinen\BusinessDataFetcher\v3\Dto\CompanySearchResult;
|
||||
|
||||
it('hydrates Company from v3 search response fixture', function () {
|
||||
$json = json_decode(
|
||||
file_get_contents(__DIR__ . '/../../../Fixtures/v3-search-response.json'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
$mapper = (new MapperBuilder())
|
||||
->allowSuperfluousKeys()
|
||||
->mapper();
|
||||
|
||||
$result = $mapper->map(CompanySearchResult::class, $json);
|
||||
|
||||
expect($result->totalResults)->toBe(1)
|
||||
->and($result->companies)->toHaveCount(1);
|
||||
|
||||
$company = $result->companies[0];
|
||||
|
||||
expect($company)->toBeInstanceOf(Company::class)
|
||||
->and($company->businessId->value)->toBe('1639413-9')
|
||||
->and($company->businessId->registrationDate)->toBe('2001-04-03')
|
||||
->and($company->euId)->not->toBeNull()
|
||||
->and($company->euId->value)->toBe('FIFOO123456')
|
||||
->and($company->names)->toHaveCount(1)
|
||||
->and($company->names[0]->name)->toBe('Oy Example Ab')
|
||||
->and($company->names[0]->type)->toBe('TOIM')
|
||||
->and($company->mainBusinessLine)->not->toBeNull()
|
||||
->and($company->mainBusinessLine->code)->toBe('62010')
|
||||
->and($company->mainBusinessLine->descriptions)->toHaveCount(1)
|
||||
->and($company->mainBusinessLine->descriptions[0]->description)->toBe('Computer programming activities')
|
||||
->and($company->website)->not->toBeNull()
|
||||
->and($company->website->url)->toBe('www.example.fi')
|
||||
->and($company->companyForms)->toHaveCount(1)
|
||||
->and($company->companyForms[0]->type)->toBe('OY')
|
||||
->and($company->companyForms[0]->descriptions)->toHaveCount(2)
|
||||
->and($company->registeredEntries)->toHaveCount(1)
|
||||
->and($company->registeredEntries[0]->register)->toBe('1')
|
||||
->and($company->registeredEntries[0]->authority)->toBe('2')
|
||||
->and($company->addresses)->toHaveCount(1)
|
||||
->and($company->addresses[0]->street)->toBe('Testitie 1')
|
||||
->and($company->addresses[0]->postOffices)->toHaveCount(1)
|
||||
->and($company->addresses[0]->postOffices[0]->city)->toBe('HELSINKI')
|
||||
->and($company->tradeRegisterStatus)->toBe('2')
|
||||
->and($company->status)->toBe('ACTIVE')
|
||||
->and($company->lastModified)->toBe('2023-06-15T10:30:00Z');
|
||||
});
|
||||
Reference in New Issue
Block a user