diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5512c35 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,87 @@ +name: CI + +on: + push: + branches: + - main + - master + pull_request: + +jobs: + compatibility: + name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - php: '8.1' + laravel: '10.x' + testbench: '^8.37' + pest: '^2.36' + - php: '8.2' + laravel: '11.x' + testbench: '^9.17' + pest: '^3.8' + - php: '8.3' + laravel: '12.x' + testbench: '^10.11' + pest: '^4.0' + - php: '8.4' + laravel: '13.x' + testbench: '^11.1' + pest: '^4.0' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + tools: composer:v2 + + - name: Validate composer.json + run: composer validate --strict + + - name: Install dependencies for matrix target + run: > + composer update + --prefer-dist + --no-interaction + --with-all-dependencies + "illuminate/contracts:${{ matrix.laravel }}" + "illuminate/http:${{ matrix.laravel }}" + "illuminate/support:${{ matrix.laravel }}" + "orchestra/testbench:${{ matrix.testbench }}" + "pestphp/pest:${{ matrix.pest }}" + + - name: Run Pest + run: vendor/bin/pest + + quality: + name: Quality + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction + + - name: Run PHPStan + run: composer analyse + + - name: Run Pint + run: vendor/bin/pint --test diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..83879ce --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/laravel-ares.iml b/.idea/laravel-ares.iml new file mode 100644 index 0000000..07b238f --- /dev/null +++ b/.idea/laravel-ares.iml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/laravel-idea-personal.xml b/.idea/laravel-idea-personal.xml new file mode 100644 index 0000000..425e121 --- /dev/null +++ b/.idea/laravel-idea-personal.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/laravel-idea.xml b/.idea/laravel-idea.xml new file mode 100644 index 0000000..d8e17dd --- /dev/null +++ b/.idea/laravel-idea.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3ce3588 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f00f124 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml new file mode 100644 index 0000000..c3b1223 --- /dev/null +++ b/.idea/php-docker-settings.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml new file mode 100644 index 0000000..b97138c --- /dev/null +++ b/.idea/php-test-framework.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..3ff3c62 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/stat.log b/.idea/stat.log new file mode 100644 index 0000000..fd45870 --- /dev/null +++ b/.idea/stat.log @@ -0,0 +1,447 @@ +{"ts":1777018029320,"action":"PROJECT_OPENED","tags":{"PROJECT_NAME":"laravel-ares"}} +{"ts":1777018032010,"action":"IDE_DEACTIVATED"} +{"ts":1777018032185,"action":"IDE_ACTIVATED"} +{"ts":1777018034155,"action":"FILE_OPENED","file":"/README.md","tags":{"FILE_LINE_OF_CODE":"3"}} +{"ts":1777018039572,"action":"IDE_DEACTIVATED"} +{"ts":1777018044489,"action":"IDE_ACTIVATED"} +{"ts":1777018044830,"action":"IDE_DEACTIVATED"} +{"ts":1777018048478,"action":"IDE_ACTIVATED"} +{"ts":1777018055159,"action":"FILE_CLOSED","file":"/README.md","tags":{"FILE_LINE_OF_CODE":"3"}} +{"ts":1777018055159,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018055597,"action":"IDE_DEACTIVATED"} +{"ts":1777018056271,"action":"IDE_ACTIVATED"} +{"ts":1777018057784,"action":"IDE_DEACTIVATED"} +{"ts":1777018059693,"action":"IDE_ACTIVATED"} +{"ts":1777018062595,"action":"IDE_DEACTIVATED"} +{"ts":1777018063341,"action":"IDE_ACTIVATED"} +{"ts":1777018064035,"action":"IDE_DEACTIVATED"} +{"ts":1777018066682,"action":"IDE_ACTIVATED"} +{"ts":1777018072099,"action":"IDE_DEACTIVATED"} +{"ts":1777018075130,"action":"IDE_ACTIVATED"} +{"ts":1777018087547,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777018087547,"action":"FILE_OPENED","file":"/src/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018089595,"action":"IDE_DEACTIVATED"} +{"ts":1777018091194,"action":"IDE_ACTIVATED"} +{"ts":1777018103743,"action":"FILE_CLOSED","file":"/src/Provider/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018103743,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777018108811,"action":"IDE_DEACTIVATED"} +{"ts":1777018112010,"action":"IDE_ACTIVATED"} +{"ts":1777018112973,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777018112973,"action":"FILE_OPENED","file":"/src/Provider/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018133981,"action":"FILE_CLOSED","file":"/src/Provider/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"44"}} +{"ts":1777018133981,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777018230070,"action":"IDE_DEACTIVATED"} +{"ts":1777018234449,"action":"IDE_ACTIVATED"} +{"ts":1777018238691,"action":"IDE_DEACTIVATED"} +{"ts":1777018277380,"action":"IDE_ACTIVATED"} +{"ts":1777018313501,"action":"IDE_DEACTIVATED"} +{"ts":1777018316719,"action":"IDE_ACTIVATED"} +{"ts":1777018321911,"action":"IDE_DEACTIVATED"} +{"ts":1777018324995,"action":"IDE_ACTIVATED"} +{"ts":1777018326792,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777018326792,"action":"FILE_OPENED","file":"/src/config/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018327278,"action":"IDE_DEACTIVATED"} +{"ts":1777018327283,"action":"IDE_ACTIVATED"} +{"ts":1777018332045,"action":"IDE_DEACTIVATED"} +{"ts":1777018332654,"action":"IDE_ACTIVATED"} +{"ts":1777018333695,"action":"IDE_DEACTIVATED"} +{"ts":1777018353049,"action":"IDE_ACTIVATED"} +{"ts":1777018354886,"action":"FILE_CLOSED","file":"/src/config/ares.php","tags":{"FILE_LINE_OF_CODE":"11"}} +{"ts":1777018354886,"action":"FILE_OPENED","file":"/src/Provider/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"44"}} +{"ts":1777018373610,"action":"IDE_DEACTIVATED"} +{"ts":1777018398175,"action":"IDE_ACTIVATED"} +{"ts":1777018402045,"action":"IDE_DEACTIVATED"} +{"ts":1777018468113,"action":"IDE_ACTIVATED"} +{"ts":1777018493116,"action":"FILE_CLOSED","file":"/src/Provider/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"42"}} +{"ts":1777018493116,"action":"FILE_OPENED","file":"/vendor/fakerphp/faker/src/Faker/Factory.php","tags":{"FILE_LINE_OF_CODE":"72"}} +{"ts":1777018510219,"action":"FILE_CLOSED","file":"/vendor/fakerphp/faker/src/Faker/Factory.php","tags":{"FILE_LINE_OF_CODE":"72"}} +{"ts":1777018510219,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"42"}} +{"ts":1777018528844,"action":"IDE_DEACTIVATED"} +{"ts":1777018537441,"action":"IDE_ACTIVATED"} +{"ts":1777018544284,"action":"IDE_DEACTIVATED"} +{"ts":1777018547850,"action":"IDE_ACTIVATED"} +{"ts":1777018575642,"action":"IDE_DEACTIVATED"} +{"ts":1777018577349,"action":"IDE_ACTIVATED"} +{"ts":1777018580554,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"53"}} +{"ts":1777018580554,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018581057,"action":"IDE_DEACTIVATED"} +{"ts":1777018581843,"action":"IDE_ACTIVATED"} +{"ts":1777018583075,"action":"IDE_DEACTIVATED"} +{"ts":1777018584377,"action":"IDE_ACTIVATED"} +{"ts":1777018585475,"action":"IDE_DEACTIVATED"} +{"ts":1777018590399,"action":"IDE_ACTIVATED"} +{"ts":1777018593800,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018593801,"action":"FILE_OPENED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018594502,"action":"IDE_DEACTIVATED"} +{"ts":1777018595187,"action":"IDE_ACTIVATED"} +{"ts":1777018595942,"action":"IDE_DEACTIVATED"} +{"ts":1777018597489,"action":"IDE_ACTIVATED"} +{"ts":1777018598855,"action":"IDE_DEACTIVATED"} +{"ts":1777018603497,"action":"IDE_ACTIVATED"} +{"ts":1777018607693,"action":"FILE_CLOSED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777018607693,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018608467,"action":"IDE_DEACTIVATED"} +{"ts":1777018609274,"action":"IDE_ACTIVATED"} +{"ts":1777018609915,"action":"IDE_DEACTIVATED"} +{"ts":1777018611296,"action":"IDE_ACTIVATED"} +{"ts":1777018613568,"action":"IDE_DEACTIVATED"} +{"ts":1777018617224,"action":"IDE_ACTIVATED"} +{"ts":1777018621609,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777018621609,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018624343,"action":"IDE_DEACTIVATED"} +{"ts":1777018626174,"action":"IDE_ACTIVATED"} +{"ts":1777018627715,"action":"IDE_DEACTIVATED"} +{"ts":1777018631477,"action":"IDE_ACTIVATED"} +{"ts":1777018635080,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777018635080,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018637769,"action":"IDE_DEACTIVATED"} +{"ts":1777018639204,"action":"IDE_ACTIVATED"} +{"ts":1777018639484,"action":"IDE_DEACTIVATED"} +{"ts":1777018640805,"action":"IDE_ACTIVATED"} +{"ts":1777018644186,"action":"IDE_DEACTIVATED"} +{"ts":1777018644855,"action":"IDE_ACTIVATED"} +{"ts":1777018649634,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777018649634,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777018651060,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777018651060,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777018654780,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777018654780,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777018662227,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777018662227,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"53"}} +{"ts":1777018675160,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"53"}} +{"ts":1777018675160,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018677271,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018677271,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777018678077,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777018678077,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777018678813,"action":"IDE_DEACTIVATED"} +{"ts":1777018689917,"action":"IDE_ACTIVATED"} +{"ts":1777018695169,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777018695169,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018695743,"action":"IDE_DEACTIVATED"} +{"ts":1777018696614,"action":"IDE_ACTIVATED"} +{"ts":1777018697630,"action":"IDE_DEACTIVATED"} +{"ts":1777018701656,"action":"IDE_ACTIVATED"} +{"ts":1777018703356,"action":"IDE_DEACTIVATED"} +{"ts":1777018711636,"action":"IDE_ACTIVATED"} +{"ts":1777018715930,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777018715930,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php ","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018717197,"action":"IDE_DEACTIVATED"} +{"ts":1777018718847,"action":"IDE_ACTIVATED"} +{"ts":1777018719253,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777018725076,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"46"}} +{"ts":1777018725076,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018726256,"action":"IDE_DEACTIVATED"} +{"ts":1777018728892,"action":"IDE_ACTIVATED"} +{"ts":1777018731951,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"9"}} +{"ts":1777018731951,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php ","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018732580,"action":"IDE_DEACTIVATED"} +{"ts":1777018734048,"action":"IDE_ACTIVATED"} +{"ts":1777018734426,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"9"}} +{"ts":1777018736787,"action":"IDE_DEACTIVATED"} +{"ts":1777018743456,"action":"IDE_ACTIVATED"} +{"ts":1777018746932,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"9"}} +{"ts":1777018746932,"action":"FILE_OPENED","file":"/example/blade-usage.blade.php ","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018747587,"action":"IDE_DEACTIVATED"} +{"ts":1777018749041,"action":"IDE_ACTIVATED"} +{"ts":1777018749485,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777018752139,"action":"IDE_DEACTIVATED"} +{"ts":1777018756329,"action":"IDE_ACTIVATED"} +{"ts":1777018759544,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018759544,"action":"FILE_OPENED","file":"/example/example/livewire-usage.php ","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018760352,"action":"IDE_DEACTIVATED"} +{"ts":1777018761965,"action":"IDE_ACTIVATED"} +{"ts":1777018762428,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018765012,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018765012,"action":"FILE_OPENED","file":"/example/blade-usage.blade.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018767029,"action":"FILE_CLOSED","file":"/example/blade-usage.blade.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018767029,"action":"FILE_OPENED","file":"/example/example/livewire-usage.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018774372,"action":"FILE_CLOSED","file":"/example/livewire-usage.php","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777018774372,"action":"FILE_OPENED","file":"/example/blade-usage.blade.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018777450,"action":"IDE_DEACTIVATED"} +{"ts":1777018783268,"action":"IDE_ACTIVATED"} +{"ts":1777018788882,"action":"IDE_DEACTIVATED"} +{"ts":1777018885223,"action":"IDE_ACTIVATED"} +{"ts":1777018887078,"action":"FILE_CLOSED","file":"/example/blade-usage.blade.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018887078,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018892017,"action":"FILE_CLOSED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018892017,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"53"}} +{"ts":1777018895811,"action":"IDE_DEACTIVATED"} +{"ts":1777018905336,"action":"IDE_ACTIVATED"} +{"ts":1777018911802,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"53"}} +{"ts":1777018911802,"action":"FILE_OPENED","file":"/config/ares.php","tags":{"FILE_LINE_OF_CODE":"11"}} +{"ts":1777018913619,"action":"IDE_DEACTIVATED"} +{"ts":1777018928890,"action":"IDE_ACTIVATED"} +{"ts":1777018934962,"action":"IDE_DEACTIVATED"} +{"ts":1777018936602,"action":"IDE_ACTIVATED"} +{"ts":1777018940235,"action":"FILE_CLOSED","file":"/config/ares.php","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777018940235,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777018944442,"action":"IDE_DEACTIVATED"} +{"ts":1777018946405,"action":"IDE_ACTIVATED"} +{"ts":1777018949743,"action":"IDE_DEACTIVATED"} +{"ts":1777018952199,"action":"IDE_ACTIVATED"} +{"ts":1777018953302,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777018953302,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018955196,"action":"IDE_DEACTIVATED"} +{"ts":1777018957889,"action":"IDE_ACTIVATED"} +{"ts":1777018959648,"action":"IDE_DEACTIVATED"} +{"ts":1777018962837,"action":"IDE_ACTIVATED"} +{"ts":1777018968102,"action":"IDE_DEACTIVATED"} +{"ts":1777018972680,"action":"IDE_ACTIVATED"} +{"ts":1777018978538,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777018978538,"action":"FILE_OPENED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777018979598,"action":"IDE_DEACTIVATED"} +{"ts":1777018981508,"action":"IDE_ACTIVATED"} +{"ts":1777018987646,"action":"IDE_DEACTIVATED"} +{"ts":1777018994079,"action":"IDE_ACTIVATED"} +{"ts":1777019000135,"action":"FILE_CLOSED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"100"}} +{"ts":1777019000135,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777019000660,"action":"IDE_DEACTIVATED"} +{"ts":1777019002610,"action":"IDE_ACTIVATED"} +{"ts":1777019008266,"action":"IDE_DEACTIVATED"} +{"ts":1777019009959,"action":"IDE_ACTIVATED"} +{"ts":1777019012717,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019012717,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019014821,"action":"IDE_DEACTIVATED"} +{"ts":1777019015849,"action":"IDE_ACTIVATED"} +{"ts":1777019019095,"action":"IDE_DEACTIVATED"} +{"ts":1777019031131,"action":"IDE_ACTIVATED"} +{"ts":1777019033645,"action":"IDE_DEACTIVATED"} +{"ts":1777019036567,"action":"IDE_ACTIVATED"} +{"ts":1777019037786,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019037786,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019041693,"action":"IDE_DEACTIVATED"} +{"ts":1777019045798,"action":"IDE_ACTIVATED"} +{"ts":1777019047669,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777019047669,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777019051702,"action":"IDE_DEACTIVATED"} +{"ts":1777019056534,"action":"IDE_ACTIVATED"} +{"ts":1777019057843,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777019057843,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"46"}} +{"ts":1777019058363,"action":"IDE_DEACTIVATED"} +{"ts":1777019060044,"action":"IDE_ACTIVATED"} +{"ts":1777019062135,"action":"IDE_DEACTIVATED"} +{"ts":1777019074832,"action":"IDE_ACTIVATED"} +{"ts":1777019076210,"action":"IDE_DEACTIVATED"} +{"ts":1777019077884,"action":"IDE_ACTIVATED"} +{"ts":1777019079969,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777019079969,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777019081165,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777019081165,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019081955,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019081955,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777019082461,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777019082461,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019083105,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019083105,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777019090412,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777019090412,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777019092258,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777019092258,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"53"}} +{"ts":1777019108994,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777019108994,"action":"FILE_OPENED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"100"}} +{"ts":1777019170014,"action":"FILE_CLOSED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777019170014,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019171494,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019171494,"action":"FILE_OPENED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777019196556,"action":"FILE_CLOSED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777019196556,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777019197206,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777019197206,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777019214401,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"20"}} +{"ts":1777019214401,"action":"FILE_OPENED","file":"/example/blade-usage.blade.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019216811,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019232776,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019232776,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777019234725,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777019234725,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777019236095,"action":"FILE_CLOSED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777019236095,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"33"}} +{"ts":1777019236789,"action":"IDE_DEACTIVATED"} +{"ts":1777019244155,"action":"IDE_ACTIVATED"} +{"ts":1777019247907,"action":"IDE_DEACTIVATED"} +{"ts":1777019250012,"action":"IDE_ACTIVATED"} +{"ts":1777019251287,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"9"}} +{"ts":1777019251287,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"0"}} +{"ts":1777019254475,"action":"FILE_CLOSED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"9"}} +{"ts":1777019254475,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777019256444,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"38"}} +{"ts":1777019256444,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019262571,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777019262571,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777019269464,"action":"IDE_DEACTIVATED"} +{"ts":1777019283530,"action":"IDE_ACTIVATED"} +{"ts":1777019293237,"action":"PROJECT_CLOSED","tags":{"PROJECT_NAME":"laravel-ares"}} +{"ts":1777019384663,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777019387912,"action":"PROJECT_OPENED","tags":{"PROJECT_NAME":"laravel-ares"}} +{"ts":1777019487056,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777019487056,"action":"FILE_OPENED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777019536838,"action":"FILE_CLOSED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"101"}} +{"ts":1777019536838,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777019565536,"action":"IDE_DEACTIVATED"} +{"ts":1777019565624,"action":"IDE_ACTIVATED"} +{"ts":1777019612835,"action":"IDE_DEACTIVATED"} +{"ts":1777020270882,"action":"IDE_ACTIVATED"} +{"ts":1777020278745,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"56"}} +{"ts":1777020332358,"action":"FILE_OPENED","file":"/README.md","tags":{"FILE_LINE_OF_CODE":"175"}} +{"ts":1777020368122,"action":"FILE_CLOSED","file":"/README.md","tags":{"FILE_LINE_OF_CODE":"175"}} +{"ts":1777020368122,"action":"FILE_OPENED","file":"/tests/Pest.php","tags":{"FILE_LINE_OF_CODE":"8"}} +{"ts":1777020373748,"action":"FILE_CLOSED","file":"/tests/Pest.php","tags":{"FILE_LINE_OF_CODE":"8"}} +{"ts":1777020373748,"action":"FILE_OPENED","file":"/tests/Unit/CompanyDataTest.php","tags":{"FILE_LINE_OF_CODE":"36"}} +{"ts":1777020380970,"action":"FILE_CLOSED","file":"/tests/Unit/CompanyDataTest.php","tags":{"FILE_LINE_OF_CODE":"36"}} +{"ts":1777020380970,"action":"FILE_OPENED","file":"/tests/Feature/AresClientTest.php","tags":{"FILE_LINE_OF_CODE":"100"}} +{"ts":1777020419518,"action":"FILE_CLOSED","file":"/tests/Feature/AresClientTest.php","tags":{"FILE_LINE_OF_CODE":"100"}} +{"ts":1777020419518,"action":"FILE_OPENED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"114"}} +{"ts":1777020421766,"action":"FILE_CLOSED","file":"/src/Services/AresClient.php","tags":{"FILE_LINE_OF_CODE":"114"}} +{"ts":1777020421766,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777020442659,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777020442659,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"15"}} +{"ts":1777020444334,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"15"}} +{"ts":1777020444334,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777020445520,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"34"}} +{"ts":1777020445520,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020457495,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020457495,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020463463,"action":"FILE_CLOSED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020463463,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020470814,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020470814,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777020475221,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"41"}} +{"ts":1777020475221,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"61"}} +{"ts":1777020533935,"action":"IDE_DEACTIVATED"} +{"ts":1777020640646,"action":"IDE_ACTIVATED"} +{"ts":1777020667642,"action":"IDE_DEACTIVATED"} +{"ts":1777020669050,"action":"IDE_ACTIVATED"} +{"ts":1777020670702,"action":"IDE_DEACTIVATED"} +{"ts":1777020673142,"action":"IDE_ACTIVATED"} +{"ts":1777020675969,"action":"IDE_DEACTIVATED"} +{"ts":1777020677239,"action":"IDE_ACTIVATED"} +{"ts":1777020736932,"action":"IDE_DEACTIVATED"} +{"ts":1777020866482,"action":"IDE_ACTIVATED"} +{"ts":1777020869630,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"63"}} +{"ts":1777020902352,"action":"FILE_OPENED","file":"/tests/Pest.php","tags":{"FILE_LINE_OF_CODE":"8"}} +{"ts":1777020903836,"action":"FILE_CLOSED","file":"/tests/Pest.php","tags":{"FILE_LINE_OF_CODE":"8"}} +{"ts":1777020903836,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020904961,"action":"FILE_CLOSED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020904961,"action":"FILE_OPENED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020908392,"action":"FILE_CLOSED","file":"/resources/lang/cs/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777020908392,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"47"}} +{"ts":1777020933493,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"47"}} +{"ts":1777020943119,"action":"IDE_DEACTIVATED"} +{"ts":1777020944410,"action":"IDE_ACTIVATED"} +{"ts":1777020950020,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"56"}} +{"ts":1777020961763,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"56"}} +{"ts":1777020961763,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"21"}} +{"ts":1777021032651,"action":"IDE_DEACTIVATED"} +{"ts":1777021267247,"action":"IDE_ACTIVATED"} +{"ts":1777021271933,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777021332150,"action":"IDE_DEACTIVATED"} +{"ts":1777021859583,"action":"IDE_ACTIVATED"} +{"ts":1777021930823,"action":"IDE_DEACTIVATED"} +{"ts":1777021968339,"action":"IDE_ACTIVATED"} +{"ts":1777022056835,"action":"IDE_DEACTIVATED"} +{"ts":1777022165233,"action":"IDE_ACTIVATED"} +{"ts":1777022201040,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777022240302,"action":"IDE_DEACTIVATED"} +{"ts":1777022495899,"action":"IDE_ACTIVATED"} +{"ts":1777022501167,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777022501167,"action":"FILE_OPENED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"138"}} +{"ts":1777022502238,"action":"FILE_CLOSED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"138"}} +{"ts":1777022502238,"action":"FILE_OPENED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"75"}} +{"ts":1777022514264,"action":"FILE_CLOSED","file":"/src/Data/CompanyData.php","tags":{"FILE_LINE_OF_CODE":"75"}} +{"ts":1777022514264,"action":"FILE_OPENED","file":"/src/Data/DeliveryAddressData.php","tags":{"FILE_LINE_OF_CODE":"49"}} +{"ts":1777022518905,"action":"FILE_CLOSED","file":"/src/Data/DeliveryAddressData.php","tags":{"FILE_LINE_OF_CODE":"49"}} +{"ts":1777022518905,"action":"FILE_OPENED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"138"}} +{"ts":1777022524333,"action":"FILE_CLOSED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"138"}} +{"ts":1777022524333,"action":"FILE_OPENED","file":"/phpstan.neon.dist","tags":{"FILE_LINE_OF_CODE":"19"}} +{"ts":1777022558823,"action":"IDE_DEACTIVATED"} +{"ts":1777022578297,"action":"IDE_ACTIVATED"} +{"ts":1777022595706,"action":"IDE_DEACTIVATED"} +{"ts":1777022643025,"action":"IDE_ACTIVATED"} +{"ts":1777022694803,"action":"IDE_DEACTIVATED"} +{"ts":1777022798895,"action":"IDE_ACTIVATED"} +{"ts":1777023046053,"action":"IDE_DEACTIVATED"} +{"ts":1777023264942,"action":"IDE_ACTIVATED"} +{"ts":1777023305628,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777023576283,"action":"IDE_DEACTIVATED"} +{"ts":1777023576357,"action":"IDE_ACTIVATED"} +{"ts":1777023600030,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777023600030,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"61"}} +{"ts":1777023637317,"action":"IDE_DEACTIVATED"} +{"ts":1777023650285,"action":"IDE_ACTIVATED"} +{"ts":1777023656612,"action":"IDE_DEACTIVATED"} +{"ts":1777023660580,"action":"IDE_ACTIVATED"} +{"ts":1777023666262,"action":"IDE_DEACTIVATED"} +{"ts":1777023667798,"action":"IDE_ACTIVATED"} +{"ts":1777023699997,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"61"}} +{"ts":1777023705701,"action":"FILE_OPENED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"61"}} +{"ts":1777023780977,"action":"IDE_DEACTIVATED"} +{"ts":1777023783317,"action":"IDE_ACTIVATED"} +{"ts":1777023789748,"action":"IDE_DEACTIVATED"} +{"ts":1777023789826,"action":"IDE_ACTIVATED"} +{"ts":1777023813462,"action":"FILE_CLOSED","file":"/composer.json","tags":{"FILE_LINE_OF_CODE":"61"}} +{"ts":1777023813462,"action":"FILE_OPENED","file":"/phpstan.neon.dist","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777023825527,"action":"FILE_CLOSED","file":"/phpstan.neon.dist","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777023825527,"action":"FILE_OPENED","file":"/.phpstan/resultCache.php","tags":{"FILE_LINE_OF_CODE":"4584"}} +{"ts":1777023830247,"action":"FILE_CLOSED","file":"/.phpstan/resultCache.php","tags":{"FILE_LINE_OF_CODE":"4584"}} +{"ts":1777023830247,"action":"FILE_OPENED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777023832022,"action":"FILE_CLOSED","file":"/resources/lang/en/ares.php","tags":{"FILE_LINE_OF_CODE":"10"}} +{"ts":1777023832022,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777023833639,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777023833639,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"26"}} +{"ts":1777023834669,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"26"}} +{"ts":1777023834669,"action":"FILE_OPENED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"139"}} +{"ts":1777023835307,"action":"FILE_CLOSED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"139"}} +{"ts":1777023835307,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"26"}} +{"ts":1777023836084,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"26"}} +{"ts":1777023836084,"action":"FILE_OPENED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777023837237,"action":"FILE_CLOSED","file":"/src/Commands/TestAresCommand.php","tags":{"FILE_LINE_OF_CODE":"54"}} +{"ts":1777023837237,"action":"FILE_OPENED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"26"}} +{"ts":1777023837800,"action":"FILE_CLOSED","file":"/src/Contracts/AresClientInterface.php","tags":{"FILE_LINE_OF_CODE":"26"}} +{"ts":1777023837800,"action":"FILE_OPENED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"139"}} +{"ts":1777023846277,"action":"FILE_CLOSED","file":"/src/Data/AddressData.php","tags":{"FILE_LINE_OF_CODE":"139"}} +{"ts":1777023846278,"action":"FILE_OPENED","file":"/src/Enums/RegistrationSourceState.php","tags":{"FILE_LINE_OF_CODE":"18"}} +{"ts":1777023847379,"action":"FILE_CLOSED","file":"/src/Enums/RegistrationSourceState.php","tags":{"FILE_LINE_OF_CODE":"18"}} +{"ts":1777023847379,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"17"}} +{"ts":1777023847694,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupFailed.php","tags":{"FILE_LINE_OF_CODE":"17"}} +{"ts":1777023847694,"action":"FILE_OPENED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777023848765,"action":"FILE_CLOSED","file":"/src/Events/CompanyLookupSucceeded.php","tags":{"FILE_LINE_OF_CODE":"13"}} +{"ts":1777023848765,"action":"FILE_OPENED","file":"/src/Exceptions/CompanyNotFoundException.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777023849079,"action":"FILE_CLOSED","file":"/src/Exceptions/CompanyNotFoundException.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777023849079,"action":"FILE_OPENED","file":"/src/Exceptions/InvalidApiResponseException.php","tags":{"FILE_LINE_OF_CODE":"21"}} +{"ts":1777023849314,"action":"FILE_CLOSED","file":"/src/Exceptions/InvalidApiResponseException.php","tags":{"FILE_LINE_OF_CODE":"21"}} +{"ts":1777023849314,"action":"FILE_OPENED","file":"/src/Exceptions/InvalidIcException.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777023850081,"action":"FILE_CLOSED","file":"/src/Exceptions/InvalidIcException.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777023850081,"action":"FILE_OPENED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777023850796,"action":"FILE_CLOSED","file":"/src/Facades/Ares.php","tags":{"FILE_LINE_OF_CODE":"27"}} +{"ts":1777023850796,"action":"FILE_OPENED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"84"}} +{"ts":1777023852737,"action":"FILE_CLOSED","file":"/src/Providers/AresServiceProvider.php","tags":{"FILE_LINE_OF_CODE":"84"}} +{"ts":1777023852737,"action":"FILE_OPENED","file":"/src/Exceptions/InvalidIcException.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777023860757,"action":"FILE_CLOSED","file":"/src/Exceptions/InvalidIcException.php","tags":{"FILE_LINE_OF_CODE":"16"}} +{"ts":1777023860757,"action":"FILE_OPENED","file":"/tests/Feature/AresClientTest.php","tags":{"FILE_LINE_OF_CODE":"244"}} +{"ts":1777023861028,"action":"FILE_CLOSED","file":"/tests/Feature/AresClientTest.php","tags":{"FILE_LINE_OF_CODE":"244"}} +{"ts":1777023861028,"action":"FILE_OPENED","file":"/tests/Feature/TestAresCommandTest.php","tags":{"FILE_LINE_OF_CODE":"140"}} +{"ts":1777023861371,"action":"FILE_CLOSED","file":"/tests/Feature/TestAresCommandTest.php","tags":{"FILE_LINE_OF_CODE":"140"}} +{"ts":1777023861371,"action":"FILE_OPENED","file":"/tests/Pest.php","tags":{"FILE_LINE_OF_CODE":"8"}} +{"ts":1777023862214,"action":"FILE_CLOSED","file":"/tests/Pest.php","tags":{"FILE_LINE_OF_CODE":"8"}} +{"ts":1777023862214,"action":"FILE_OPENED","file":"/tests/Unit/CompanyDataTest.php","tags":{"FILE_LINE_OF_CODE":"116"}} +{"ts":1777023879329,"action":"FILE_CLOSED","file":"/tests/Unit/CompanyDataTest.php","tags":{"FILE_LINE_OF_CODE":"116"}} +{"ts":1777023879329,"action":"FILE_OPENED","file":"/README.md","tags":{"FILE_LINE_OF_CODE":"177"}} +{"ts":1777023893214,"action":"IDE_DEACTIVATED"} +{"ts":1777023893283,"action":"IDE_ACTIVATED"} +{"ts":1777023912506,"action":"IDE_DEACTIVATED"} +{"ts":1777023959432,"action":"IDE_ACTIVATED"} +{"ts":1777023964248,"action":"IDE_DEACTIVATED"} +{"ts":1777023977962,"action":"IDE_ACTIVATED"} +{"ts":1777023990966,"action":"IDE_DEACTIVATED"} +{"ts":1777024003962,"action":"IDE_ACTIVATED"} +{"ts":1777024031574,"action":"IDE_DEACTIVATED"} +{"ts":1777024031653,"action":"IDE_ACTIVATED"} +{"ts":1777024244369,"action":"IDE_DEACTIVATED"} +{"ts":1777024244737,"action":"IDE_ACTIVATED"} +{"ts":1777024281303,"action":"FILE_CLOSED","file":"/README.md","tags":{"FILE_LINE_OF_CODE":"177"}} +{"ts":1777024281303,"action":"FILE_OPENED","file":"/tests/Unit/ServiceProviderTest.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777024281827,"action":"FILE_CLOSED","file":"/tests/Unit/ServiceProviderTest.php","tags":{"FILE_LINE_OF_CODE":"12"}} +{"ts":1777024281827,"action":"FILE_OPENED","file":"/tests/Unit/CompanyDataTest.php","tags":{"FILE_LINE_OF_CODE":"116"}} +{"ts":1777024292789,"action":"FILE_CLOSED","file":"/tests/Unit/CompanyDataTest.php","tags":{"FILE_LINE_OF_CODE":"116"}} +{"ts":1777024292789,"action":"FILE_OPENED","file":"/.github/workflows/ci.yml","tags":{"FILE_LINE_OF_CODE":"88"}} diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 92de2b8..c9bbfff 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,176 @@ # laravel-ares -Laravel package for ARES (Czech business register) API + +`laravel-ares` is a Laravel package for the Czech ARES business register API. It provides a typed client, a facade, configurable caching, lookup events, ICO validation, static analysis support, and an artisan command for diagnostics. + +## Features + +- Typed public API through `AresClientInterface` +- `Ares` facade with convenience methods for common workflows +- Structured domain objects instead of one large flat payload object +- Configurable caching and HTTP timeouts +- Events for successful and failed lookups +- ICO normalization and checksum validation +- Explicit exceptions for invalid ICO and missing companies +- Pest test suite, PHPStan configuration, and GitHub Actions CI + +## Requirements + +- PHP 8.1+ +- Laravel 10, 11, 12, or 13 + +## Installation + +Install the package with Composer: + +```bash +composer require nyoncode/laravel-ares +``` + +Publish the configuration file if you want local overrides: + +```bash +php artisan vendor:publish --tag=laravel-ares::config +``` + +## Configuration + +| Key | Default | Description | +| --- | --- | --- | +| `api_url` | `https://ares.gov.cz/ekonomicke-subjekty-v-be/rest` | Base URL for the ARES REST API | +| `cache_ttl` | `86400` | Cache lifetime for successful lookups in seconds | +| `log_channel` | `stack` | Laravel log channel used for client errors | +| `http_options.timeout` | `5.0` | Request timeout in seconds | +| `http_options.connect_timeout` | `3.0` | Connection timeout in seconds | + +## Usage + +Use dependency injection when you want explicit contracts: + +```php +use NyonCode\Ares\Contracts\AresClientInterface; + +final class CompanyLookupService +{ + public function __construct( + private readonly AresClientInterface $ares, + ) {} + + public function companyName(string $ic): ?string + { + return $this->ares->findCompany($ic)?->name; + } +} +``` + +Use the facade for concise application code: + +```php +use NyonCode\Ares\Facades\Ares; + +$normalizedIc = Ares::normalizeIc('27 074 358'); +$company = Ares::findCompanyOrFail($normalizedIc); + +dump($company->name); +dump($company->registeredOffice?->formatted); +dump($company->registration->naceCodes); +``` + +Public API: + +- `findCompany(string $ic): ?CompanyData` +- `findCompanyRaw(string $ic): ?array` +- `findCompanyOrFail(string $ic): CompanyData` +- `forgetCompany(string $ic): bool` +- `isValidIc(string $ic): bool` +- `normalizeIc(string $ic): string` + +## Domain Model + +Successful lookups return `NyonCode\Ares\Data\CompanyData`: + +```php +final class CompanyData +{ + public readonly string $ic; + public readonly string $name; + public readonly ?string $dic; + public readonly ?string $dicSkDph; + public readonly ?AddressData $registeredOffice; + public readonly ?DeliveryAddressData $deliveryAddress; + public readonly RegistrationData $registration; + public readonly array $rawData; +} +``` + +Related DTOs: + +- `AddressData` models the registered office +- `DeliveryAddressData` models the mailing address +- `RegistrationData` groups legal form, dates, source, file mark, NACE codes, and source statuses +- `RegistrationStatusData` represents one registry source status +- `RegistrationSourceState` is a typed enum for known ARES status values + +`rawData` remains available as an escape hatch for fields the package does not map yet. + +## Exceptions + +The fail-fast API throws explicit domain exceptions: + +- `NyonCode\Ares\Exceptions\InvalidIcException` +- `NyonCode\Ares\Exceptions\CompanyNotFoundException` + +Malformed payloads are treated as failed lookups internally and surface through the failure event path. + +## Events + +The package dispatches: + +- `NyonCode\Ares\Events\CompanyLookupSucceeded` +- `NyonCode\Ares\Events\CompanyLookupFailed` + +## Artisan Command + +The package includes an artisan helper for manual verification: + +```bash +php artisan ares:test 27074358 +``` + +The command renders a compact company summary including DIC, source, dates, registered office, delivery address, and register metadata. + +## Quality Gates + +Run the automated tests: + +```bash +composer test +``` + +Run static analysis: + +```bash +composer analyse +``` + +Run the formatter: + +```bash +composer format +``` + +The repository includes a GitHub Actions workflow for: + +- PHP/Laravel compatibility matrix tests +- PHPStan on the quality lane +- Pint on the quality lane + +## Development Notes + +- Successful lookups are cached under the `ares:company:{ic}` key format. +- Invalid ICO values are rejected before any HTTP request is sent. +- `forgetCompany()` invalidates cache entries using normalized ICO values. +- Failed HTTP responses, malformed payloads, and transport exceptions all dispatch `CompanyLookupFailed`. + +## License + +The package is open-sourced under the [MIT license](LICENSE). diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cb80a35 --- /dev/null +++ b/composer.json @@ -0,0 +1,60 @@ +{ + "name": "nyoncode/laravel-ares", + "description": "Laravel package for the Czech ARES business register API with caching, events, validation and artisan tooling.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "NyonCode", + "email": "info@nyoncode.com" + } + ], + "require": { + "php": "^8.1", + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "illuminate/http": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "guzzlehttp/guzzle": "^7.0", + "nyoncode/laravel-package-toolkit": "^2.0" + }, + "require-dev": { + "larastan/larastan": "^2.9|^3.0", + "laravel/pint": "^1.13", + "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", + "pestphp/pest": "^2.0|^3.0|^4.0", + "phpstan/phpstan": "^2.1" + }, + "autoload": { + "psr-4": { + "NyonCode\\Ares\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "NyonCode\\Ares\\Tests\\": "tests/" + } + }, + "scripts": { + "analyse": "vendor/bin/phpstan analyse -c phpstan.neon.dist --memory-limit=1G", + "test": "vendor/bin/pest", + "test:parallel": "vendor/bin/pest --parallel", + "format": "vendor/bin/pint" + }, + "extra": { + "laravel": { + "providers": [ + "NyonCode\\Ares\\Providers\\AresServiceProvider" + ], + "aliases": { + "Ares": "NyonCode\\Ares\\Facades\\Ares" + } + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + } + } +} diff --git a/config/ares.php b/config/ares.php new file mode 100644 index 0000000..ef2f512 --- /dev/null +++ b/config/ares.php @@ -0,0 +1,34 @@ + env('ARES_API_URL', 'https://ares.gov.cz/ekonomicke-subjekty-v-be/rest'), + + /* + |-------------------------------------------------------------------------- + | Cache TTL (seconds) + |-------------------------------------------------------------------------- + */ + 'cache_ttl' => env('ARES_CACHE_TTL', 86400), + + /* + |-------------------------------------------------------------------------- + | Logging channel + |-------------------------------------------------------------------------- + */ + 'log_channel' => env('ARES_LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Guzzle HTTP options + |-------------------------------------------------------------------------- + */ + 'http_options' => [ + 'timeout' => 5.0, + 'connect_timeout' => 3.0, + ], +]; diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..0ebc249 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,12 @@ +includes: + - vendor/larastan/larastan/extension.neon + +parameters: + paths: + - src + level: max + tmpDir: .phpstan + treatPhpDocTypesAsCertain: true + + universalObjectCratesClasses: + - Illuminate\Container\Container diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..2bd0059 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + tests/Unit + + + tests/Feature + + + + + + + + diff --git a/resources/lang/cs/ares.php b/resources/lang/cs/ares.php new file mode 100644 index 0000000..bd2812c --- /dev/null +++ b/resources/lang/cs/ares.php @@ -0,0 +1,9 @@ + [ + 'not_found' => "Subjekt s I\u{010C}O :ic nebyl nalezen.", + 'api_error' => 'Chyba komunikace s ARES API.', + 'invalid_ic' => "Neplatn\u{00E9} I\u{010C}O.", + ], +]; diff --git a/resources/lang/en/ares.php b/resources/lang/en/ares.php new file mode 100644 index 0000000..9c5b5f2 --- /dev/null +++ b/resources/lang/en/ares.php @@ -0,0 +1,9 @@ + [ + 'not_found' => 'Entity with IC :ic was not found.', + 'api_error' => 'ARES API communication error.', + 'invalid_ic' => 'Invalid IC.', + ], +]; diff --git a/src/Commands/TestAresCommand.php b/src/Commands/TestAresCommand.php new file mode 100644 index 0000000..776c8ae --- /dev/null +++ b/src/Commands/TestAresCommand.php @@ -0,0 +1,53 @@ +icArgument(); + $company = Ares::findCompany($ic); + + if (! $company) { + $this->error(__('laravel-ares::ares.errors.not_found', ['ic' => $ic])); + + return self::FAILURE; + } + + $this->info('Company found:'); + $this->table( + ['Property', 'Value'], + [ + ['IC', $company->ic], + ['Name', $company->name], + ['DIC', $company->dic ?? 'N/A'], + ['Primary Source', $company->registration->primarySource ?? 'N/A'], + ['Date of Establishment', $company->registration->dateOfEstablishment ?? 'N/A'], + ['Financial Office', $company->registration->financialOffice ?? 'N/A'], + ['Address', $company->registeredOffice === null ? 'N/A' : ($company->registeredOffice->formatted ?? 'N/A')], + ['Delivery Address', $company->deliveryAddress === null ? 'N/A' : ($company->deliveryAddress->formatted ?? 'N/A')], + ['Legal Form', $company->registration->legalForm ?? 'N/A'], + ['Business Register File Mark', $company->registration->businessRegisterFileMark ?? 'N/A'], + ] + ); + + return self::SUCCESS; + } + + private function icArgument(): string + { + $value = $this->argument('ic'); + + return is_scalar($value) ? (string) $value : ''; + } +} diff --git a/src/Contracts/AresClientInterface.php b/src/Contracts/AresClientInterface.php new file mode 100644 index 0000000..ed1d04a --- /dev/null +++ b/src/Contracts/AresClientInterface.php @@ -0,0 +1,25 @@ +|null + */ + public function findCompanyRaw(string $ic): ?array; + + public function findCompanyOrFail(string $ic): CompanyData; + + public function forgetCompany(string $ic): bool; + + public function isValidIc(string $ic): bool; + + public function normalizeIc(string $ic): string; +} diff --git a/src/Data/AddressData.php b/src/Data/AddressData.php new file mode 100644 index 0000000..1f29b3f --- /dev/null +++ b/src/Data/AddressData.php @@ -0,0 +1,138 @@ + $data + */ + public static function fromApiResponse(array $data): ?self + { + $street = self::nullableString($data['nazevUlice'] ?? null); + $houseNumber = self::resolveHouseNumber($data); + $district = self::nullableString($data['nazevCastiObce'] ?? null); + $city = self::nullableString($data['nazevObce'] ?? $data['obec'] ?? null); + $postalCode = self::scalarToString($data['psc'] ?? null); + $countryCode = self::nullableString($data['kodStatu'] ?? null); + $country = self::nullableString($data['nazevStatu'] ?? null); + $formatted = self::resolveFormattedAddress($data, $street, $houseNumber, $district, $city, $postalCode); + + if ( + $formatted === null + && $street === null + && $houseNumber === null + && $district === null + && $city === null + && $postalCode === null + && $countryCode === null + && $country === null + ) { + return null; + } + + return new self( + formatted: $formatted, + street: $street, + houseNumber: $houseNumber, + district: $district, + city: $city, + postalCode: $postalCode, + countryCode: $countryCode, + country: $country, + ); + } + + /** + * @param array $data + */ + private static function resolveFormattedAddress( + array $data, + ?string $street, + ?string $houseNumber, + ?string $district, + ?string $city, + ?string $postalCode, + ): ?string { + $textAddress = self::nullableString($data['textovaAdresa'] ?? null); + + if ($textAddress !== null) { + return $textAddress; + } + + $addressParts = array_values(array_filter([ + self::combineStreetAndHouseNumber($street, $houseNumber), + $district, + $postalCode !== null && $city !== null ? "{$postalCode} {$city}" : $city, + $postalCode !== null && $city === null ? $postalCode : null, + ])); + + if ($addressParts === []) { + return null; + } + + return implode(', ', $addressParts); + } + + private static function combineStreetAndHouseNumber(?string $street, ?string $houseNumber): ?string + { + if ($street === null && $houseNumber === null) { + return null; + } + + if ($street !== null && $houseNumber !== null) { + return "{$street} {$houseNumber}"; + } + + return $street ?? $houseNumber; + } + + /** + * @param array $data + */ + private static function resolveHouseNumber(array $data): ?string + { + $descriptiveNumber = self::scalarToString($data['cisloDomovni'] ?? null); + $orientationNumber = self::scalarToString($data['cisloOrientacni'] ?? null); + $orientationLetter = self::nullableString($data['cisloOrientacniPismeno'] ?? null) ?? ''; + + if ($descriptiveNumber === null && $orientationNumber === null) { + return null; + } + + if ($descriptiveNumber !== null && $orientationNumber !== null) { + return $descriptiveNumber.'/'.$orientationNumber.$orientationLetter; + } + + return $descriptiveNumber ?? $orientationNumber.$orientationLetter; + } + + private static function nullableString(mixed $value): ?string + { + $string = self::scalarToString($value); + + return $string === null || $string === '' ? null : $string; + } + + private static function scalarToString(mixed $value): ?string + { + if (! is_scalar($value)) { + return null; + } + + return trim((string) $value); + } +} diff --git a/src/Data/CompanyData.php b/src/Data/CompanyData.php new file mode 100644 index 0000000..5797e43 --- /dev/null +++ b/src/Data/CompanyData.php @@ -0,0 +1,83 @@ + $rawData + */ + public function __construct( + public readonly string $ic, + public readonly string $name, + public readonly ?string $dic, + public readonly ?string $dicSkDph, + public readonly ?AddressData $registeredOffice, + public readonly ?DeliveryAddressData $deliveryAddress, + public readonly RegistrationData $registration, + public readonly array $rawData, + ) {} + + /** + * @param array $data + * + * @throws InvalidApiResponseException + */ + public static function fromApiResponse(array $data): self + { + return new self( + ic: self::requiredString($data, 'ico'), + name: self::requiredString($data, 'obchodniJmeno'), + dic: self::nullableString($data['dic'] ?? null), + dicSkDph: self::nullableString($data['dicSkDph'] ?? null), + registeredOffice: AddressData::fromApiResponse(self::arrayData($data['sidlo'] ?? null)), + deliveryAddress: DeliveryAddressData::fromApiResponse(self::arrayData($data['adresaDorucovaci'] ?? null)), + registration: RegistrationData::fromApiResponse($data), + rawData: $data, + ); + } + + /** + * @param array $data + * + * @throws InvalidApiResponseException + */ + private static function requiredString(array $data, string $field): string + { + $value = self::nullableString($data[$field] ?? null); + + if ($value === null) { + throw InvalidApiResponseException::missingRequiredField($field); + } + + return $value; + } + + private static function nullableString(mixed $value): ?string + { + if (! is_scalar($value)) { + return null; + } + + $string = trim((string) $value); + + return $string === '' ? null : $string; + } + + /** + * @return array + */ + private static function arrayData(mixed $value): array + { + if (! is_array($value)) { + return []; + } + + /** @var array $value */ + return $value; + } +} diff --git a/src/Data/DeliveryAddressData.php b/src/Data/DeliveryAddressData.php new file mode 100644 index 0000000..163ae6a --- /dev/null +++ b/src/Data/DeliveryAddressData.php @@ -0,0 +1,48 @@ + $lines + */ + public function __construct( + public readonly array $lines, + public readonly ?string $formatted, + ) {} + + /** + * @param array $data + */ + public static function fromApiResponse(array $data): ?self + { + $lines = array_values(array_filter([ + self::nullableString($data['radekAdresy1'] ?? null), + self::nullableString($data['radekAdresy2'] ?? null), + self::nullableString($data['radekAdresy3'] ?? null), + ])); + + if ($lines === []) { + return null; + } + + return new self( + lines: $lines, + formatted: implode(', ', $lines), + ); + } + + private static function nullableString(mixed $value): ?string + { + if (! is_scalar($value)) { + return null; + } + + $string = trim((string) $value); + + return $string === '' ? null : $string; + } +} diff --git a/src/Data/RegistrationData.php b/src/Data/RegistrationData.php new file mode 100644 index 0000000..2d00004 --- /dev/null +++ b/src/Data/RegistrationData.php @@ -0,0 +1,180 @@ + $naceCodes + * @param list $nace2008Codes + * @param list $sourceStatuses + */ + public function __construct( + public readonly ?string $legalForm, + public readonly ?string $financialOffice, + public readonly ?string $dateOfEstablishment, + public readonly ?string $dateOfLastUpdate, + public readonly ?string $primarySource, + public readonly ?string $businessRegisterFileMark, + public readonly array $naceCodes, + public readonly array $nace2008Codes, + public readonly array $sourceStatuses, + ) {} + + /** + * @param array $data + */ + public static function fromApiResponse(array $data): self + { + return new self( + legalForm: self::nullableString($data['pravniForma'] ?? null), + financialOffice: self::nullableString($data['financniUrad'] ?? null), + dateOfEstablishment: self::nullableString($data['datumVzniku'] ?? null), + dateOfLastUpdate: self::nullableString($data['datumAktualizace'] ?? null), + primarySource: self::nullableString($data['primarniZdroj'] ?? null), + businessRegisterFileMark: self::resolveBusinessRegisterFileMark(self::listOfAssociativeArrays($data['dalsiUdaje'] ?? null)), + naceCodes: self::normalizeStringList(self::listData($data['czNace'] ?? null)), + nace2008Codes: self::normalizeStringList(self::listData($data['czNace2008'] ?? null)), + sourceStatuses: self::normalizeSourceStatuses(self::associativeArrayData($data['seznamRegistraci'] ?? null)), + ); + } + + public function sourceStatus(string $source): ?RegistrationStatusData + { + foreach ($this->sourceStatuses as $sourceStatus) { + if ($sourceStatus->source === $source) { + return $sourceStatus; + } + } + + return null; + } + + /** + * @param array> $additionalData + */ + private static function resolveBusinessRegisterFileMark(array $additionalData): ?string + { + foreach ($additionalData as $entry) { + $fileMark = self::nullableString($entry['spisovaZnacka'] ?? null); + + if ($fileMark !== null) { + return $fileMark; + } + } + + return null; + } + + /** + * @param array $values + * @return list + */ + private static function normalizeStringList(array $values): array + { + $normalized = []; + + foreach ($values as $value) { + $string = self::nullableString($value); + + if ($string !== null) { + $normalized[] = $string; + } + } + + return $normalized; + } + + /** + * @param array $registrationStatuses + * @return list + */ + private static function normalizeSourceStatuses(array $registrationStatuses): array + { + $normalized = []; + + foreach ($registrationStatuses as $key => $value) { + $rawStatus = self::nullableString($value); + + if ($rawStatus === null) { + continue; + } + + $normalized[] = new RegistrationStatusData( + source: self::resolveSourceName($key), + rawStatus: $rawStatus, + status: RegistrationSourceState::tryFromApi($rawStatus), + ); + } + + return $normalized; + } + + private static function resolveSourceName(string $key): string + { + $prefix = 'stavZdroje'; + + if (str_starts_with($key, $prefix)) { + return lcfirst(substr($key, strlen($prefix))); + } + + return $key; + } + + /** + * @return list + */ + private static function listData(mixed $value): array + { + return is_array($value) ? array_values($value) : []; + } + + /** + * @return array + */ + private static function associativeArrayData(mixed $value): array + { + if (! is_array($value)) { + return []; + } + + /** @var array $value */ + return $value; + } + + /** + * @return array> + */ + private static function listOfAssociativeArrays(mixed $value): array + { + if (! is_array($value)) { + return []; + } + + $normalized = []; + + foreach ($value as $entry) { + if (is_array($entry)) { + /** @var array $entry */ + $normalized[] = $entry; + } + } + + return $normalized; + } + + private static function nullableString(mixed $value): ?string + { + if (! is_scalar($value)) { + return null; + } + + $string = trim((string) $value); + + return $string === '' ? null : $string; + } +} diff --git a/src/Data/RegistrationStatusData.php b/src/Data/RegistrationStatusData.php new file mode 100644 index 0000000..8c48bcf --- /dev/null +++ b/src/Data/RegistrationStatusData.php @@ -0,0 +1,16 @@ +|null findCompanyRaw(string $ic) + * @method static \NyonCode\Ares\Data\CompanyData findCompanyOrFail(string $ic) + * @method static bool forgetCompany(string $ic) + * @method static bool isValidIc(string $ic) + * @method static string normalizeIc(string $ic) + * + * @see AresClientInterface + */ +final class Ares extends Facade +{ + protected static function getFacadeAccessor(): string + { + return AresClientInterface::class; + } +} diff --git a/src/Providers/AresServiceProvider.php b/src/Providers/AresServiceProvider.php new file mode 100644 index 0000000..e88f41f --- /dev/null +++ b/src/Providers/AresServiceProvider.php @@ -0,0 +1,83 @@ +name('laravel-ares') + ->hasConfig() + ->hasCommands([ + TestAresCommand::class, + ]) + ->hasTranslations('resources/lang'); + } + + public function register(): void + { + parent::register(); + + $this->app->singleton(AresClientInterface::class, function (Application $app): AresClient { + return new AresClient( + baseUrl: $this->configString('ares.api_url'), + cacheTtl: $this->configInt('ares.cache_ttl'), + logger: $app->make(LogManager::class)->channel($this->configString('ares.log_channel')), + events: $app->make(Dispatcher::class), + cache: $app->make(CacheFactory::class)->store(), + http: $app->make(Http::class), + ); + }); + + $this->app->bind('ares', fn (Application $app) => $app->make(AresClientInterface::class)); + } + + /** + * @return array + */ + public function aboutData(): array + { + return [ + 'Client contract' => AresClientInterface::class, + 'Facade alias' => 'Ares', + 'Cache support' => 'enabled', + ]; + } + + private function configString(string $key): string + { + $value = config($key); + + return is_string($value) ? $value : ''; + } + + private function configInt(string $key): int + { + $value = config($key); + + return is_int($value) ? $value : (is_numeric($value) ? (int) $value : 0); + } +} diff --git a/src/Services/AresClient.php b/src/Services/AresClient.php new file mode 100644 index 0000000..3e0e15c --- /dev/null +++ b/src/Services/AresClient.php @@ -0,0 +1,183 @@ +baseUrl = rtrim($baseUrl, '/'); + $this->cacheTtl = $cacheTtl; + $this->logger = $logger; + $this->events = $events; + $this->cache = $cache; + } + + public function findCompany(string $ic): ?CompanyData + { + $ic = $this->normalizeIc($ic); + + if (! $this->isValidIc($ic)) { + $this->logger->warning('Invalid IC format', ['ic' => $ic]); + + return null; + } + + $cacheKey = $this->cacheKey($ic); + + return $this->cache->remember($cacheKey, $this->cacheTtl, function () use ($ic) { + try { + $response = $this->http + ->timeout($this->httpTimeout()) + ->connectTimeout($this->httpConnectTimeout()) + ->get("{$this->baseUrl}/ekonomicke-subjekty/{$ic}"); + + if ($response->failed()) { + $this->events->dispatch(new CompanyLookupFailed($ic, $response->status())); + + return null; + } + + $company = CompanyData::fromApiResponse( + $this->payloadData($response->json()) + ); + $this->events->dispatch(new CompanyLookupSucceeded($company)); + + return $company; + } catch (Throwable $e) { + $this->logger->error('ARES API error', [ + 'ic' => $ic, + 'exception' => $e->getMessage(), + ]); + $this->events->dispatch(new CompanyLookupFailed($ic, 0, $e)); + + return null; + } + }); + } + + public function findCompanyRaw(string $ic): ?array + { + return $this->findCompany($ic)?->rawData; + } + + public function findCompanyOrFail(string $ic): CompanyData + { + $normalizedIc = $this->normalizeIc($ic); + + if (! $this->isValidIc($normalizedIc)) { + throw InvalidIcException::forIc($normalizedIc); + } + + $company = $this->findCompany($normalizedIc); + + if ($company === null) { + throw CompanyNotFoundException::forIc($normalizedIc); + } + + return $company; + } + + public function forgetCompany(string $ic): bool + { + return $this->cache->forget($this->cacheKey($this->normalizeIc($ic))); + } + + public function isValidIc(string $ic): bool + { + $ic = $this->normalizeIc($ic); + + if (! preg_match('/^\d{8}$/', $ic)) { + return false; + } + + $sum = 0; + for ($i = 0; $i < 7; $i++) { + $sum += (int) $ic[$i] * (8 - $i); + } + + $remainder = $sum % 11; + $checksum = match ($remainder) { + 0 => 1, + 1 => 0, + default => 11 - $remainder, + }; + + return $checksum === (int) $ic[7]; + } + + public function normalizeIc(string $ic): string + { + return str_pad(preg_replace('/\D/', '', $ic) ?? '', 8, '0', STR_PAD_LEFT); + } + + private function cacheKey(string $ic): string + { + return "ares:company:{$ic}"; + } + + private function httpTimeout(): float + { + $timeout = config('ares.http_options.timeout'); + + return is_numeric($timeout) ? (float) $timeout : 5.0; + } + + private function httpConnectTimeout(): float + { + $timeout = config('ares.http_options.connect_timeout'); + + return is_numeric($timeout) ? (float) $timeout : 3.0; + } + + /** + * @return array + */ + private function payloadData(mixed $payload): array + { + if (! is_array($payload)) { + throw InvalidApiResponseException::invalidPayloadType(); + } + + $normalized = []; + + foreach ($payload as $key => $value) { + if (is_string($key)) { + $normalized[$key] = $value; + } + } + + return $normalized; + } +} diff --git a/tests/Feature/AresClientTest.php b/tests/Feature/AresClientTest.php new file mode 100644 index 0000000..e74a6d0 --- /dev/null +++ b/tests/Feature/AresClientTest.php @@ -0,0 +1,243 @@ + Http::response([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + 'dic' => 'CZ27074358', + 'pravniForma' => '121', + 'financniUrad' => '004', + 'datumVzniku' => '2003-08-06', + 'datumAktualizace' => '2026-03-08', + 'primarniZdroj' => 'ros', + 'sidlo' => [ + 'kodStatu' => 'CZ', + 'nazevStatu' => 'Ceska republika', + 'nazevUlice' => 'Budejovicka', + 'cisloDomovni' => 778, + 'cisloOrientacni' => 3, + 'cisloOrientacniPismeno' => 'a', + 'nazevCastiObce' => 'Michle', + 'obec' => 'Praha', + 'nazevObce' => 'Praha', + 'psc' => 14000, + 'textovaAdresa' => 'Budejovicka 778/3a, Michle, 14000 Praha 4', + ], + 'adresaDorucovaci' => [ + 'radekAdresy1' => 'Budejovicka 778/3a', + 'radekAdresy2' => 'Michle', + 'radekAdresy3' => '14000 Praha 4', + ], + 'czNace' => ['62', '63'], + 'czNace2008' => ['620', '63'], + 'seznamRegistraci' => [ + 'stavZdrojeRos' => 'AKTIVNI', + 'stavZdrojeVr' => 'AKTIVNI', + ], + 'dalsiUdaje' => [ + [ + 'datovyZdroj' => 'vr', + 'spisovaZnacka' => 'B 8525/MSPH', + ], + ], + ]), + ]); + + $company = app(AresClientInterface::class)->findCompany('27074358'); + + expect($company) + ->not->toBeNull() + ->and($company->ic)->toBe('27074358') + ->and($company->name)->toBe('Asseco Central Europe, a.s.') + ->and($company->dic)->toBe('CZ27074358') + ->and($company->registeredOffice?->formatted)->toBe('Budejovicka 778/3a, Michle, 14000 Praha 4') + ->and($company->registeredOffice?->houseNumber)->toBe('778/3a') + ->and($company->deliveryAddress?->lines)->toBe([ + 'Budejovicka 778/3a', + 'Michle', + '14000 Praha 4', + ]) + ->and($company->registration->financialOffice)->toBe('004') + ->and($company->registration->dateOfEstablishment)->toBe('2003-08-06') + ->and($company->registration->primarySource)->toBe('ros') + ->and($company->registration->businessRegisterFileMark)->toBe('B 8525/MSPH') + ->and($company->registration->naceCodes)->toBe(['62', '63']) + ->and($company->registration->sourceStatus('ros')?->status)->toBe(RegistrationSourceState::Active); + + Event::assertDispatched(CompanyLookupSucceeded::class, fn (CompanyLookupSucceeded $event) => $event->company->ic === '27074358'); + Event::assertNotDispatched(CompanyLookupFailed::class); +}); + +it('caches successful company lookups', function () { + $requestCount = 0; + + Http::fake(function () use (&$requestCount) { + $requestCount++; + + return Http::response([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + 'sidlo' => [ + 'nazevObce' => 'Praha', + ], + ]); + }); + + $client = app(AresClientInterface::class); + + $first = $client->findCompany('27074358'); + $second = $client->findCompany('27074358'); + + expect($first?->ic)->toBe('27074358') + ->and($second?->ic)->toBe('27074358') + ->and($requestCount)->toBe(1); +}); + +it('dispatches a failed event when the API returns an error response', function () { + Event::fake(); + + Http::fake([ + 'https://ares.gov.cz/*' => Http::response([], 404), + ]); + + $company = app(AresClientInterface::class)->findCompany('27074358'); + + expect($company)->toBeNull(); + + Event::assertDispatched( + CompanyLookupFailed::class, + fn (CompanyLookupFailed $event) => $event->ic === '27074358' + && $event->httpStatus === 404 + && $event->exception === null + ); +}); + +it('returns raw api data through the convenience method', function () { + Http::fake([ + 'https://ares.gov.cz/*' => Http::response([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + 'dic' => 'CZ27074358', + ]), + ]); + + $raw = app(AresClientInterface::class)->findCompanyRaw('27074358'); + + expect($raw)->toMatchArray([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + 'dic' => 'CZ27074358', + ]); +}); + +it('can forget a cached company lookup across normalized ic values', function () { + $requestCount = 0; + + Http::fake(function () use (&$requestCount) { + $requestCount++; + + return Http::response([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + ]); + }); + + $client = app(AresClientInterface::class); + + $client->findCompany('27 074 358'); + $client->forgetCompany('27074358'); + $client->findCompany('27074358'); + + expect($requestCount)->toBe(2); +}); + +it('throws a domain exception for invalid ic in the fail-fast method', function () { + expect(fn () => app(AresClientInterface::class)->findCompanyOrFail('123')) + ->toThrow(InvalidIcException::class, 'Invalid ICO [00000123].'); +}); + +it('throws a domain exception when a company is not found in the fail-fast method', function () { + Http::fake([ + 'https://ares.gov.cz/*' => Http::response([], 404), + ]); + + expect(fn () => app(AresClientInterface::class)->findCompanyOrFail('27074358')) + ->toThrow(CompanyNotFoundException::class, 'Company with ICO [27074358] was not found in ARES.'); +}); + +it('handles transport exceptions and dispatches a failed event', function () { + Event::fake(); + + Http::fake(fn () => throw new ConnectionException('Connection refused.')); + + $company = app(AresClientInterface::class)->findCompany('27074358'); + + expect($company)->toBeNull(); + + Event::assertDispatched( + CompanyLookupFailed::class, + fn (CompanyLookupFailed $event) => $event->ic === '27074358' + && $event->httpStatus === 0 + && $event->exception instanceof ConnectionException + ); +}); + +it('treats malformed payloads as failed lookups', function () { + Event::fake(); + + Http::fake([ + 'https://ares.gov.cz/*' => Http::response([ + 'ico' => '27074358', + ]), + ]); + + $company = app(AresClientInterface::class)->findCompany('27074358'); + + expect($company)->toBeNull(); + + Event::assertDispatched( + CompanyLookupFailed::class, + fn (CompanyLookupFailed $event) => $event->ic === '27074358' + && $event->exception instanceof InvalidApiResponseException + ); +}); + +it('rejects invalid IC values before making an HTTP request', function () { + Http::fake(); + + $company = app(AresClientInterface::class)->findCompany('123'); + + expect($company)->toBeNull(); + Http::assertNothingSent(); +}); + +it('validates normalized IC values correctly', function () { + $client = app(AresClientInterface::class); + + expect($client->isValidIc('27074358'))->toBeTrue() + ->and($client->isValidIc(' 27 074 358 '))->toBeTrue() + ->and($client->isValidIc('12345678'))->toBeFalse(); +}); + +it('normalizes ic values for consistent lookups', function () { + $client = app(AresClientInterface::class); + + expect($client->normalizeIc('27 074 358'))->toBe('27074358') + ->and($client->normalizeIc('123'))->toBe('00000123'); +}); diff --git a/tests/Feature/TestAresCommandTest.php b/tests/Feature/TestAresCommandTest.php new file mode 100644 index 0000000..926c24a --- /dev/null +++ b/tests/Feature/TestAresCommandTest.php @@ -0,0 +1,139 @@ +app->instance(AresClientInterface::class, new class implements AresClientInterface + { + public function findCompany(string $ic): ?CompanyData + { + return new CompanyData( + ic: '27074358', + name: 'Asseco Central Europe, a.s.', + dic: 'CZ27074358', + dicSkDph: null, + registeredOffice: new AddressData( + formatted: 'Budejovicka 778/3a, Michle, 14000 Praha 4', + street: 'Budejovicka', + houseNumber: '778/3a', + district: 'Michle', + city: 'Praha', + postalCode: '14000', + countryCode: 'CZ', + country: 'Ceska republika', + ), + deliveryAddress: new DeliveryAddressData( + lines: [ + 'Budejovicka 778/3a', + 'Michle', + '14000 Praha 4', + ], + formatted: 'Budejovicka 778/3a, Michle, 14000 Praha 4', + ), + registration: new RegistrationData( + legalForm: '121', + financialOffice: '004', + dateOfEstablishment: '2003-08-06', + dateOfLastUpdate: '2026-03-08', + primarySource: 'ros', + businessRegisterFileMark: 'B 8525/MSPH', + naceCodes: ['62', '63'], + nace2008Codes: ['620', '63'], + sourceStatuses: [], + ), + rawData: [] + ); + } + + public function findCompanyRaw(string $ic): ?array + { + return $this->findCompany($ic)?->rawData; + } + + public function findCompanyOrFail(string $ic): CompanyData + { + return $this->findCompany($ic) ?? throw CompanyNotFoundException::forIc($ic); + } + + public function forgetCompany(string $ic): bool + { + return true; + } + + public function isValidIc(string $ic): bool + { + return true; + } + + public function normalizeIc(string $ic): string + { + return $ic; + } + }); + + $this->artisan('ares:test', ['ic' => '27074358']) + ->expectsOutputToContain('Company found:') + ->expectsTable( + ['Property', 'Value'], + [ + ['IC', '27074358'], + ['Name', 'Asseco Central Europe, a.s.'], + ['DIC', 'CZ27074358'], + ['Primary Source', 'ros'], + ['Date of Establishment', '2003-08-06'], + ['Financial Office', '004'], + ['Address', 'Budejovicka 778/3a, Michle, 14000 Praha 4'], + ['Delivery Address', 'Budejovicka 778/3a, Michle, 14000 Praha 4'], + ['Legal Form', '121'], + ['Business Register File Mark', 'B 8525/MSPH'], + ] + ) + ->assertExitCode(0); +}); + +it('renders a translated error when the company is not found', function () { + $this->app->instance(AresClientInterface::class, new class implements AresClientInterface + { + public function findCompany(string $ic): ?CompanyData + { + return null; + } + + public function findCompanyRaw(string $ic): ?array + { + return null; + } + + public function findCompanyOrFail(string $ic): CompanyData + { + throw InvalidIcException::forIc($ic); + } + + public function forgetCompany(string $ic): bool + { + return true; + } + + public function isValidIc(string $ic): bool + { + return false; + } + + public function normalizeIc(string $ic): string + { + return $ic; + } + }); + + $this->artisan('ares:test', ['ic' => '00000000']) + ->expectsOutputToContain("Subjekt s I\u{010C}O 00000000 nebyl nalezen.") + ->assertExitCode(1); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..a38d9d3 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,7 @@ +in('Feature', 'Unit'); diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..9d850a9 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,29 @@ +set('app.key', 'base64:'.base64_encode(str_repeat('a', 32))); + $app['config']->set('app.locale', 'cs'); + $app['config']->set('app.fallback_locale', 'en'); + $app['config']->set('cache.default', 'array'); + $app['config']->set('ares.api_url', 'https://ares.gov.cz/ekonomicke-subjekty-v-be/rest'); + $app['config']->set('ares.cache_ttl', 3600); + $app['config']->set('ares.log_channel', 'stack'); + $app['config']->set('ares.http_options.timeout', 5.0); + $app['config']->set('ares.http_options.connect_timeout', 3.0); + } +} diff --git a/tests/Unit/CompanyDataTest.php b/tests/Unit/CompanyDataTest.php new file mode 100644 index 0000000..546af65 --- /dev/null +++ b/tests/Unit/CompanyDataTest.php @@ -0,0 +1,115 @@ + '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + 'dic' => 'CZ27074358', + 'pravniForma' => '121', + 'financniUrad' => '004', + 'datumVzniku' => '2003-08-06', + 'datumAktualizace' => '2026-03-08', + 'primarniZdroj' => 'ros', + 'sidlo' => [ + 'kodStatu' => 'CZ', + 'nazevStatu' => 'Ceska republika', + 'nazevUlice' => 'Budejovicka', + 'cisloDomovni' => 778, + 'cisloOrientacni' => 3, + 'cisloOrientacniPismeno' => 'a', + 'nazevCastiObce' => 'Michle', + 'obec' => 'Praha', + 'nazevObce' => 'Praha', + 'psc' => 14000, + 'textovaAdresa' => 'Budejovicka 778/3a, Michle, 14000 Praha 4', + ], + 'adresaDorucovaci' => [ + 'radekAdresy1' => 'Budejovicka 778/3a', + 'radekAdresy2' => 'Michle', + 'radekAdresy3' => '14000 Praha 4', + ], + 'czNace' => ['62', '63'], + 'czNace2008' => ['620', '63'], + 'seznamRegistraci' => [ + 'stavZdrojeRos' => 'AKTIVNI', + 'stavZdrojeVr' => 'AKTIVNI', + ], + 'dalsiUdaje' => [ + [ + 'datovyZdroj' => 'vr', + 'spisovaZnacka' => 'B 8525/MSPH', + ], + ], + ]); + + expect($company->ic)->toBe('27074358') + ->and($company->name)->toBe('Asseco Central Europe, a.s.') + ->and($company->dic)->toBe('CZ27074358') + ->and($company->registeredOffice?->formatted)->toBe('Budejovicka 778/3a, Michle, 14000 Praha 4') + ->and($company->registeredOffice?->street)->toBe('Budejovicka') + ->and($company->registeredOffice?->houseNumber)->toBe('778/3a') + ->and($company->registeredOffice?->district)->toBe('Michle') + ->and($company->registeredOffice?->city)->toBe('Praha') + ->and($company->registeredOffice?->postalCode)->toBe('14000') + ->and($company->deliveryAddress?->lines)->toBe([ + 'Budejovicka 778/3a', + 'Michle', + '14000 Praha 4', + ]) + ->and($company->registration->legalForm)->toBe('121') + ->and($company->registration->financialOffice)->toBe('004') + ->and($company->registration->dateOfEstablishment)->toBe('2003-08-06') + ->and($company->registration->dateOfLastUpdate)->toBe('2026-03-08') + ->and($company->registration->primarySource)->toBe('ros') + ->and($company->registration->businessRegisterFileMark)->toBe('B 8525/MSPH') + ->and($company->registration->naceCodes)->toBe(['62', '63']) + ->and($company->registration->nace2008Codes)->toBe(['620', '63']) + ->and($company->registration->sourceStatus('ros')?->status)->toBe(RegistrationSourceState::Active) + ->and($company->registration->sourceStatus('vr')?->rawStatus)->toBe('AKTIVNI'); +}); + +it('keeps nullable nested data safe when the api response is partial', function () { + $company = CompanyData::fromApiResponse([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Example Company s.r.o.', + ]); + + expect($company->registeredOffice)->toBeNull() + ->and($company->deliveryAddress)->toBeNull() + ->and($company->dic)->toBeNull() + ->and($company->registration->naceCodes)->toBe([]) + ->and($company->registration->nace2008Codes)->toBe([]) + ->and($company->registration->sourceStatuses)->toBe([]) + ->and($company->registration->businessRegisterFileMark)->toBeNull() + ->and($company->registration->legalForm)->toBeNull(); +}); + +it('builds a formatted address when text address is missing', function () { + $company = CompanyData::fromApiResponse([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Example Company s.r.o.', + 'sidlo' => [ + 'nazevUlice' => 'Budejovicka', + 'cisloDomovni' => 778, + 'cisloOrientacni' => 3, + 'cisloOrientacniPismeno' => 'a', + 'nazevCastiObce' => 'Michle', + 'nazevObce' => 'Praha', + 'psc' => 14000, + ], + ]); + + expect($company->registeredOffice?->formatted)->toBe('Budejovicka 778/3a, Michle, 14000 Praha'); +}); + +it('fails fast on malformed api responses missing required fields', function () { + expect(fn () => CompanyData::fromApiResponse([ + 'ico' => '27074358', + ]))->toThrow(InvalidApiResponseException::class, 'ARES response is missing required field [obchodniJmeno].'); +}); diff --git a/tests/Unit/ServiceProviderTest.php b/tests/Unit/ServiceProviderTest.php new file mode 100644 index 0000000..28e972b --- /dev/null +++ b/tests/Unit/ServiceProviderTest.php @@ -0,0 +1,11 @@ +toBeInstanceOf(AresClient::class) + ->and(app('ares'))->toBeInstanceOf(AresClientInterface::class); +});