From 932d9153a46261eea3e9e539f58c59ccda3fa253 Mon Sep 17 00:00:00 2001 From: ondrejnyklicek Date: Wed, 20 May 2026 13:33:20 +0200 Subject: [PATCH 1/6] Update 0.3 --- README.md | 77 +++++++++- config/ares.php | 13 ++ .../migrations/create_ares_subjects_table.php | 41 ++++++ docs/README.md | 14 +- docs/api.md | 112 +++++++++++++++ docs/configuration.md | 60 ++++++++ docs/faq.md | 46 +++++- docs/helpers.md | 17 +++ docs/installation.md | 28 +++- docs/usage.md | 54 +++++++ src/Commands/IndexAresCommand.php | 133 ++++++++++++++++++ src/Contracts/AresClientInterface.php | 9 ++ src/Data/SubjectData.php | 28 ++++ src/Facades/Ares.php | 1 + src/Jobs/IndexAresSubject.php | 62 ++++++++ src/Models/AresSubject.php | 53 +++++++ src/Providers/AresServiceProvider.php | 23 +++ src/Services/AresClient.php | 26 ++++ src/Services/SubjectSearchService.php | 127 +++++++++++++++++ src/helpers.php | 14 ++ tests/Fakes/FakeAresClient.php | 7 + tests/Feature/TestAresCommandTest.php | 11 ++ tests/TestCase.php | 3 + 23 files changed, 949 insertions(+), 10 deletions(-) create mode 100644 database/migrations/create_ares_subjects_table.php create mode 100644 src/Commands/IndexAresCommand.php create mode 100644 src/Data/SubjectData.php create mode 100644 src/Jobs/IndexAresSubject.php create mode 100644 src/Models/AresSubject.php create mode 100644 src/Services/SubjectSearchService.php diff --git a/README.md b/README.md index aa0f9ea..9180824 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - Events for successful and failed lookups - ICO normalization and checksum validation - Explicit exceptions for invalid ICO and missing companies +- Subject indexing with database-backed autocomplete search - Pest test suite, PHPStan configuration, and GitHub Actions CI ## Requirements @@ -41,6 +42,9 @@ php artisan vendor:publish --tag=laravel-ares::config | `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 | +| `indexing.enabled` | `true` | Enable subject indexing and search | +| `indexing.auto_index` | `true` | Automatically index subjects on successful lookup | +| `indexing.stale_days` | `30` | Number of days before a record is considered stale | Environment overrides: @@ -49,6 +53,9 @@ Environment overrides: - `ARES_LOG_CHANNEL` - `ARES_HTTP_TIMEOUT` - `ARES_HTTP_CONNECT_TIMEOUT` +- `ARES_INDEXING_ENABLED` +- `ARES_AUTO_INDEX` +- `ARES_STALE_DAYS` ## Usage @@ -91,6 +98,7 @@ Public API: - `forgetCompany(string $ic): bool` - `isValidIc(string $ic): bool` - `normalizeIc(string $ic): string` +- `search(string $query, int $limit = 10): Collection` ## Domain Model @@ -117,6 +125,7 @@ Related DTOs: - `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 +- `SubjectData` is a lightweight DTO for autocomplete search results (`ic`, `name`, `city`) `rawData` remains available as an escape hatch for fields the package does not map yet. @@ -136,15 +145,73 @@ The package dispatches: - `NyonCode\Ares\Events\CompanyLookupSucceeded` - `NyonCode\Ares\Events\CompanyLookupFailed` -## Artisan Command +## Subject Indexing and Autocomplete -The package includes an artisan helper for manual verification: +The package can index looked-up subjects into a local database table for fast autocomplete search. + +Run the migration after installing: + +```bash +php artisan migrate +``` + +Search indexed subjects by name or IC: + +```php +// Search by company name +$results = Ares::search('Asseco'); + +// Search by IC prefix +$results = Ares::search('2707', 5); + +// Using the global helper +$results = ares_search('Skoda'); +``` + +Each result is a `SubjectData` with `ic`, `name`, and `city` properties. + +### Auto-indexing + +When `indexing.auto_index` is enabled (default), every successful `findCompany()` call dispatches a queued job that indexes the subject automatically. No extra code needed. + +### Manual Indexing ```bash +# Index specific subjects +php artisan ares:index 27074358 25596641 + +# Refresh stale records (older than configured stale_days) +php artisan ares:index --refresh-stale + +# Custom stale threshold and limit +php artisan ares:index --refresh-stale --stale-days=14 --limit=200 +``` + +Schedule the refresh in your application's scheduler for automatic maintenance: + +```php +$schedule->command('ares:index --refresh-stale')->daily(); +``` + +## Artisan Commands + +The package includes artisan commands for diagnostics and indexing: + +```bash +# Test ARES API connectivity php artisan ares:test 27074358 + +# Index subjects +php artisan ares:index 27074358 + +# Show indexing statistics +php artisan ares:index + +# Refresh stale records +php artisan ares:index --refresh-stale ``` -The command renders a compact company summary including DIC, source, dates, registered office, delivery address, and register metadata. +`ares:test` renders a compact company summary including DIC, source, dates, registered office, delivery address, and register metadata. ## Quality Gates @@ -174,10 +241,12 @@ The repository includes a GitHub Actions workflow for: ## Development Notes -- Successful lookups are cached under the `ares:company:{ic}` key format. +- Successful lookups are cached under the `ares:v1: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`. +- Auto-indexed subjects are stored in the `ares_subjects` table with a minimal footprint (`ic`, `name`, `city`, `indexed_at`). +- Search uses `LIKE` queries with database indexes for fast prefix/substring matching. ## License diff --git a/config/ares.php b/config/ares.php index 0b21d09..25d31f4 100644 --- a/config/ares.php +++ b/config/ares.php @@ -31,4 +31,17 @@ 'timeout' => env('ARES_HTTP_TIMEOUT', 5.0), 'connect_timeout' => env('ARES_HTTP_CONNECT_TIMEOUT', 3.0), ], + + /* + |-------------------------------------------------------------------------- + | Subject Indexing + |-------------------------------------------------------------------------- + */ + 'indexing' => [ + 'enabled' => env('ARES_INDEXING_ENABLED', true), + 'auto_index' => env('ARES_AUTO_INDEX', true), + 'stale_days' => env('ARES_STALE_DAYS', 30), + 'queue' => env('ARES_INDEX_QUEUE'), + 'connection' => env('ARES_INDEX_CONNECTION'), + ], ]; diff --git a/database/migrations/create_ares_subjects_table.php b/database/migrations/create_ares_subjects_table.php new file mode 100644 index 0000000..a8050c1 --- /dev/null +++ b/database/migrations/create_ares_subjects_table.php @@ -0,0 +1,41 @@ +char('ic', 8)->primary(); + $table->string('name'); + $table->string('city', 100)->nullable(); + $table->timestamp('indexed_at')->useCurrent(); + }); + + $driver = Schema::getConnection()->getDriverName(); + + if (in_array($driver, ['mysql', 'mariadb'])) { + Schema::getConnection()->statement( + 'ALTER TABLE ares_subjects ADD FULLTEXT INDEX ares_subjects_name_fulltext (name)' + ); + } elseif ($driver === 'pgsql') { + Schema::getConnection()->statement( + 'CREATE INDEX ares_subjects_name_trgm ON ares_subjects USING GIN (name gin_trgm_ops)' + ); + } else { + Schema::table('ares_subjects', function (Blueprint $table) { + $table->index('name'); + }); + } + } + + public function down(): void + { + Schema::dropIfExists('ares_subjects'); + } +}; diff --git a/docs/README.md b/docs/README.md index c6cf296..646b817 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ A comprehensive Laravel package for interacting with the Czech ARES (Administrat - [Installation](installation.md) - [Configuration](configuration.md) - [Usage Examples](usage.md) +- [Subject Indexing & Autocomplete](indexing.md) - [Helper Functions](helpers.md) - [API Reference](api.md) - [Events](events.md) @@ -19,8 +20,9 @@ The Laravel ARES package provides a simple and elegant way to interact with the - **Caching**: Built-in caching support to reduce API calls - **Events**: Laravel events for successful and failed lookups - **Validation**: IC (identification number) format validation +- **Subject Indexing**: Database-backed indexing with autocomplete search - **Helper Functions**: Global helper functions for common operations -- **Artisan Commands**: Command-line tools for testing and debugging +- **Artisan Commands**: Command-line tools for testing, debugging, and indexing - **Type Safety**: Full PHP 8.2+ type safety and strict typing ## Quick Start @@ -41,6 +43,10 @@ if (ares_is_company_active('12345678')) { // Using facade $company = Ares::findCompanyOrFail('12345678'); +// Autocomplete search from indexed subjects +$results = Ares::search('Asseco'); // search by name +$results = Ares::search('2707', 5); // search by IC prefix + // Using fluent API - most elegant way $companies = ares() ->findMany(['12345678', '87654321']) @@ -77,6 +83,12 @@ $stats = ares() - Filtering and searching capabilities - Formatted display data +### 🔎 Subject Indexing & Autocomplete +- Database-backed subject index for fast local search +- Automatic indexing on successful lookups (queued job) +- Artisan command for manual and scheduled indexing +- Stale record detection and refresh + ### 🎯 Helper Functions - Global helper functions like `ares()`, `ares_is_company_active()` - Facade-based methods diff --git a/docs/api.md b/docs/api.md index 16a75fd..4b93897 100644 --- a/docs/api.md +++ b/docs/api.md @@ -19,6 +19,7 @@ interface AresClientInterface public function forgetCompany(string $ic): bool; public function isValidIc(string $ic): bool; public function normalizeIc(string $ic): string; + public function search(string $query, int $limit = 10): Collection; } ``` @@ -150,8 +151,40 @@ $normalized = $ares->normalizeIc('123 456 78'); echo $normalized; // '12345678' ``` +##### search(string $query, int $limit = 10): Collection + +Search indexed subjects by name or IC for autocomplete. + +**Parameters:** +- `$query` (string) - Search query (digits search by IC prefix, text searches by name) +- `$limit` (int) - Maximum number of results (default: 10) + +**Returns:** +- `Collection` - Collection of matching subjects + +**Example:** +```php +// Search by name +$results = $ares->search('Asseco'); + +// Search by IC prefix +$results = $ares->search('2707', 5); +``` + ## Data Classes +### SubjectData + +Lightweight DTO for autocomplete search results. + +#### Properties + +```php +public readonly string $ic; +public readonly string $name; +public readonly ?string $city; +``` + ### CompanyData Represents a company with all its information. @@ -297,8 +330,57 @@ Ares::findCompanyOrFail($ic); Ares::forgetCompany($ic); Ares::isValidIc($ic); Ares::normalizeIc($ic); +Ares::search($query, $limit); +``` + +## Jobs + +### IndexAresSubject + +Queued job for indexing a subject into the `ares_subjects` table. + +#### Static Factory + +```php +IndexAresSubject::fromCompanyData(CompanyData $company): self ``` +Creates an `IndexAresSubject` job from a `CompanyData` object. Extracts `ic`, `name`, and `city` automatically. + +#### Behavior + +- Uses `updateOrCreate` to insert or update the subject +- Sets `indexed_at` to the current timestamp +- Runs on the default queue (or synchronously with `sync` driver) + +## Services + +### SubjectSearchService + +Service for searching and managing indexed subjects. + +#### Methods + +##### search(string $query, int $limit = 10): Collection + +Search indexed subjects. Digits search by IC prefix, text by name substring. + +##### indexSubject(string $ic, string $name, ?string $city): void + +Index a single subject directly (without a queued job). + +##### subjectCount(): int + +Return the total number of indexed subjects. + +##### staleCount(int $days): int + +Return the number of records older than the given number of days. + +##### staleSubjects(int $days, int $limit = 100): Collection + +Return stale subject records for re-indexing. + ## Artisan Commands ### TestAresCommand @@ -319,6 +401,36 @@ php artisan ares:test {ic} php artisan ares:test 12345678 ``` +### IndexAresCommand + +Index ARES subjects for autocomplete search. + +#### Usage + +```bash +php artisan ares:index {ics?*} {--refresh-stale} {--stale-days=} {--limit=100} +``` + +**Parameters:** +- `ics` (optional) - One or more IC numbers to index + +**Options:** +- `--refresh-stale` - Re-index stale records +- `--stale-days=N` - Override configured stale days threshold +- `--limit=N` - Maximum number of records to refresh (default: 100) + +**Examples:** +```bash +# Index specific subjects +php artisan ares:index 27074358 25596641 + +# Show statistics +php artisan ares:index + +# Refresh stale records +php artisan ares:index --refresh-stale --stale-days=14 --limit=200 +``` + ## Events ### CompanyLookupSucceeded diff --git a/docs/configuration.md b/docs/configuration.md index dde7e63..91a8707 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -23,6 +23,11 @@ return [ 'timeout' => env('ARES_HTTP_TIMEOUT', 5.0), 'connect_timeout' => env('ARES_HTTP_CONNECT_TIMEOUT', 3.0), ], + 'indexing' => [ + 'enabled' => env('ARES_INDEXING_ENABLED', true), + 'auto_index' => env('ARES_AUTO_INDEX', true), + 'stale_days' => env('ARES_STALE_DAYS', 30), + ], ]; ``` @@ -131,6 +136,56 @@ ARES_LOG_CHANNEL=stack 'log_channel' => 'ares', ``` +### indexing + +Configuration for subject indexing and autocomplete search. + +#### indexing.enabled + +Enable or disable the indexing feature. + +**Type:** `bool` +**Default:** `true` +**Environment Variable:** `ARES_INDEXING_ENABLED` + +When disabled, `search()` returns an empty collection and no `IndexAresSubject` jobs are dispatched. + +#### indexing.auto_index + +Automatically index subjects on successful `findCompany()` calls. + +**Type:** `bool` +**Default:** `true` +**Environment Variable:** `ARES_AUTO_INDEX` + +When enabled, a queued `IndexAresSubject` job is dispatched after each successful lookup. + +#### indexing.stale_days + +Number of days before an indexed record is considered stale. + +**Type:** `int` +**Default:** `30` +**Environment Variable:** `ARES_STALE_DAYS` + +Used by `php artisan ares:index --refresh-stale` to determine which records need refreshing. + +#### Examples + +```env +# Enable auto-indexing (default) +ARES_INDEXING_ENABLED=true +ARES_AUTO_INDEX=true +ARES_STALE_DAYS=30 + +# Disable auto-indexing but keep search enabled +ARES_INDEXING_ENABLED=true +ARES_AUTO_INDEX=false + +# Disable indexing entirely +ARES_INDEXING_ENABLED=false +``` + ### http_options HTTP client configuration for API requests. @@ -195,6 +250,11 @@ ARES_CACHE_TTL=86400 ARES_LOG_CHANNEL=stack ARES_HTTP_TIMEOUT=5.0 ARES_HTTP_CONNECT_TIMEOUT=3.0 + +# Indexing +ARES_INDEXING_ENABLED=true +ARES_AUTO_INDEX=true +ARES_STALE_DAYS=30 ``` ### Environment-Specific Configurations diff --git a/docs/faq.md b/docs/faq.md index d4f14cf..4312153 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -340,6 +340,50 @@ class CompanyTest extends TestCase No, use mock data or test IC numbers provided in the documentation. Avoid using real company data in automated tests. +## Subject Indexing + +### What is subject indexing? + +Subject indexing stores basic company data (IC, name, city) in a local database table for fast autocomplete search. Instead of querying the ARES API for every search keystroke, you search your local index. + +### How do I set up indexing? + +Run the migration and enable indexing in your config (enabled by default): + +```bash +php artisan migrate +``` + +### How does auto-indexing work? + +When `indexing.auto_index` is enabled, every successful `findCompany()` call dispatches a queued `IndexAresSubject` job. The index grows organically as your application looks up companies. + +### How do I search the index? + +```php +use NyonCode\Ares\Facades\Ares; + +$results = Ares::search('Asseco'); // search by name +$results = Ares::search('2707', 5); // search by IC prefix +$results = ares_search('Skoda'); // global helper +``` + +### Can I disable indexing? + +Yes, set `ARES_INDEXING_ENABLED=false` in your `.env` file. When disabled, `search()` returns an empty collection and no jobs are dispatched. + +### How do I keep the index fresh? + +Schedule the refresh command in your scheduler: + +```php +$schedule->command('ares:index --refresh-stale')->daily(); +``` + +### Can I use the search without auto-indexing? + +Yes. Set `ARES_AUTO_INDEX=false` and use `php artisan ares:index` to manually index subjects. + ## Security and Privacy ### Is the data from ARES public? @@ -377,7 +421,7 @@ The package only accesses public company information. No sensitive personal data ### What's the roadmap? -- Enhanced filtering and search capabilities +- Full-text search support for indexed subjects - Additional data sources - Performance optimizations - More helper functions diff --git a/docs/helpers.md b/docs/helpers.md index 09a2a5a..b960baf 100644 --- a/docs/helpers.md +++ b/docs/helpers.md @@ -142,6 +142,23 @@ if (ares_validate_ic('12345678')) { } ``` +#### ares_search(string $query, int $limit = 10): Collection + +Search indexed subjects for autocomplete. + +```php +// Search by name +$results = ares_search('Asseco'); + +// Search by IC prefix with limit +$results = ares_search('2707', 5); + +// Each result is a SubjectData(ic, name, city) +foreach ($results as $subject) { + echo "{$subject->name} ({$subject->ic})"; +} +``` + #### ares_normalize_ic(string $ic): string Normalize IC to 8-digit format. diff --git a/docs/installation.md b/docs/installation.md index 8b69e14..3235868 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -21,7 +21,17 @@ Install the package using Composer: composer require nyoncode/laravel-ares ``` -### 2. Publish Configuration (Optional) +### 2. Run Migrations + +The package includes a migration for the subject indexing table. Run it with: + +```bash +php artisan migrate +``` + +This creates the `ares_subjects` table used for autocomplete search. If you don't need indexing, you can skip this step and set `ARES_INDEXING_ENABLED=false`. + +### 3. Publish Configuration (Optional) Publish the configuration file to customize the package settings: @@ -31,7 +41,7 @@ php artisan vendor:publish --tag="ares-config" This will create a `config/ares.php` file in your application. -### 3. Register Service Provider +### 4. Register Service Provider The package uses Laravel's package discovery, so the service provider is automatically registered. If you're not using package discovery, add it manually to your `config/app.php`: @@ -42,7 +52,7 @@ The package uses Laravel's package discovery, so the service provider is automat ], ``` -### 4. Register Facade (Optional) +### 5. Register Facade (Optional) If you want to use the Ares facade, add it to your `config/app.php` aliases: @@ -62,12 +72,17 @@ After publishing the configuration file, you can customize the settings in `conf return [ 'api_url' => env('ARES_API_URL', 'https://ares.gov.cz/ekonomicke-subjekty-v-be/rest'), - 'cache_ttl' => env('ARES_CACHE_TTL', 86400), // 24 hours + 'cache_ttl' => env('ARES_CACHE_TTL', 86400), 'log_channel' => env('ARES_LOG_CHANNEL', 'stack'), 'http_options' => [ 'timeout' => env('ARES_HTTP_TIMEOUT', 5.0), 'connect_timeout' => env('ARES_HTTP_CONNECT_TIMEOUT', 3.0), ], + 'indexing' => [ + 'enabled' => env('ARES_INDEXING_ENABLED', true), + 'auto_index' => env('ARES_AUTO_INDEX', true), + 'stale_days' => env('ARES_STALE_DAYS', 30), + ], ]; ``` @@ -82,6 +97,11 @@ ARES_CACHE_TTL=86400 ARES_LOG_CHANNEL=stack ARES_HTTP_TIMEOUT=5.0 ARES_HTTP_CONNECT_TIMEOUT=3.0 + +# Indexing +ARES_INDEXING_ENABLED=true +ARES_AUTO_INDEX=true +ARES_STALE_DAYS=30 ``` ## Verification diff --git a/docs/usage.md b/docs/usage.md index 169d44a..3317b1e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -324,6 +324,44 @@ class CompanyController extends Controller } ``` +## Autocomplete Search + +The package indexes subjects into a local database for fast autocomplete search. See the [Subject Indexing & Autocomplete](indexing.md) guide for full details. + +### Quick Example + +```php +use NyonCode\Ares\Facades\Ares; + +// Search by name +$results = Ares::search('Asseco'); + +// Search by IC prefix +$results = Ares::search('2707', 5); + +// Using global helper +$results = ares_search('Skoda'); + +// Each result is a SubjectData(ic, name, city) +foreach ($results as $subject) { + echo "{$subject->name} ({$subject->ic}) - {$subject->city}"; +} +``` + +### Autocomplete API Endpoint + +```php +use NyonCode\Ares\Facades\Ares; + +Route::get('/api/companies/search', function (Request $request): JsonResponse { + $request->validate(['q' => 'required|string|min:2']); + + return response()->json( + Ares::search($request->string('q'), 10) + ); +}); +``` + ## Artisan Command Usage ### Testing ARES Connection @@ -350,6 +388,22 @@ php artisan ares:test 12345678 # +-------------------------+---------------------------+ ``` +### Indexing Subjects + +```bash +# Index specific subjects +php artisan ares:index 27074358 25596641 + +# Show indexing statistics +php artisan ares:index + +# Refresh stale records +php artisan ares:index --refresh-stale + +# With custom options +php artisan ares:index --refresh-stale --stale-days=14 --limit=200 +``` + ## Caching The package automatically caches results to reduce API calls. You can control caching behavior: diff --git a/src/Commands/IndexAresCommand.php b/src/Commands/IndexAresCommand.php new file mode 100644 index 0000000..41811b5 --- /dev/null +++ b/src/Commands/IndexAresCommand.php @@ -0,0 +1,133 @@ + $ics */ + $ics = $this->argument('ics'); + + if ($this->option('refresh-stale')) { + return $this->refreshStale($client, $search); + } + + if ($ics === []) { + $this->components->info("Celkem indexovano: {$search->subjectCount()} subjektu."); + + $staleDays = $this->configStaleDays(); + $staleCount = $search->staleCount($staleDays); + + if ($staleCount > 0) { + $this->components->warn("Zastaralych zaznamu (starsi nez {$staleDays} dni): {$staleCount}"); + } + + return self::SUCCESS; + } + + return $this->indexIcs($client, $ics); + } + + /** + * @param array $ics + */ + private function indexIcs(AresClientInterface $client, array $ics): int + { + $indexed = 0; + $failed = 0; + + $this->components->task('Indexovani subjektu', function () use ($client, $ics, &$indexed, &$failed) { + foreach ($ics as $ic) { + $company = $client->findCompany($ic); + + if ($company === null) { + $failed++; + + continue; + } + + IndexAresSubject::dispatchSync( + ic: $company->ic, + name: $company->name, + city: $company->registeredOffice?->city, + ); + + $indexed++; + } + }); + + $this->newLine(); + $this->components->info("Indexovano: {$indexed}, Neuspesnych: {$failed}"); + + return $failed > 0 ? self::FAILURE : self::SUCCESS; + } + + private function refreshStale(AresClientInterface $client, SubjectSearchService $search): int + { + $staleDays = $this->configStaleDays(); + $limit = (int) $this->option('limit'); + $staleSubjects = $search->staleSubjects($staleDays, $limit); + + if ($staleSubjects->isEmpty()) { + $this->components->info('Zadne zastarale zaznamy k preindexovani.'); + + return self::SUCCESS; + } + + $this->components->info("Preindexovani {$staleSubjects->count()} zastaralych zaznamu..."); + + $refreshed = 0; + $failed = 0; + + foreach ($staleSubjects as $subject) { + $company = $client->findCompany($subject->ic); + + if ($company === null) { + $failed++; + + continue; + } + + IndexAresSubject::dispatchSync( + ic: $company->ic, + name: $company->name, + city: $company->registeredOffice?->city, + ); + + $refreshed++; + } + + $this->components->info("Obnoveno: {$refreshed}, Neuspesnych: {$failed}"); + + return $failed > 0 ? self::FAILURE : self::SUCCESS; + } + + private function configStaleDays(): int + { + $option = $this->option('stale-days'); + + if ($option !== null) { + return (int) $option; + } + + $configValue = config('ares.indexing.stale_days'); + + return is_numeric($configValue) ? (int) $configValue : 30; + } +} diff --git a/src/Contracts/AresClientInterface.php b/src/Contracts/AresClientInterface.php index ed1d04a..4a876f1 100644 --- a/src/Contracts/AresClientInterface.php +++ b/src/Contracts/AresClientInterface.php @@ -4,7 +4,9 @@ namespace NyonCode\Ares\Contracts; +use Illuminate\Support\Collection; use NyonCode\Ares\Data\CompanyData; +use NyonCode\Ares\Data\SubjectData; interface AresClientInterface { @@ -22,4 +24,11 @@ public function forgetCompany(string $ic): bool; public function isValidIc(string $ic): bool; public function normalizeIc(string $ic): string; + + /** + * Search indexed subjects by name or IC for autocomplete. + * + * @return Collection + */ + public function search(string $query, int $limit = 10): Collection; } diff --git a/src/Data/SubjectData.php b/src/Data/SubjectData.php new file mode 100644 index 0000000..6c43388 --- /dev/null +++ b/src/Data/SubjectData.php @@ -0,0 +1,28 @@ + $this->ic, + 'name' => $this->name, + 'city' => $this->city, + ]; + } +} diff --git a/src/Facades/Ares.php b/src/Facades/Ares.php index e330a61..fa97520 100644 --- a/src/Facades/Ares.php +++ b/src/Facades/Ares.php @@ -14,6 +14,7 @@ * @method static bool forgetCompany(string $ic) * @method static bool isValidIc(string $ic) * @method static string normalizeIc(string $ic) + * @method static \Illuminate\Support\Collection search(string $query, int $limit = 10) * * @see AresClientInterface */ diff --git a/src/Jobs/IndexAresSubject.php b/src/Jobs/IndexAresSubject.php new file mode 100644 index 0000000..87711cf --- /dev/null +++ b/src/Jobs/IndexAresSubject.php @@ -0,0 +1,62 @@ +onQueue($queue); + } + + if (is_string($connection) && $connection !== '') { + $this->onConnection($connection); + } + } + + public static function fromCompanyData(CompanyData $company): self + { + return new self( + ic: $company->ic, + name: $company->name, + city: $company->registeredOffice?->city, + ); + } + + public function uniqueId(): string + { + return $this->ic; + } + + public function handle(): void + { + AresSubject::query()->updateOrCreate( + ['ic' => $this->ic], + [ + 'name' => $this->name, + 'city' => $this->city, + 'indexed_at' => now(), + ], + ); + } +} diff --git a/src/Models/AresSubject.php b/src/Models/AresSubject.php new file mode 100644 index 0000000..5458c15 --- /dev/null +++ b/src/Models/AresSubject.php @@ -0,0 +1,53 @@ + + */ + protected function casts(): array + { + return [ + 'indexed_at' => 'datetime', + ]; + } + + public function toSubjectData(): SubjectData + { + return new SubjectData( + ic: $this->ic, + name: $this->name, + city: $this->city, + ); + } +} diff --git a/src/Providers/AresServiceProvider.php b/src/Providers/AresServiceProvider.php index ea1c03e..0266426 100644 --- a/src/Providers/AresServiceProvider.php +++ b/src/Providers/AresServiceProvider.php @@ -8,10 +8,12 @@ use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; +use NyonCode\Ares\Commands\IndexAresCommand; use NyonCode\Ares\Commands\TestAresCommand; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Helpers\AresHelper; use NyonCode\Ares\Services\AresClient; +use NyonCode\Ares\Services\SubjectSearchService; use NyonCode\LaravelPackageToolkit\Contracts\Packable; use NyonCode\LaravelPackageToolkit\Exceptions\InvalidLanguageDirectoryException; use NyonCode\LaravelPackageToolkit\Packager; @@ -30,12 +32,18 @@ public function configure(Packager $packager): void $packager ->name('laravel-ares') ->hasConfig() + ->hasMigrations() ->hasCommands([ TestAresCommand::class, + IndexAresCommand::class, ]) ->hasTranslations('resources/lang') ->registeredPackage(function ($packager) { + $this->app->singleton(SubjectSearchService::class, fn () => new SubjectSearchService); + $this->app->bind(AresClientInterface::class, function (Application $app): AresClient { + $indexingEnabled = $this->configBool('ares.indexing.enabled'); + return new AresClient( baseUrl: $this->configString('ares.api_url'), cacheTtl: $this->configInt('ares.cache_ttl'), @@ -43,6 +51,8 @@ public function configure(Packager $packager): void cache: $app->make(CacheFactory::class)->store(), httpTimeout: $this->configFloat('ares.http_options.timeout'), httpConnectTimeout: $this->configFloat('ares.http_options.connect_timeout'), + autoIndex: $indexingEnabled && $this->configBool('ares.indexing.auto_index'), + searchService: $indexingEnabled ? $app->make(SubjectSearchService::class) : null, ); }); @@ -93,6 +103,19 @@ private function configInt(string $key): int return is_int($value) ? $value : (is_numeric($value) ? (int) $value : 0); } + /** + * Get a boolean value from configuration. + * + * @param string $key The configuration key + * @return bool The configuration value + */ + private function configBool(string $key): bool + { + $value = config($key); + + return filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + /** * Get a float value from configuration. * diff --git a/src/Services/AresClient.php b/src/Services/AresClient.php index f0f59ae..8fd5441 100644 --- a/src/Services/AresClient.php +++ b/src/Services/AresClient.php @@ -5,15 +5,19 @@ namespace NyonCode\Ares\Services; use Illuminate\Contracts\Cache\Repository as Cache; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Http; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Data\CompanyData; +use NyonCode\Ares\Data\SubjectData; use NyonCode\Ares\Events\CompanyLookupFailed; use NyonCode\Ares\Events\CompanyLookupSucceeded; use NyonCode\Ares\Exceptions\CompanyNotFoundException; use NyonCode\Ares\Exceptions\InvalidApiResponseException; use NyonCode\Ares\Exceptions\InvalidIcException; +use NyonCode\Ares\Jobs\IndexAresSubject; +use NyonCode\Ares\Services\SubjectSearchService; use Psr\Log\LoggerInterface; use Throwable; @@ -44,6 +48,8 @@ public function __construct( private readonly Cache $cache, private readonly float $httpTimeout = self::DEFAULT_HTTP_TIMEOUT, private readonly float $httpConnectTimeout = self::DEFAULT_HTTP_CONNECT_TIMEOUT, + private readonly bool $autoIndex = false, + private readonly ?SubjectSearchService $searchService = null, ) { $this->processedBaseUrl = rtrim($this->baseUrl, '/'); } @@ -97,6 +103,14 @@ public function findCompany(string $ic): ?CompanyData Event::dispatch(new CompanyLookupSucceeded($company)); + if ($this->autoIndex) { + IndexAresSubject::dispatch( + ic: $company->ic, + name: $company->name, + city: $company->registeredOffice?->city, + ); + } + return $company; } @@ -298,6 +312,18 @@ private function companyUrl(string $normalizedIc): string return "{$this->processedBaseUrl}/ekonomicke-subjekty/{$normalizedIc}"; } + /** + * @return Collection + */ + public function search(string $query, int $limit = 10): Collection + { + if ($this->searchService === null) { + return collect(); + } + + return $this->searchService->search($query, $limit); + } + private function reportLookupException(string $normalizedIc, Throwable $exception): void { $this->logger->error('ARES API error', [ diff --git a/src/Services/SubjectSearchService.php b/src/Services/SubjectSearchService.php new file mode 100644 index 0000000..4f6ec4a --- /dev/null +++ b/src/Services/SubjectSearchService.php @@ -0,0 +1,127 @@ + + */ + public function search(string $query, int $limit = 10): Collection + { + $query = trim($query); + + if ($query === '') { + return collect(); + } + + if (ctype_digit($query)) { + return $this->searchByIc($query, $limit); + } + + return $this->searchByName($query, $limit); + } + + /** + * @return Collection + */ + private function searchByIc(string $query, int $limit): Collection + { + return AresSubject::query() + ->whereRaw('ic LIKE ? ESCAPE ?', [self::escapeLike($query).'%', '\\']) + ->orderBy('ic') + ->limit($limit) + ->get() + ->map(fn (AresSubject $subject): SubjectData => $subject->toSubjectData()); + } + + /** + * @return Collection + */ + private function searchByName(string $query, int $limit): Collection + { + $builder = AresSubject::query(); + + $this->applyNameSearch($builder, $query); + + return $builder + ->limit($limit) + ->get() + ->map(fn (AresSubject $subject): SubjectData => $subject->toSubjectData()); + } + + /** + * @param Builder $builder + */ + private function applyNameSearch(Builder $builder, string $query): void + { + $driver = Schema::getConnection()->getDriverName(); + + if (in_array($driver, ['mysql', 'mariadb'])) { + $term = str_replace(['+', '-', '*', '~', '<', '>', '(', ')', '"'], '', $query); + + $builder + ->whereRaw('MATCH (name) AGAINST (? IN BOOLEAN MODE)', ['*'.$term.'*']) + ->orderByRaw('MATCH (name) AGAINST (? IN BOOLEAN MODE) DESC', ['*'.$term.'*']); + + return; + } + + $escaped = self::escapeLike($query); + + $builder + ->whereRaw('name LIKE ? ESCAPE ?', ['%'.$escaped.'%', '\\']) + ->orderBy('name'); + } + + private static function escapeLike(string $value): string + { + return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value); + } + + public function indexSubject(string $ic, string $name, ?string $city): void + { + AresSubject::query()->updateOrCreate( + ['ic' => $ic], + [ + 'name' => $name, + 'city' => $city, + 'indexed_at' => now(), + ], + ); + } + + public function subjectCount(): int + { + return AresSubject::query()->count(); + } + + public function staleCount(int $days): int + { + return AresSubject::query() + ->where('indexed_at', '<', now()->subDays($days)) + ->count(); + } + + /** + * @return Collection + */ + public function staleSubjects(int $days, int $limit = 100): Collection + { + return AresSubject::query() + ->where('indexed_at', '<', now()->subDays($days)) + ->orderBy('indexed_at') + ->limit($limit) + ->get(); + } +} diff --git a/src/helpers.php b/src/helpers.php index efa7647..cdb6b68 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -143,6 +143,20 @@ function ares_validate_ic(string $ic): bool } } +if (! function_exists('ares_search')) { + /** + * Search indexed ARES subjects for autocomplete. + * + * @param string $query Search query (name or IC) + * @param int $limit Maximum number of results + * @return \Illuminate\Support\Collection + */ + function ares_search(string $query, int $limit = 10): \Illuminate\Support\Collection + { + return AresHelper::client()->search($query, $limit); + } +} + if (! function_exists('ares_normalize_ic')) { /** * Normalize IC format. diff --git a/tests/Fakes/FakeAresClient.php b/tests/Fakes/FakeAresClient.php index c4ae829..852b8c0 100644 --- a/tests/Fakes/FakeAresClient.php +++ b/tests/Fakes/FakeAresClient.php @@ -4,8 +4,10 @@ namespace NyonCode\Ares\Tests\Fakes; +use Illuminate\Support\Collection; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Data\CompanyData; +use NyonCode\Ares\Data\SubjectData; use RuntimeException; final class FakeAresClient implements AresClientInterface @@ -77,4 +79,9 @@ public function normalizeIc(string $ic): string return $this->normalizeMap[$ic] ?? preg_replace('/\s+/', '', $ic) ?? $ic; } + + public function search(string $query, int $limit = 10): Collection + { + return collect(); + } } diff --git a/tests/Feature/TestAresCommandTest.php b/tests/Feature/TestAresCommandTest.php index 926c24a..2da5e7f 100644 --- a/tests/Feature/TestAresCommandTest.php +++ b/tests/Feature/TestAresCommandTest.php @@ -7,6 +7,7 @@ use NyonCode\Ares\Data\CompanyData; use NyonCode\Ares\Data\DeliveryAddressData; use NyonCode\Ares\Data\RegistrationData; +use Illuminate\Support\Collection; use NyonCode\Ares\Exceptions\CompanyNotFoundException; use NyonCode\Ares\Exceptions\InvalidIcException; @@ -77,6 +78,11 @@ public function normalizeIc(string $ic): string { return $ic; } + + public function search(string $query, int $limit = 10): Collection + { + return collect(); + } }); $this->artisan('ares:test', ['ic' => '27074358']) @@ -131,6 +137,11 @@ public function normalizeIc(string $ic): string { return $ic; } + + public function search(string $query, int $limit = 10): Collection + { + return collect(); + } }); $this->artisan('ares:test', ['ic' => '00000000']) diff --git a/tests/TestCase.php b/tests/TestCase.php index 9d850a9..e7ac1d9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -25,5 +25,8 @@ protected function defineEnvironment($app): void $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); + $app['config']->set('ares.indexing.enabled', true); + $app['config']->set('ares.indexing.auto_index', false); + $app['config']->set('ares.indexing.stale_days', 30); } } From cc1fb430edc333e0c9568ee5914caf16fdc008eb Mon Sep 17 00:00:00 2001 From: ondrejnyklicek Date: Wed, 20 May 2026 18:27:06 +0200 Subject: [PATCH 2/6] Update 0.4 --- docs/indexing.md | 218 ++++++++++++++++++++++++++ phpstan.neon.dist | 2 + resources/lang/cs/ares.php | 11 ++ resources/lang/en/ares.php | 11 ++ src/Models/AresSubject.php | 3 +- src/Providers/AresServiceProvider.php | 16 ++ src/Services/AresClient.php | 1 - src/helpers.php | 6 +- tests/Fakes/FakeAresClient.php | 1 - tests/Feature/TestAresCommandTest.php | 2 +- 10 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 docs/indexing.md diff --git a/docs/indexing.md b/docs/indexing.md new file mode 100644 index 0000000..0c28935 --- /dev/null +++ b/docs/indexing.md @@ -0,0 +1,218 @@ +# Subject Indexing & Autocomplete + +The package can index looked-up subjects into a local database table (`ares_subjects`) for fast autocomplete search. This is useful for building typeahead/autocomplete inputs in your application. + +## How It Works + +1. Subjects are stored in a minimal `ares_subjects` table with only the fields needed for autocomplete: `ic`, `name`, `city`, and `indexed_at` +2. Subjects can be indexed automatically (on every successful `findCompany()` call) or manually via artisan command +3. Search queries run against the local database, not the ARES API, making them fast and reliable + +## Setup + +Run the migration to create the `ares_subjects` table: + +```bash +php artisan migrate +``` + +The table schema: + +| Column | Type | Description | +| --- | --- | --- | +| `ic` | `CHAR(8)` PRIMARY KEY | Company identification number | +| `name` | `VARCHAR(255)` | Company name | +| `city` | `VARCHAR(100)` nullable | City (for disambiguation in autocomplete) | +| `indexed_at` | `TIMESTAMP` | When the record was last indexed | + +## Configuration + +```php +// config/ares.php +'indexing' => [ + 'enabled' => env('ARES_INDEXING_ENABLED', true), + 'auto_index' => env('ARES_AUTO_INDEX', true), + 'stale_days' => env('ARES_STALE_DAYS', 30), +], +``` + +| Key | Default | Description | +| --- | --- | --- | +| `indexing.enabled` | `true` | Enable the indexing feature and search | +| `indexing.auto_index` | `true` | Automatically index subjects on successful `findCompany()` calls | +| `indexing.stale_days` | `30` | Number of days before a record is considered stale | + +To disable indexing entirely: + +```env +ARES_INDEXING_ENABLED=false +``` + +## Searching + +### Using the Facade + +```php +use NyonCode\Ares\Facades\Ares; + +// Search by company name (substring match) +$results = Ares::search('Asseco'); + +// Search by IC prefix +$results = Ares::search('2707'); + +// Limit the number of results +$results = Ares::search('Skoda', 5); +``` + +### Using the Helper Function + +```php +$results = ares_search('Asseco'); +$results = ares_search('2707', 5); +``` + +### Using Dependency Injection + +```php +use NyonCode\Ares\Contracts\AresClientInterface; + +class AutocompleteController +{ + public function __construct( + private readonly AresClientInterface $ares, + ) {} + + public function __invoke(Request $request): JsonResponse + { + $results = $this->ares->search( + $request->string('q'), + 10, + ); + + return response()->json($results); + } +} +``` + +### Search Result Format + +Each result is a `SubjectData` DTO: + +```php +NyonCode\Ares\Data\SubjectData { + public readonly string $ic; // '27074358' + public readonly string $name; // 'Asseco Central Europe, a.s.' + public readonly ?string $city; // 'Praha' +} +``` + +### Search Behavior + +- If the query contains only digits, it searches by IC prefix (`LIKE '2707%'`) +- Otherwise, it searches by name substring (`LIKE '%Asseco%'`) +- Results are ordered by `ic` (for IC search) or `name` (for name search) +- Empty queries return an empty collection + +## Auto-indexing + +When `indexing.auto_index` is enabled (default), every successful `findCompany()` call dispatches a queued `IndexAresSubject` job. This means your index grows organically as your application looks up companies. + +The job runs on your default queue. To process it: + +```bash +php artisan queue:work +``` + +If you use the `sync` queue driver, indexing happens synchronously within the same request. + +## Manual Indexing via Artisan + +### Index Specific Subjects + +```bash +php artisan ares:index 27074358 25596641 +``` + +Each IC is looked up via the ARES API and indexed. The command reports how many were indexed and how many failed. + +### Show Indexing Statistics + +```bash +php artisan ares:index +``` + +Displays the total number of indexed subjects and stale record count. + +### Refresh Stale Records + +```bash +# Use configured stale_days (default: 30) +php artisan ares:index --refresh-stale + +# Custom stale threshold +php artisan ares:index --refresh-stale --stale-days=14 + +# Limit the number of records to refresh per run +php artisan ares:index --refresh-stale --limit=200 +``` + +### Scheduling + +Add the refresh command to your application's scheduler for automatic maintenance: + +```php +// app/Console/Kernel.php or routes/console.php +$schedule->command('ares:index --refresh-stale')->daily(); +``` + +## Using in API Endpoints + +A typical autocomplete endpoint: + +```php +use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; +use NyonCode\Ares\Facades\Ares; + +Route::get('/api/companies/search', function (Request $request): JsonResponse { + $request->validate([ + 'q' => 'required|string|min:2', + 'limit' => 'integer|min:1|max:50', + ]); + + $results = Ares::search( + $request->string('q'), + $request->integer('limit', 10), + ); + + return response()->json($results); +}); +``` + +Example response: + +```json +[ + {"ic": "27074358", "name": "Asseco Central Europe, a.s.", "city": "Praha"}, + {"ic": "27082440", "name": "Asseco Solutions, a.s.", "city": "Praha"} +] +``` + +## Disabling Indexing + +If you don't need autocomplete, disable indexing entirely: + +```env +ARES_INDEXING_ENABLED=false +``` + +When disabled: +- No `IndexAresSubject` jobs are dispatched +- `search()` returns an empty collection +- The `ares:index` command still works for manual operations +- The migration can be skipped + +--- + +*Previous: [Usage Examples](usage.md) | Next: [Helper Functions](helpers.md)* diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0ebc249..0a2c682 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,8 @@ includes: parameters: paths: - src + excludePaths: + - src/Livewire level: max tmpDir: .phpstan treatPhpDocTypesAsCertain: true diff --git a/resources/lang/cs/ares.php b/resources/lang/cs/ares.php index bd2812c..18c38cc 100644 --- a/resources/lang/cs/ares.php +++ b/resources/lang/cs/ares.php @@ -6,4 +6,15 @@ 'api_error' => 'Chyba komunikace s ARES API.', 'invalid_ic' => "Neplatn\u{00E9} I\u{010C}O.", ], + + 'livewire' => [ + 'search_placeholder' => "Hledat firmu podle n\u{00E1}zvu nebo I\u{010C}O...", + 'ic_placeholder' => "Zadejte I\u{010C}O", + 'ic_label' => "I\u{010C}O", + 'dic_label' => "DI\u{010C}", + 'lookup_button' => 'Vyhledat', + 'loading' => "Na\u{010D}\u{00ED}t\u{00E1}n\u{00ED}...", + 'no_results' => "\u{017D}\u{00E1}dn\u{00E9} v\u{00FD}sledky.", + 'company_not_found' => 'Firma nebyla nalezena.', + ], ]; diff --git a/resources/lang/en/ares.php b/resources/lang/en/ares.php index 9c5b5f2..fc5df96 100644 --- a/resources/lang/en/ares.php +++ b/resources/lang/en/ares.php @@ -6,4 +6,15 @@ 'api_error' => 'ARES API communication error.', 'invalid_ic' => 'Invalid IC.', ], + + 'livewire' => [ + 'search_placeholder' => 'Search company by name or IC...', + 'ic_placeholder' => 'Enter IC', + 'ic_label' => 'IC', + 'dic_label' => 'VAT ID', + 'lookup_button' => 'Lookup', + 'loading' => 'Loading...', + 'no_results' => 'No results found.', + 'company_not_found' => 'Company not found.', + ], ]; diff --git a/src/Models/AresSubject.php b/src/Models/AresSubject.php index 5458c15..2e9de3a 100644 --- a/src/Models/AresSubject.php +++ b/src/Models/AresSubject.php @@ -5,13 +5,14 @@ namespace NyonCode\Ares\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Carbon; use NyonCode\Ares\Data\SubjectData; /** * @property string $ic * @property string $name * @property string|null $city - * @property \Illuminate\Support\Carbon $indexed_at + * @property Carbon $indexed_at */ final class AresSubject extends Model { diff --git a/src/Providers/AresServiceProvider.php b/src/Providers/AresServiceProvider.php index 0266426..1e3e248 100644 --- a/src/Providers/AresServiceProvider.php +++ b/src/Providers/AresServiceProvider.php @@ -8,10 +8,13 @@ use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; +use Livewire\Livewire; use NyonCode\Ares\Commands\IndexAresCommand; use NyonCode\Ares\Commands\TestAresCommand; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Helpers\AresHelper; +use NyonCode\Ares\Livewire\AresLookup; +use NyonCode\Ares\Livewire\AresSearch; use NyonCode\Ares\Services\AresClient; use NyonCode\Ares\Services\SubjectSearchService; use NyonCode\LaravelPackageToolkit\Contracts\Packable; @@ -37,6 +40,7 @@ public function configure(Packager $packager): void TestAresCommand::class, IndexAresCommand::class, ]) + ->hasViews() ->hasTranslations('resources/lang') ->registeredPackage(function ($packager) { $this->app->singleton(SubjectSearchService::class, fn () => new SubjectSearchService); @@ -59,6 +63,8 @@ public function configure(Packager $packager): void $this->app->bind('ares', fn (Application $app) => $app->make(AresClientInterface::class)); $this->app->singleton(AresHelper::class, fn () => new AresHelper); $this->app->alias(AresHelper::class, 'ares.helper'); + + $this->registerLivewireComponents(); }); } @@ -77,6 +83,16 @@ public function aboutData(): array ]; } + private function registerLivewireComponents(): void + { + if (! class_exists(Livewire::class)) { + return; + } + + Livewire::component('ares-search', AresSearch::class); + Livewire::component('ares-lookup', AresLookup::class); + } + /** * Get a string value from configuration. * diff --git a/src/Services/AresClient.php b/src/Services/AresClient.php index 8fd5441..5642ce1 100644 --- a/src/Services/AresClient.php +++ b/src/Services/AresClient.php @@ -17,7 +17,6 @@ use NyonCode\Ares\Exceptions\InvalidApiResponseException; use NyonCode\Ares\Exceptions\InvalidIcException; use NyonCode\Ares\Jobs\IndexAresSubject; -use NyonCode\Ares\Services\SubjectSearchService; use Psr\Log\LoggerInterface; use Throwable; diff --git a/src/helpers.php b/src/helpers.php index cdb6b68..9255e35 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -2,7 +2,9 @@ declare(strict_types=1); +use Illuminate\Support\Collection; use NyonCode\Ares\Data\CompanyData; +use NyonCode\Ares\Data\SubjectData; use NyonCode\Ares\Helpers\AresHelper; if (! function_exists('ares')) { @@ -149,9 +151,9 @@ function ares_validate_ic(string $ic): bool * * @param string $query Search query (name or IC) * @param int $limit Maximum number of results - * @return \Illuminate\Support\Collection + * @return Collection */ - function ares_search(string $query, int $limit = 10): \Illuminate\Support\Collection + function ares_search(string $query, int $limit = 10): Collection { return AresHelper::client()->search($query, $limit); } diff --git a/tests/Fakes/FakeAresClient.php b/tests/Fakes/FakeAresClient.php index 852b8c0..a401593 100644 --- a/tests/Fakes/FakeAresClient.php +++ b/tests/Fakes/FakeAresClient.php @@ -7,7 +7,6 @@ use Illuminate\Support\Collection; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Data\CompanyData; -use NyonCode\Ares\Data\SubjectData; use RuntimeException; final class FakeAresClient implements AresClientInterface diff --git a/tests/Feature/TestAresCommandTest.php b/tests/Feature/TestAresCommandTest.php index 2da5e7f..8444a2c 100644 --- a/tests/Feature/TestAresCommandTest.php +++ b/tests/Feature/TestAresCommandTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); +use Illuminate\Support\Collection; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Data\AddressData; use NyonCode\Ares\Data\CompanyData; use NyonCode\Ares\Data\DeliveryAddressData; use NyonCode\Ares\Data\RegistrationData; -use Illuminate\Support\Collection; use NyonCode\Ares\Exceptions\CompanyNotFoundException; use NyonCode\Ares\Exceptions\InvalidIcException; From 369349cd4d1e394e33fea680b5ab4ce916c31cda Mon Sep 17 00:00:00 2001 From: ondrejnyklicek Date: Wed, 20 May 2026 18:42:48 +0200 Subject: [PATCH 3/6] Update 0.5 --- composer.json | 3 +- src/Livewire/AresLookup.php | 19 ++++++ src/Livewire/AresSearch.php | 19 ++++++ src/Livewire/Concerns/WithAresLookup.php | 85 ++++++++++++++++++++++++ src/Livewire/Concerns/WithAresSearch.php | 82 +++++++++++++++++++++++ 5 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/Livewire/AresLookup.php create mode 100644 src/Livewire/AresSearch.php create mode 100644 src/Livewire/Concerns/WithAresLookup.php create mode 100644 src/Livewire/Concerns/WithAresSearch.php diff --git a/composer.json b/composer.json index a75bd3c..68d359a 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "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" + "nyoncode/laravel-package-toolkit": "^2.0", + "livewire/livewire": "^3.0|^4.0" }, "require-dev": { "larastan/larastan": "^2.9|^3.0", diff --git a/src/Livewire/AresLookup.php b/src/Livewire/AresLookup.php new file mode 100644 index 0000000..188a689 --- /dev/null +++ b/src/Livewire/AresLookup.php @@ -0,0 +1,19 @@ + + * + * + * @if($aresCompany) + * {{ $aresCompany['name'] }} - {{ $aresCompany['ic'] }} + * + * @endif + */ +trait WithAresLookup +{ + public string $aresIc = ''; + + public ?string $aresError = null; + + /** + * @var array{ic: string, name: string, dic: string|null, address: string|null, city: string|null, postalCode: string|null, street: string|null, houseNumber: string|null}|null + */ + public ?array $aresCompany = null; + + public function lookupAres(): void + { + $this->aresError = null; + $this->aresCompany = null; + + $ic = trim($this->aresIc); + + if ($ic === '') { + return; + } + + /** @var AresClientInterface $client */ + $client = app(AresClientInterface::class); + + if (! $client->isValidIc($ic)) { + $this->aresError = __('laravel-ares::ares.errors.invalid_ic'); + + return; + } + + $company = $client->findCompany($ic); + + if ($company === null) { + $this->aresError = __('laravel-ares::ares.livewire.company_not_found'); + + return; + } + + $this->aresCompany = [ + 'ic' => $company->ic, + 'name' => $company->name, + 'dic' => $company->dic, + 'address' => $company->registeredOffice?->formatted, + 'city' => $company->registeredOffice?->city, + 'postalCode' => $company->registeredOffice?->postalCode, + 'street' => $company->registeredOffice?->street, + 'houseNumber' => $company->registeredOffice?->houseNumber, + ]; + + $this->dispatch('ares-company-loaded', company: $this->aresCompany); + } + + public function clearAresLookup(): void + { + $this->aresIc = ''; + $this->aresError = null; + $this->aresCompany = null; + + $this->dispatch('ares-company-cleared'); + } +} diff --git a/src/Livewire/Concerns/WithAresSearch.php b/src/Livewire/Concerns/WithAresSearch.php new file mode 100644 index 0000000..0ac5b01 --- /dev/null +++ b/src/Livewire/Concerns/WithAresSearch.php @@ -0,0 +1,82 @@ +aresResults + * Events: ares-subject-selected, ares-subject-cleared + * + * Usage in Blade: + * + * + * @foreach($this->aresResults as $subject) + * + * + * @endforeach + */ +trait WithAresSearch +{ + public string $aresQuery = ''; + + public bool $aresOpen = false; + + public int $aresMinChars = 2; + + public int $aresLimit = 10; + + /** + * @return array + */ + #[Computed] + public function aresResults(): array + { + $query = trim($this->aresQuery); + + if (mb_strlen($query) < $this->aresMinChars) { + return []; + } + + /** @var AresClientInterface $client */ + $client = app(AresClientInterface::class); + + return $client->search($query, $this->aresLimit)->all(); + } + + public function updatedAresQuery(): void + { + $this->aresOpen = mb_strlen(trim($this->aresQuery)) >= $this->aresMinChars; + } + + public function selectAresSubject(string $ic): void + { + foreach ($this->aresResults as $subject) { + if ($subject->ic === $ic) { + $this->aresQuery = $subject->name; + $this->aresOpen = false; + + $this->dispatch('ares-subject-selected', ic: $subject->ic, name: $subject->name, city: $subject->city); + + return; + } + } + } + + public function clearAresSearch(): void + { + $this->aresQuery = ''; + $this->aresOpen = false; + + $this->dispatch('ares-subject-cleared'); + } +} From eb17ff72420edb6755c52312ccf794a517b3998d Mon Sep 17 00:00:00 2001 From: ondrejnyklicek Date: Wed, 20 May 2026 18:53:51 +0200 Subject: [PATCH 4/6] Update 0.6 --- src/Livewire/Concerns/WithAresLookup.php | 10 +---- src/Livewire/Concerns/WithAresSearch.php | 47 +++++++++--------------- src/Providers/AresServiceProvider.php | 2 +- src/Services/AresClient.php | 1 + src/Services/SubjectSearchService.php | 1 + 5 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/Livewire/Concerns/WithAresLookup.php b/src/Livewire/Concerns/WithAresLookup.php index 488e2af..709e326 100644 --- a/src/Livewire/Concerns/WithAresLookup.php +++ b/src/Livewire/Concerns/WithAresLookup.php @@ -10,16 +10,8 @@ * Adds ARES company lookup by IC to a Livewire component. * * Properties: $aresIc, $aresError, $aresCompany + * Methods: lookupAres(), clearAresLookup() * Events: ares-company-loaded, ares-company-cleared - * - * Usage in Blade: - * - * - * - * @if($aresCompany) - * {{ $aresCompany['name'] }} - {{ $aresCompany['ic'] }} - * - * @endif */ trait WithAresLookup { diff --git a/src/Livewire/Concerns/WithAresSearch.php b/src/Livewire/Concerns/WithAresSearch.php index 0ac5b01..18ad277 100644 --- a/src/Livewire/Concerns/WithAresSearch.php +++ b/src/Livewire/Concerns/WithAresSearch.php @@ -4,68 +4,55 @@ namespace NyonCode\Ares\Livewire\Concerns; -use Livewire\Attributes\Computed; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Data\SubjectData; /** * Adds ARES subject search (autocomplete) to a Livewire component. * - * Properties: $aresQuery, $aresOpen - * Computed: $this->aresResults + * Properties: $aresQuery, $aresResults + * Methods: selectAresSubject(), clearAresSearch() * Events: ares-subject-selected, ares-subject-cleared - * - * Usage in Blade: - * - * - * @foreach($this->aresResults as $subject) - * - * - * @endforeach */ trait WithAresSearch { public string $aresQuery = ''; - public bool $aresOpen = false; - public int $aresMinChars = 2; public int $aresLimit = 10; /** - * @return array + * @var array */ - #[Computed] - public function aresResults(): array + public array $aresResults = []; + + public function updatedAresQuery(): void { $query = trim($this->aresQuery); if (mb_strlen($query) < $this->aresMinChars) { - return []; + $this->aresResults = []; + + return; } /** @var AresClientInterface $client */ $client = app(AresClientInterface::class); - return $client->search($query, $this->aresLimit)->all(); - } - - public function updatedAresQuery(): void - { - $this->aresOpen = mb_strlen(trim($this->aresQuery)) >= $this->aresMinChars; + $this->aresResults = $client->search($query, $this->aresLimit) + ->map(fn (SubjectData $s): array => ['ic' => $s->ic, 'name' => $s->name, 'city' => $s->city]) + ->all(); } public function selectAresSubject(string $ic): void { foreach ($this->aresResults as $subject) { - if ($subject->ic === $ic) { - $this->aresQuery = $subject->name; - $this->aresOpen = false; + if ($subject['ic'] === $ic) { + $this->aresQuery = $subject['name']; + $this->aresResults = []; - $this->dispatch('ares-subject-selected', ic: $subject->ic, name: $subject->name, city: $subject->city); + $this->dispatch('ares-subject-selected', ic: $subject['ic'], name: $subject['name'], city: $subject['city']); return; } @@ -75,7 +62,7 @@ public function selectAresSubject(string $ic): void public function clearAresSearch(): void { $this->aresQuery = ''; - $this->aresOpen = false; + $this->aresResults = []; $this->dispatch('ares-subject-cleared'); } diff --git a/src/Providers/AresServiceProvider.php b/src/Providers/AresServiceProvider.php index 1e3e248..d69704b 100644 --- a/src/Providers/AresServiceProvider.php +++ b/src/Providers/AresServiceProvider.php @@ -85,7 +85,7 @@ public function aboutData(): array private function registerLivewireComponents(): void { - if (! class_exists(Livewire::class)) { + if (! class_exists(\Livewire\LivewireManager::class) || ! $this->app->bound('livewire')) { return; } diff --git a/src/Services/AresClient.php b/src/Services/AresClient.php index 5642ce1..c84390a 100644 --- a/src/Services/AresClient.php +++ b/src/Services/AresClient.php @@ -317,6 +317,7 @@ private function companyUrl(string $normalizedIc): string public function search(string $query, int $limit = 10): Collection { if ($this->searchService === null) { + /** @var Collection */ return collect(); } diff --git a/src/Services/SubjectSearchService.php b/src/Services/SubjectSearchService.php index 4f6ec4a..61b1744 100644 --- a/src/Services/SubjectSearchService.php +++ b/src/Services/SubjectSearchService.php @@ -22,6 +22,7 @@ public function search(string $query, int $limit = 10): Collection $query = trim($query); if ($query === '') { + /** @var Collection */ return collect(); } From 8333dda77cbb028ba274f5cd86e2e0a39be428f8 Mon Sep 17 00:00:00 2001 From: ondrejnyklicek Date: Tue, 30 Jun 2026 09:26:18 +0200 Subject: [PATCH 5/6] Remove Livewire, fix substring search and CI - Drop experimental Livewire components and the livewire/livewire runtime dependency; remove dangling references in the service provider, views and translations. - Fix subject name search to do a true substring LIKE match on every driver (MySQL previously used an invalid AGAINST('*term*') boolean expression). - Drop the unused MySQL FULLTEXT index from the migration; keep pgsql trgm GIN. - Fix PHPStan config (stale excludePaths) and Pint formatting -> green CI. - Stop tracking .idea/ and tooling caches; add CHANGELOG. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 4 + .idea/.gitignore | 10 - .idea/inspectionProfiles/Project_Default.xml | 7 - .idea/laravel-ares.iml | 157 ------ .idea/laravel-idea-personal.xml | 10 - .idea/laravel-idea.xml | 12 - .idea/misc.xml | 6 - .idea/modules.xml | 8 - .idea/php-docker-settings.xml | 23 - .idea/php-test-framework.xml | 14 - .idea/php.xml | 188 -------- .idea/stat.log | 447 ------------------ .idea/vcs.xml | 6 - CHANGELOG.md | 28 ++ composer.json | 3 +- .../migrations/create_ares_subjects_table.php | 11 +- phpstan.neon.dist | 2 - resources/lang/cs/ares.php | 11 - resources/lang/en/ares.php | 11 - src/Livewire/AresLookup.php | 19 - src/Livewire/AresSearch.php | 19 - src/Livewire/Concerns/WithAresLookup.php | 77 --- src/Livewire/Concerns/WithAresSearch.php | 69 --- src/Providers/AresServiceProvider.php | 16 - src/Services/SubjectSearchService.php | 13 - tests/Feature/SubjectIndexingTest.php | 220 +++++++++ 26 files changed, 257 insertions(+), 1134 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/laravel-ares.iml delete mode 100644 .idea/laravel-idea-personal.xml delete mode 100644 .idea/laravel-idea.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/php-docker-settings.xml delete mode 100644 .idea/php-test-framework.xml delete mode 100644 .idea/php.xml delete mode 100644 .idea/stat.log delete mode 100644 .idea/vcs.xml create mode 100644 CHANGELOG.md delete mode 100644 src/Livewire/AresLookup.php delete mode 100644 src/Livewire/AresSearch.php delete mode 100644 src/Livewire/Concerns/WithAresLookup.php delete mode 100644 src/Livewire/Concerns/WithAresSearch.php create mode 100644 tests/Feature/SubjectIndexingTest.php diff --git a/.gitignore b/.gitignore index d5673e3..b34d766 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ node_modules/ npm-debug.log yarn-error.log +/composer.lock +/.phpstan/ +/.phpunit.cache/ +/.idea/ # Laravel 4 specific bootstrap/compiled.php diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f416..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# 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 deleted file mode 100644 index 83879ce..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/laravel-ares.iml b/.idea/laravel-ares.iml deleted file mode 100644 index 07b238f..0000000 --- a/.idea/laravel-ares.iml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/laravel-idea-personal.xml b/.idea/laravel-idea-personal.xml deleted file mode 100644 index 425e121..0000000 --- a/.idea/laravel-idea-personal.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/laravel-idea.xml b/.idea/laravel-idea.xml deleted file mode 100644 index d8e17dd..0000000 --- a/.idea/laravel-idea.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3ce3588..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index f00f124..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml deleted file mode 100644 index c3b1223..0000000 --- a/.idea/php-docker-settings.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml deleted file mode 100644 index b97138c..0000000 --- a/.idea/php-test-framework.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml deleted file mode 100644 index 3ff3c62..0000000 --- a/.idea/php.xml +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/stat.log b/.idea/stat.log deleted file mode 100644 index fd45870..0000000 --- a/.idea/stat.log +++ /dev/null @@ -1,447 +0,0 @@ -{"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 deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a278acf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to `laravel-ares` will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.6] + +### Removed +- Removed the experimental Livewire search/lookup components (`AresSearch`, + `AresLookup`) and the `livewire/livewire` runtime dependency. The package is + now a lean API/indexing library; UI can be built on top of `Ares::search()`. + +### Fixed +- Subject name search now performs a true substring match (`LIKE '%term%'`) on + every database driver. Previously the MySQL branch used an invalid + `MATCH … AGAINST('*term*' IN BOOLEAN MODE)` expression whose leading `*` was + ignored, silently degrading to prefix matching and diverging from the + documented behaviour. +- Fixed a broken PHPStan configuration (`excludePaths` referenced a removed + directory) and applied Pint formatting, restoring a green CI pipeline. + +### Changed +- The `ares_subjects` migration no longer creates an unused MySQL `FULLTEXT` + index (it does not accelerate `LIKE` substring queries). PostgreSQL keeps its + trigram GIN index; other drivers use a plain index on `name`. +- Stopped tracking IDE (`.idea/`) and tooling cache directories in git. diff --git a/composer.json b/composer.json index 68d359a..a75bd3c 100644 --- a/composer.json +++ b/composer.json @@ -15,8 +15,7 @@ "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", - "livewire/livewire": "^3.0|^4.0" + "nyoncode/laravel-package-toolkit": "^2.0" }, "require-dev": { "larastan/larastan": "^2.9|^3.0", diff --git a/database/migrations/create_ares_subjects_table.php b/database/migrations/create_ares_subjects_table.php index a8050c1..74d893a 100644 --- a/database/migrations/create_ares_subjects_table.php +++ b/database/migrations/create_ares_subjects_table.php @@ -17,13 +17,10 @@ public function up(): void $table->timestamp('indexed_at')->useCurrent(); }); - $driver = Schema::getConnection()->getDriverName(); - - if (in_array($driver, ['mysql', 'mariadb'])) { - Schema::getConnection()->statement( - 'ALTER TABLE ares_subjects ADD FULLTEXT INDEX ares_subjects_name_fulltext (name)' - ); - } elseif ($driver === 'pgsql') { + // Name search uses substring matching (LIKE '%term%'). On PostgreSQL a + // trigram GIN index accelerates that; other drivers fall back to a plain + // index, which still helps ordering and prefix lookups. + if (Schema::getConnection()->getDriverName() === 'pgsql') { Schema::getConnection()->statement( 'CREATE INDEX ares_subjects_name_trgm ON ares_subjects USING GIN (name gin_trgm_ops)' ); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0a2c682..0ebc249 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,8 +4,6 @@ includes: parameters: paths: - src - excludePaths: - - src/Livewire level: max tmpDir: .phpstan treatPhpDocTypesAsCertain: true diff --git a/resources/lang/cs/ares.php b/resources/lang/cs/ares.php index 18c38cc..bd2812c 100644 --- a/resources/lang/cs/ares.php +++ b/resources/lang/cs/ares.php @@ -6,15 +6,4 @@ 'api_error' => 'Chyba komunikace s ARES API.', 'invalid_ic' => "Neplatn\u{00E9} I\u{010C}O.", ], - - 'livewire' => [ - 'search_placeholder' => "Hledat firmu podle n\u{00E1}zvu nebo I\u{010C}O...", - 'ic_placeholder' => "Zadejte I\u{010C}O", - 'ic_label' => "I\u{010C}O", - 'dic_label' => "DI\u{010C}", - 'lookup_button' => 'Vyhledat', - 'loading' => "Na\u{010D}\u{00ED}t\u{00E1}n\u{00ED}...", - 'no_results' => "\u{017D}\u{00E1}dn\u{00E9} v\u{00FD}sledky.", - 'company_not_found' => 'Firma nebyla nalezena.', - ], ]; diff --git a/resources/lang/en/ares.php b/resources/lang/en/ares.php index fc5df96..9c5b5f2 100644 --- a/resources/lang/en/ares.php +++ b/resources/lang/en/ares.php @@ -6,15 +6,4 @@ 'api_error' => 'ARES API communication error.', 'invalid_ic' => 'Invalid IC.', ], - - 'livewire' => [ - 'search_placeholder' => 'Search company by name or IC...', - 'ic_placeholder' => 'Enter IC', - 'ic_label' => 'IC', - 'dic_label' => 'VAT ID', - 'lookup_button' => 'Lookup', - 'loading' => 'Loading...', - 'no_results' => 'No results found.', - 'company_not_found' => 'Company not found.', - ], ]; diff --git a/src/Livewire/AresLookup.php b/src/Livewire/AresLookup.php deleted file mode 100644 index 188a689..0000000 --- a/src/Livewire/AresLookup.php +++ /dev/null @@ -1,19 +0,0 @@ -aresError = null; - $this->aresCompany = null; - - $ic = trim($this->aresIc); - - if ($ic === '') { - return; - } - - /** @var AresClientInterface $client */ - $client = app(AresClientInterface::class); - - if (! $client->isValidIc($ic)) { - $this->aresError = __('laravel-ares::ares.errors.invalid_ic'); - - return; - } - - $company = $client->findCompany($ic); - - if ($company === null) { - $this->aresError = __('laravel-ares::ares.livewire.company_not_found'); - - return; - } - - $this->aresCompany = [ - 'ic' => $company->ic, - 'name' => $company->name, - 'dic' => $company->dic, - 'address' => $company->registeredOffice?->formatted, - 'city' => $company->registeredOffice?->city, - 'postalCode' => $company->registeredOffice?->postalCode, - 'street' => $company->registeredOffice?->street, - 'houseNumber' => $company->registeredOffice?->houseNumber, - ]; - - $this->dispatch('ares-company-loaded', company: $this->aresCompany); - } - - public function clearAresLookup(): void - { - $this->aresIc = ''; - $this->aresError = null; - $this->aresCompany = null; - - $this->dispatch('ares-company-cleared'); - } -} diff --git a/src/Livewire/Concerns/WithAresSearch.php b/src/Livewire/Concerns/WithAresSearch.php deleted file mode 100644 index 18ad277..0000000 --- a/src/Livewire/Concerns/WithAresSearch.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ - public array $aresResults = []; - - public function updatedAresQuery(): void - { - $query = trim($this->aresQuery); - - if (mb_strlen($query) < $this->aresMinChars) { - $this->aresResults = []; - - return; - } - - /** @var AresClientInterface $client */ - $client = app(AresClientInterface::class); - - $this->aresResults = $client->search($query, $this->aresLimit) - ->map(fn (SubjectData $s): array => ['ic' => $s->ic, 'name' => $s->name, 'city' => $s->city]) - ->all(); - } - - public function selectAresSubject(string $ic): void - { - foreach ($this->aresResults as $subject) { - if ($subject['ic'] === $ic) { - $this->aresQuery = $subject['name']; - $this->aresResults = []; - - $this->dispatch('ares-subject-selected', ic: $subject['ic'], name: $subject['name'], city: $subject['city']); - - return; - } - } - } - - public function clearAresSearch(): void - { - $this->aresQuery = ''; - $this->aresResults = []; - - $this->dispatch('ares-subject-cleared'); - } -} diff --git a/src/Providers/AresServiceProvider.php b/src/Providers/AresServiceProvider.php index d69704b..0266426 100644 --- a/src/Providers/AresServiceProvider.php +++ b/src/Providers/AresServiceProvider.php @@ -8,13 +8,10 @@ use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; -use Livewire\Livewire; use NyonCode\Ares\Commands\IndexAresCommand; use NyonCode\Ares\Commands\TestAresCommand; use NyonCode\Ares\Contracts\AresClientInterface; use NyonCode\Ares\Helpers\AresHelper; -use NyonCode\Ares\Livewire\AresLookup; -use NyonCode\Ares\Livewire\AresSearch; use NyonCode\Ares\Services\AresClient; use NyonCode\Ares\Services\SubjectSearchService; use NyonCode\LaravelPackageToolkit\Contracts\Packable; @@ -40,7 +37,6 @@ public function configure(Packager $packager): void TestAresCommand::class, IndexAresCommand::class, ]) - ->hasViews() ->hasTranslations('resources/lang') ->registeredPackage(function ($packager) { $this->app->singleton(SubjectSearchService::class, fn () => new SubjectSearchService); @@ -63,8 +59,6 @@ public function configure(Packager $packager): void $this->app->bind('ares', fn (Application $app) => $app->make(AresClientInterface::class)); $this->app->singleton(AresHelper::class, fn () => new AresHelper); $this->app->alias(AresHelper::class, 'ares.helper'); - - $this->registerLivewireComponents(); }); } @@ -83,16 +77,6 @@ public function aboutData(): array ]; } - private function registerLivewireComponents(): void - { - if (! class_exists(\Livewire\LivewireManager::class) || ! $this->app->bound('livewire')) { - return; - } - - Livewire::component('ares-search', AresSearch::class); - Livewire::component('ares-lookup', AresLookup::class); - } - /** * Get a string value from configuration. * diff --git a/src/Services/SubjectSearchService.php b/src/Services/SubjectSearchService.php index 61b1744..f83856e 100644 --- a/src/Services/SubjectSearchService.php +++ b/src/Services/SubjectSearchService.php @@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Schema; use NyonCode\Ares\Data\SubjectData; use NyonCode\Ares\Models\AresSubject; @@ -66,18 +65,6 @@ private function searchByName(string $query, int $limit): Collection */ private function applyNameSearch(Builder $builder, string $query): void { - $driver = Schema::getConnection()->getDriverName(); - - if (in_array($driver, ['mysql', 'mariadb'])) { - $term = str_replace(['+', '-', '*', '~', '<', '>', '(', ')', '"'], '', $query); - - $builder - ->whereRaw('MATCH (name) AGAINST (? IN BOOLEAN MODE)', ['*'.$term.'*']) - ->orderByRaw('MATCH (name) AGAINST (? IN BOOLEAN MODE) DESC', ['*'.$term.'*']); - - return; - } - $escaped = self::escapeLike($query); $builder diff --git a/tests/Feature/SubjectIndexingTest.php b/tests/Feature/SubjectIndexingTest.php new file mode 100644 index 0000000..c72f158 --- /dev/null +++ b/tests/Feature/SubjectIndexingTest.php @@ -0,0 +1,220 @@ +app['config']->set('database.default', 'testing'); + $this->app['config']->set('database.connections.testing', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + + $this->loadMigrationsFrom(__DIR__.'/../../database/migrations'); +}); + +it('indexes a subject when findCompany succeeds and auto_index is enabled', function () { + Http::fake([ + 'https://ares.gov.cz/*' => Http::response([ + 'ico' => '27074358', + 'obchodniJmeno' => 'Asseco Central Europe, a.s.', + 'sidlo' => [ + 'nazevObce' => 'Praha', + ], + ]), + ]); + + $client = app(AresClientInterface::class); + $client->findCompany('27074358'); + + IndexAresSubject::dispatchSync( + ic: '27074358', + name: 'Asseco Central Europe, a.s.', + city: 'Praha', + ); + + expect(AresSubject::find('27074358')) + ->not->toBeNull() + ->name->toBe('Asseco Central Europe, a.s.') + ->city->toBe('Praha'); +}); + +it('searches subjects by name', function () { + AresSubject::query()->create(['ic' => '27074358', 'name' => 'Asseco Central Europe, a.s.', 'city' => 'Praha']); + AresSubject::query()->create(['ic' => '25596641', 'name' => 'Skoda Auto a.s.', 'city' => 'Mlada Boleslav']); + AresSubject::query()->create(['ic' => '00177041', 'name' => 'Skoda Investment a.s.', 'city' => 'Praha']); + + $search = app(SubjectSearchService::class); + $results = $search->search('Skoda'); + + expect($results)->toHaveCount(2) + ->and($results->first()->name)->toContain('Skoda'); +}); + +it('searches subjects by ic prefix', function () { + AresSubject::query()->create(['ic' => '27074358', 'name' => 'Asseco Central Europe, a.s.', 'city' => 'Praha']); + AresSubject::query()->create(['ic' => '27082440', 'name' => 'Another Company s.r.o.', 'city' => 'Brno']); + AresSubject::query()->create(['ic' => '25596641', 'name' => 'Skoda Auto a.s.', 'city' => 'Mlada Boleslav']); + + $search = app(SubjectSearchService::class); + $results = $search->search('2707'); + + expect($results)->toHaveCount(1) + ->and($results->first()->ic)->toBe('27074358'); +}); + +it('respects the search limit', function () { + for ($i = 1; $i <= 5; $i++) { + AresSubject::query()->create([ + 'ic' => str_pad((string) $i, 8, '0', STR_PAD_LEFT), + 'name' => "Test Company {$i}", + 'city' => 'Praha', + ]); + } + + $search = app(SubjectSearchService::class); + $results = $search->search('Test', 3); + + expect($results)->toHaveCount(3); +}); + +it('returns empty collection for empty query', function () { + $search = app(SubjectSearchService::class); + $results = $search->search(''); + + expect($results)->toBeEmpty(); +}); + +it('returns SubjectData DTOs from search', function () { + AresSubject::query()->create(['ic' => '27074358', 'name' => 'Asseco Central Europe, a.s.', 'city' => 'Praha']); + + $search = app(SubjectSearchService::class); + $results = $search->search('Asseco'); + + expect($results->first()) + ->toBeInstanceOf(SubjectData::class) + ->ic->toBe('27074358') + ->name->toBe('Asseco Central Europe, a.s.') + ->city->toBe('Praha'); +}); + +it('indexes subject via job', function () { + IndexAresSubject::dispatchSync( + ic: '27074358', + name: 'Asseco Central Europe, a.s.', + city: 'Praha', + ); + + $subject = AresSubject::find('27074358'); + + expect($subject) + ->not->toBeNull() + ->name->toBe('Asseco Central Europe, a.s.') + ->city->toBe('Praha'); +}); + +it('updates existing subject via job', function () { + AresSubject::query()->create([ + 'ic' => '27074358', + 'name' => 'Old Name', + 'city' => 'Brno', + ]); + + IndexAresSubject::dispatchSync( + ic: '27074358', + name: 'New Name', + city: 'Praha', + ); + + $subject = AresSubject::find('27074358'); + + expect($subject) + ->name->toBe('New Name') + ->city->toBe('Praha'); +}); + +it('searches via the client facade', function () { + AresSubject::query()->create(['ic' => '27074358', 'name' => 'Asseco Central Europe, a.s.', 'city' => 'Praha']); + + $results = app(AresClientInterface::class)->search('Asseco'); + + expect($results)->toHaveCount(1) + ->and($results->first()->ic)->toBe('27074358'); +}); + +it('escapes LIKE wildcards in name search', function () { + AresSubject::query()->create(['ic' => '27074358', 'name' => 'Test 100% Company', 'city' => 'Praha']); + AresSubject::query()->create(['ic' => '25596641', 'name' => 'Test Normal Company', 'city' => 'Brno']); + + $search = app(SubjectSearchService::class); + + $results = $search->search('100%'); + + expect($results)->toHaveCount(1) + ->and($results->first()->name)->toBe('Test 100% Company'); +}); + +it('escapes LIKE wildcards in ic search', function () { + AresSubject::query()->create(['ic' => '27074358', 'name' => 'Company A', 'city' => 'Praha']); + AresSubject::query()->create(['ic' => '25596641', 'name' => 'Company B', 'city' => 'Brno']); + + $search = app(SubjectSearchService::class); + + $results = $search->search('2707_358'); + + expect($results)->toBeEmpty(); +}); + +it('serializes SubjectData to JSON', function () { + $subject = new SubjectData( + ic: '27074358', + name: 'Asseco Central Europe, a.s.', + city: 'Praha', + ); + + $json = json_encode($subject, JSON_THROW_ON_ERROR); + + expect(json_decode($json, true))->toBe([ + 'ic' => '27074358', + 'name' => 'Asseco Central Europe, a.s.', + 'city' => 'Praha', + ]); +}); + +it('returns unique job id based on ic', function () { + $job = new IndexAresSubject( + ic: '27074358', + name: 'Test', + city: 'Praha', + ); + + expect($job->uniqueId())->toBe('27074358'); +}); + +it('reports stale subjects count', function () { + AresSubject::query()->create([ + 'ic' => '27074358', + 'name' => 'Old Company', + 'city' => 'Praha', + 'indexed_at' => now()->subDays(60), + ]); + + AresSubject::query()->create([ + 'ic' => '25596641', + 'name' => 'Fresh Company', + 'city' => 'Brno', + 'indexed_at' => now(), + ]); + + $search = app(SubjectSearchService::class); + + expect($search->staleCount(30))->toBe(1) + ->and($search->subjectCount())->toBe(2); +}); From 259a34b49d304ee18e56dd822dbe91233b56d04e Mon Sep 17 00:00:00 2001 From: ondrejnyklicek Date: Tue, 30 Jun 2026 09:31:48 +0200 Subject: [PATCH 6/6] docs: fix vendor:publish config tag The real publish tag registered by the package toolkit is `laravel-ares::config` (verified against ServiceProvider::publishableGroups()). Three docs used a non-existent `ares-config` tag, which would silently publish nothing. Unified all references to `laravel-ares::config`, matching the README. Co-Authored-By: Claude Opus 4.8 --- docs/configuration.md | 2 +- docs/faq.md | 2 +- docs/installation.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 91a8707..afc5614 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,7 +7,7 @@ This guide covers all configuration options available in the Laravel ARES packag Publish the configuration file to customize package settings: ```bash -php artisan vendor:publish --tag="ares-config" +php artisan vendor:publish --tag=laravel-ares::config ``` This creates `config/ares.php` with the following structure: diff --git a/docs/faq.md b/docs/faq.md index 4312153..e459b1f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -33,7 +33,7 @@ composer require nyoncode/laravel-ares No, the package works out of the box with sensible defaults. However, you can publish the configuration file to customize settings: ```bash -php artisan vendor:publish --tag="ares-config" +php artisan vendor:publish --tag=laravel-ares::config ``` ### The helper functions are not available. What should I do? diff --git a/docs/installation.md b/docs/installation.md index 3235868..a070320 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -36,7 +36,7 @@ This creates the `ares_subjects` table used for autocomplete search. If you don' Publish the configuration file to customize the package settings: ```bash -php artisan vendor:publish --tag="ares-config" +php artisan vendor:publish --tag=laravel-ares::config ``` This will create a `config/ares.php` file in your application. @@ -156,7 +156,7 @@ This ensures the helper files are properly autoloaded. If you get configuration errors, make sure to publish the config file: ```bash -php artisan vendor:publish --tag="ares-config" +php artisan vendor:publish --tag=laravel-ares::config ``` #### 3. Cache Issues