diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b6c750..9e4ca0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,9 +31,9 @@ - **Breaking:** Array return values replaced by typed DTOs - **Breaking:** Guzzle used as default transport; `withClient()` supports any PSR-18 client - `getListKey()` in Repository-Klassen nicht mehr abstrakt; Default ist `$this->resourcePath` +- Typed repository accessors (`$client->ticket()`, `$client->user()`, …) are the recommended public API; the underlying `repo()` method is `@internal`. ### Removed -- Magic resource accessor (`$client->ticket()`) — ersatzlos entfernt. Nutze `$client->repo(TicketRepository::class)`. - `ZammadClient::aliasMap()`, `resolveAlias()`, `__call()` — deprecated in v3.0, jetzt entfernt - Shared mutable Impersonation-State aus `RequestHandler` entfernt diff --git a/README.md b/README.md index 47cb62a..07072a7 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,18 @@ PSR-compliant PHP client for the [Zammad](https://zammad.com) REST API. PHP 8.1+ ```php use ZammadAPIClient\Endpoints\Tickets\TicketDTO; -use ZammadAPIClient\Endpoints\Tickets\TicketRepository; use ZammadAPIClient\ZammadClient; $client = ZammadClient::withToken('https://zammad.example', 'your-token'); // Fetch -$ticket = $client->repo(TicketRepository::class)->find(1); +$ticket = $client->ticket()->find(1); echo $ticket->title; // typed property, IDE autocomplete // Create (customer_id is required on creation; article is optional) // For production code, resolve priority_id/state_id by name via -// TicketPriorityRepository / TicketStateRepository. See examples/cookbook.php. -$created = $client->repo(TicketRepository::class)->create(new TicketDTO( +// TicketPriorityRepository / TicketStateRepository. See examples/cookbook/. +$created = $client->ticket()->create(new TicketDTO( title: 'Hello from v3', customer_id: 1, group_id: 1, @@ -40,10 +39,10 @@ $created = $client->repo(TicketRepository::class)->create(new TicketDTO( )); // Partial update -$client->repo(TicketRepository::class)->patch($created->id, ['title' => 'Updated']); +$client->ticket()->patch($created->id, ['title' => 'Updated']); // Search -foreach ($client->repo(TicketRepository::class)->search('error') as $ticket) { +foreach ($client->ticket()->search('error') as $ticket) { echo $ticket->title; } ``` @@ -111,12 +110,12 @@ ZammadClient::withToken($url, 'your-token', ## Examples -The primary example is [`examples/cookbook.php`](examples/cookbook/README.md))) — nine runnable recipes covering tickets, stateful resources, pagination, error handling, impersonation, and search. Run it against any Zammad instance: +The primary example is the [`examples/cookbook/`](examples/cookbook/README.md) directory — runnable recipes covering tickets, stateful resources, pagination, error handling, impersonation, and search. Run them against any Zammad instance: ```bash ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_URL=http://your-zammad:3000 \ ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_TOKEN=your-token \ -php examples/cookbook.php +php examples/cookbook/01-quick-start.php ``` > The env vars are named `...UNIT_TESTS...` for historical reasons. They are used by integration tests and the cookbook example. Unit tests (`make test`) need no env vars. @@ -129,10 +128,11 @@ This library offers three interaction styles. Choose based on your use case: | Style | API | Best for | |-------|-----|----------| -| **Repository + DTOs** (recommended) | `$repo->find()`, `create()`, `patch()`, `delete()` | Type-safe CRUD, IDE autocomplete, explicit intent. Use this by default. | -| **Stateful Resource** | `$repo->resource($id)->save()` / `destroy()` | Interactive editing — mutate properties step by step, only changes are sent. | +| **Repository + DTOs** (recommended) | `$client->ticket()->find()`, `create()`, `patch()`, `delete()` | Type-safe CRUD, IDE autocomplete, explicit intent. Use this by default. | +| **Stateful Resource** | `$client->ticket()->resource($id)->save()` / `destroy()` | Interactive editing — mutate properties step by step, only changes are sent. | | **Raw HTTP** | `$client->getHandler()->get()`, `delete()`, etc. | Calling endpoints that have no dedicated repository. Escape hatch. | -| **Magic accessor** (deprecated) | `$client->ticket()->find(1)` | Triggers `E_USER_DEPRECATED`. Removed in v4. Migrate to `repo()`. | + +Repositories are accessed via typed accessors: `$client->ticket()`, `$client->user()`, `$client->organization()`, `$client->group()`, `$client->ticketArticle()`, `$client->ticketState()`, `$client->ticketPriority()`, `$client->tag()`, `$client->textModule()`, `$client->link()`. The underlying `repo()` method is internal. ### Connecting @@ -146,16 +146,16 @@ $client = ZammadClient::withToken('https://zammad.example', 'your-token'); ### Fetching ```php -// Access via typed repository — autocomplete, type-safe -$ticket = $client->repo(TicketRepository::class)->find(1); -$user = $client->repo(UserRepository::class)->find(1); -$group = $client->repo(GroupRepository::class)->find(1); +// Access via typed accessor — autocomplete, type-safe +$ticket = $client->ticket()->find(1); +$user = $client->user()->find(1); +$group = $client->group()->find(1); ``` ### Accessing values ```php -$ticket = $client->repo(TicketRepository::class)->find(1); +$ticket = $client->ticket()->find(1); echo $ticket->title; // Typed property, IDE autocomplete echo $ticket->state_id; // ?int @@ -168,7 +168,7 @@ $id = $ticket->id; // Server-assigned ID (null before create) ### Creating ```php -$ticket = $client->repo(TicketRepository::class)->create(new TicketDTO( +$ticket = $client->ticket()->create(new TicketDTO( title: 'My ticket', customer_id: 1, group_id: 1, @@ -187,7 +187,7 @@ echo $ticket->id; // Server-assigned after creation ### Updating ```php -$repo = $client->repo(TicketRepository::class); +$repo = $client->ticket(); // Send a DTO — only non-null fields are transmitted $repo->patch(1, new TicketDTO(title: 'New title', group_id: 1)); @@ -204,7 +204,7 @@ $repo->patch(1, new TicketUpdateDTO(title: 'New title')); `$repo->resource($id)` returns a `Resource` wrapper — **not a DTO**. Properties are accessed and mutated via `__get`/`__set` magic (not typed properties), and changes are automatically tracked. `save()` sends only modified fields; `destroy()` sends DELETE. ```php -$repo = $client->repo(TicketRepository::class); +$repo = $client->ticket(); $r = $repo->resource(1); // Returns Resource, fetches ticket #1 echo $r->title; // Reads current title @@ -228,7 +228,7 @@ Use this for interactive workflows where you read, modify, then write. For singl | `resource($id)->save()` | Only actually changed fields (tracked) | Interactive editing with change tracking. | ```php -$repo = $client->repo(TicketRepository::class); +$repo = $client->ticket(); // Array — simplest for ad-hoc changes $repo->patch(1, ['title' => 'New title', 'state_id' => 3]); @@ -268,14 +268,14 @@ All repositories expose a `delete()` method. Repositories implementing `Deletabl | `TicketPriorityRepository` | exception | System resource, read-only | ```php -$client->repo(TicketRepository::class)->delete(1); -$client->repo(UserRepository::class)->delete(1); +$client->ticket()->delete(1); +$client->user()->delete(1); ``` ### Searching ```php -$repo = $client->repo(TicketRepository::class); +$repo = $client->ticket(); // Full-text search — returns a lazy Generator (page by page) // Use foreach directly; count()/array access requires iterator_to_array() @@ -298,7 +298,7 @@ $list->each(function ($t) { echo $t->title; }); ### Listing all ```php -$repo = $client->repo(TicketRepository::class); +$repo = $client->ticket(); // Lazy Generator — pages fetched on demand, memory-efficient // Use in foreach; need count? Use list() for PaginatedList instead. @@ -317,7 +317,7 @@ $list->each(function ($t) { echo $t->title; }); ### Ticket articles ```php -$repo = $client->repo(TicketArticleRepository::class); +$repo = $client->ticketArticle(); // All articles for a ticket (paginated) foreach ($repo->getForTicket(1) as $article) { @@ -333,7 +333,7 @@ $binary = $repo->getAttachmentContent( ### Tags ```php -$repo = $client->repo(TagRepository::class); +$repo = $client->tag(); $repo->add('Ticket', $ticketId, 'urgent'); $repo->remove('Ticket', $ticketId, 'urgent'); @@ -349,9 +349,9 @@ $results = $repo->tagSearch('urg'); // Autocomplete ```php $csv = file_get_contents('users.csv'); -$result = $client->repo(UserRepository::class)->import($csv); // Returns import summary array -$result = $client->repo(OrganizationRepository::class)->import($csv); // Returns import summary array -$client->repo(TextModuleRepository::class)->import($csv); // Returns import summary array +$result = $client->user()->import($csv); // Returns import summary array +$result = $client->organization()->import($csv); // Returns import summary array +$client->textModule()->import($csv); // Returns import summary array ``` All `import()` methods return an `array` — the Zammad API response containing import statistics (rows processed, skipped, errors). @@ -373,7 +373,7 @@ use ZammadAPIClient\Exceptions\{ }; try { - $client->repo(TicketRepository::class)->find(999999); + $client->ticket()->find(999999); } catch (NotFoundException $e) { echo $e->getMessage(); // "Resource not found: tickets/999999" } catch (ValidationException $e) { @@ -443,7 +443,7 @@ Used with `patch()` for partial ticket updates. Only non-null fields are sent to ```php // Example: reassign ticket and leave an internal note -$client->repo(TicketRepository::class)->patch(42, new TicketUpdateDTO( +$client->ticket()->patch(42, new TicketUpdateDTO( owner_id: 7, note: 'Reassigned from support queue.', )); @@ -583,12 +583,10 @@ $client->repo(TicketRepository::class)->patch(42, new TicketUpdateDTO( ## Impersonation ```php -use ZammadAPIClient\Endpoints\Tickets\TicketRepository; - // Temporary — auto-cleanup via finally // Accepts user ID (int), login, or email (string) -$client->performOnBehalfOf(1, fn() => $client->repo(TicketRepository::class)->find(42)); -$client->performOnBehalfOf('agent@example.com', fn() => $client->repo(TicketRepository::class)->find(42)); +$client->performOnBehalfOf(1, fn() => $client->ticket()->find(42)); +$client->performOnBehalfOf('agent@example.com', fn() => $client->ticket()->find(42)); // Persistent — same parameter types $client->setOnBehalfOfUser(1); @@ -629,15 +627,15 @@ v2 reference documentation is preserved in [docs/v2-reference.md](docs/v2-refere | v2 | v3 | |----|----| | `new Client(['url' => ..., 'http_token' => ...])` | `ZammadClient::withToken($url, ...)` | -| `$client->resource(TICKET)->get(1)` | `$client->repo(TicketRepository::class)->find(1)` | +| `$client->resource(TICKET)->get(1)` | `$client->ticket()->find(1)` | | `$ticket->getValue('title')` | `$ticket->title` | | `$ticket->getValues()` | `$ticket->toArray()` | -| `$ticket->setValue('title', 'x'); $ticket->save()` | `$client->repo(TicketRepository::class)->patch(1, ['title' => 'x'])` | +| `$ticket->setValue('title', 'x'); $ticket->save()` | `$client->ticket()->patch(1, ['title' => 'x'])` | | `if ($ticket->hasError()) { $ticket->getError(); }` | `catch (NotFoundException $e) { $e->getMessage(); }` | -| `$client->resource(TICKET)->search('term')` | `$client->repo(TicketRepository::class)->search('term')` | -| `$client->resource(TICKET)->all()` | `$client->repo(TicketRepository::class)->all()` | -| `$ticket->delete()` | `$client->repo(TicketRepository::class)->delete($id)` | -| `$client->resource(TAG)->add($ticketId, 'tag', 'Ticket')` | `$client->repo(TagRepository::class)->add('Ticket', $ticketId, 'tag')` *(order changed)* | +| `$client->resource(TICKET)->search('term')` | `$client->ticket()->search('term')` | +| `$client->resource(TICKET)->all()` | `$client->ticket()->all()` | +| `$ticket->delete()` | `$client->ticket()->delete($id)` | +| `$client->resource(TAG)->add($ticketId, 'tag', 'Ticket')` | `$client->tag()->add('Ticket', $ticketId, 'tag')` *(order changed)* | ## License diff --git a/docs/migration-v3-examples.md b/docs/migration-v3-examples.md index d3534ba..f814a6a 100644 --- a/docs/migration-v3-examples.md +++ b/docs/migration-v3-examples.md @@ -52,7 +52,7 @@ $title = $ticket->getValue('title'); **v3:** ```php -$ticket = $client->repo(TicketRepository::class)->find(1); +$ticket = $client->ticket()->find(1); $title = $ticket->title; ``` @@ -86,7 +86,7 @@ $ticket->save(); **v3:** ```php -$ticket = $client->repo(TicketRepository::class)->create(new TicketDTO( +$ticket = $client->ticket()->create(new TicketDTO( title: 'My ticket', group_id: 1, )); @@ -107,15 +107,15 @@ $ticket->save(); **v3:** ```php // Via array -$client->repo(TicketRepository::class)->patch(1, ['title' => 'Updated']); +$client->ticket()->patch(1, ['title' => 'Updated']); // Via TicketUpdateDTO -$client->repo(TicketRepository::class)->patch(1, new TicketUpdateDTO( +$client->ticket()->patch(1, new TicketUpdateDTO( title: 'Updated', )); // Via TicketDTO (same behavior — all non-null fields are sent) -$client->repo(TicketRepository::class)->patch(1, new TicketDTO( +$client->ticket()->patch(1, new TicketDTO( title: 'Updated', state_id: 3, )); @@ -135,7 +135,7 @@ $ticket->save(); // Sends all values back to Zammad **v3:** ```php -$resource = $client->repo(TicketRepository::class)->resource(1); +$resource = $client->ticket()->resource(1); $resource->title = 'New'; $resource->state_id = 3; $resource->save(); // Sends only {title, state_id} @@ -156,7 +156,7 @@ $ticket->delete(); **v3:** ```php -$client->repo(TicketRepository::class)->delete(1); +$client->ticket()->delete(1); ``` --- @@ -178,7 +178,7 @@ if (!is_array($tickets)) { **v3:** ```php try { - foreach ($client->repo(TicketRepository::class)->search('some text') as $ticket) { + foreach ($client->ticket()->search('some text') as $ticket) { echo $ticket->title; } } catch (NotFoundException $e) { @@ -197,7 +197,7 @@ $tickets = $client->resource(ResourceType::TICKET)->all(); **v3:** ```php -foreach ($client->repo(TicketRepository::class)->all() as $ticket) { +foreach ($client->ticket()->all() as $ticket) { echo $ticket->title; } ``` @@ -213,7 +213,7 @@ $page = $client->resource(ResourceType::TICKET)->all(1, 25); **v3:** ```php -$list = $client->repo(TicketRepository::class)->list(['per_page' => 25]); +$list = $client->ticket()->list(['per_page' => 25]); $list->page(1); // First page $list->page(2); // Second page $list->pageNext(); // Next page @@ -236,7 +236,7 @@ if ($ticket->hasError()) { **v3:** ```php try { - $ticket = $client->repo(TicketRepository::class)->find(999); + $ticket = $client->ticket()->find(999); } catch (NotFoundException $e) { echo $e->getMessage(); // "Resource not found: tickets/999" } catch (ValidationException $e) { @@ -264,7 +264,7 @@ $articles = $ticket->getTicketArticles(); **v3:** ```php -foreach ($client->repo(TicketArticleRepository::class)->getForTicket(1) as $article) { +foreach ($client->ticketArticle()->getForTicket(1) as $article) { echo $article->body; } ``` @@ -281,7 +281,7 @@ $content = $ticket_article->getAttachmentContent(23); **v3:** ```php -$content = $client->repo(TicketArticleRepository::class)->getAttachmentContent( +$content = $client->ticketArticle()->getAttachmentContent( ticketId: 1, articleId: 5, attachmentId: 23, @@ -303,14 +303,14 @@ $tags = $tag->getValue('tags'); **v3:** ```php -$client->repo(TagRepository::class)->add('Ticket', $ticketId, 'urgent'); -$client->repo(TagRepository::class)->remove('Ticket', $ticketId, 'urgent'); +$client->tag()->add('Ticket', $ticketId, 'urgent'); +$client->tag()->remove('Ticket', $ticketId, 'urgent'); -foreach ($client->repo(TagRepository::class)->all(['object' => 'Ticket', 'o_id' => $ticketId]) as $tag) { +foreach ($client->tag()->all(['object' => 'Ticket', 'o_id' => $ticketId]) as $tag) { echo $tag->value; } -$results = $client->repo(TagRepository::class)->tagSearch('urg'); +$results = $client->tag()->tagSearch('urg'); ``` --- @@ -326,7 +326,7 @@ $client->resource(ResourceType::USER)->import($csv); **v3:** ```php $csv = file_get_contents('users.csv'); -$client->repo(UserRepository::class)->import($csv); +$client->user()->import($csv); ``` --- @@ -344,7 +344,7 @@ $client->unsetOnBehalfOfUser(); ```php // Temporary (auto-cleanup on callback return or exception) $client->performOnBehalfOf(1, function () use ($client) { - $client->repo(TicketRepository::class)->find(42); + $client->ticket()->find(42); }); // Persistent @@ -353,34 +353,24 @@ $client->setOnBehalfOfUser(1); $client->unsetOnBehalfOfUser(); ``` -> **Breaking:** v2 used a username string (`'myuser'`). v3 requires a **numeric user ID**. Use `$client->repo(UserRepository::class)->search('login:myuser')` to resolve a username to an ID if needed. +> **Breaking:** v2 used a username string (`'myuser'`). v3 requires a **numeric user ID**. Use `$client->user()->search('login:myuser')` to resolve a username to an ID if needed. --- -## Resource Access — Ruby-Style vs. repo() +## Resource Access — Typed Accessors -**v3 (explicit, recommended — type-safe with IDE autocomplete):** -```php -$client->repo(TicketRepository::class)->find(1); -$client->repo(UserRepository::class)->find(1); -$client->repo(OrganizationRepository::class)->find(1); -$client->repo(GroupRepository::class)->find(1); -$client->repo(TicketArticleRepository::class)->getForTicket(1); -$client->repo(TicketStateRepository::class)->all(); -$client->repo(TicketPriorityRepository::class)->all(); -$client->repo(TagRepository::class)->add('Ticket', 1, 'urgent'); -$client->repo(TextModuleRepository::class)->find(1); -``` - -**v3 (Ruby-style convenience, deprecated in favor of repo()):** +**v3 (recommended — typed accessors with IDE autocomplete):** ```php $client->ticket()->find(1); $client->user()->find(1); $client->organization()->find(1); $client->group()->find(1); -$client->ticket_article()->getForTicket(1); -$client->ticket_state()->all(); -$client->ticket_priority()->all(); +$client->ticketArticle()->getForTicket(1); +$client->ticketState()->all(); +$client->ticketPriority()->all(); $client->tag()->add('Ticket', 1, 'urgent'); -$client->text_module()->find(1); +$client->textModule()->find(1); +$client->link()->find(1); ``` + +The underlying `repo()` method is `@internal` — prefer the typed accessors. diff --git a/docs/migration-v3.md b/docs/migration-v3.md index eb274d5..484d0fa 100644 --- a/docs/migration-v3.md +++ b/docs/migration-v3.md @@ -5,14 +5,14 @@ | v2 | v3 | |---|---| | `new Client(['url' => ..., 'http_token' => ...])` | `ZammadClient::withToken($url, $token)` | -| `$client->resource(TICKET)` | `$client->repo(TicketRepository::class)` | -| `$ticket->get(1)` | `$client->repo(TicketRepository::class)->find(1)` | +| `$client->resource(TICKET)` | `$client->ticket()` | +| `$ticket->get(1)` | `$client->ticket()->find(1)` | | `$ticket->getValue('title')` | `$ticket->title` | | `$ticket->getValues()` | `$ticket->toArray()` | -| `$ticket->setValue('title', 'x'); $ticket->save()` | `$client->repo(TicketRepository::class)->patch(1, ['title' => 'x'])` | -| `$ticket->search('term')` | `$client->repo(TicketRepository::class)->search('term')` | -| `$ticket->all()` | `$client->repo(TicketRepository::class)->all()` | -| `$ticket->delete()` | `$client->repo(TicketRepository::class)->delete($id)` | +| `$ticket->setValue('title', 'x'); $ticket->save()` | `$client->ticket()->patch(1, ['title' => 'x'])` | +| `$ticket->search('term')` | `$client->ticket()->search('term')` | +| `$ticket->all()` | `$client->ticket()->all()` | +| `$ticket->delete()` | `$client->ticket()->delete($id)` | | `if ($ticket->hasError())` | `catch (NotFoundException\|ValidationException $e)` | ## Why Migrate? @@ -50,15 +50,13 @@ $client = \ZammadAPIClient\ZammadClient::withToken( ### 3. Replace Resource Access ```php -use ZammadAPIClient\Endpoints\Tickets\TicketRepository; - // v2 $ticket = $client->resource(\ZammadAPIClient\ResourceType::TICKET); $ticket->get(1); $title = $ticket->getValue('title'); // v3 -$tickets = $client->repo(TicketRepository::class); +$tickets = $client->ticket(); $ticket = $tickets->find(1); $title = $ticket->title; ``` @@ -66,8 +64,6 @@ $title = $ticket->title; ### 4. Replace Error Handling ```php -use ZammadAPIClient\Endpoints\Tickets\TicketRepository; - // v2 $ticket->get(999); if ($ticket->hasError()) { @@ -76,7 +72,7 @@ if ($ticket->hasError()) { // v3 try { - $ticket = $client->repo(TicketRepository::class)->find(999); + $ticket = $client->ticket()->find(999); } catch (\ZammadAPIClient\Exceptions\NotFoundException $e) { $error = $e->getMessage(); } @@ -87,5 +83,5 @@ try { Register the repository in `RepositoryRegistry::DEFINITIONS` (path + DTO class). No changes to `ZammadClient` are required: ```php -$tickets = $client->repo(TicketRepository::class); +$tickets = $client->ticket(); ``` diff --git a/examples/cookbook/00-laravel.php b/examples/cookbook/07-laravel.php similarity index 100% rename from examples/cookbook/00-laravel.php rename to examples/cookbook/07-laravel.php diff --git a/examples/cookbook/00-symfony.php b/examples/cookbook/08-symfony.php similarity index 100% rename from examples/cookbook/00-symfony.php rename to examples/cookbook/08-symfony.php diff --git a/examples/cookbook/00-slim.php b/examples/cookbook/09-slim.php similarity index 96% rename from examples/cookbook/00-slim.php rename to examples/cookbook/09-slim.php index 8507ad4..4baae3a 100644 --- a/examples/cookbook/00-slim.php +++ b/examples/cookbook/09-slim.php @@ -6,7 +6,7 @@ * Requirements: * composer require symfony/http-client nyholm/psr7 * - * Run: php examples/cookbook/00-slim.php + * Run: php examples/cookbook/09-slim.php */ declare(strict_types=1); diff --git a/examples/cookbook/README.md b/examples/cookbook/README.md index 57f35ed..354556d 100644 --- a/examples/cookbook/README.md +++ b/examples/cookbook/README.md @@ -12,15 +12,15 @@ cp .env.example .env | File | Description | |---|---| | `00-plain.php` | Guzzle setup (used by recipes 01-06). Standalone, copy-paste-ready. | -| `00-laravel.php` | Laravel service container setup. | -| `00-symfony.php` | Symfony bundle setup. | -| `00-slim.php` | Non-Guzzle setup (Symfony HttpClient + Nyholm PSR-17). | | `01-quick-start.php` | Client instantiation + find ticket #1. | | `02-crud.php` | Create, read, and delete tickets. | | `03-listing.php` | `all()` streaming, `list()` pagination, `totalCount()`. | | `04-updates.php` | `patch()` partial update + `TicketUpdateDTO`. | | `05-impersonation.php` | `ImpersonationHandler` for scoped on-behalf-of requests. | | `06-search.php` | Full-text search via `search()` and `searchList()`. | +| `07-laravel.php` | Laravel service container setup. | +| `08-symfony.php` | Symfony bundle setup. | +| `09-slim.php` | Non-Guzzle setup (Symfony HttpClient + Nyholm PSR-17). | ## Run diff --git a/src/Bridge/LaravelServiceProvider.php b/src/Bridge/LaravelServiceProvider.php index 77020e1..4267a93 100644 --- a/src/Bridge/LaravelServiceProvider.php +++ b/src/Bridge/LaravelServiceProvider.php @@ -49,7 +49,6 @@ * **Usage** * * ```php - * use ZammadAPIClient\Endpoints\Tickets\TicketRepository; * use ZammadAPIClient\ZammadClient; * * class TicketController @@ -58,12 +57,12 @@ * * public function show(int $id) * { - * $ticket = $this->zammad->repo(TicketRepository::class)->find($id); + * $ticket = $this->zammad->ticket()->find($id); * } * } * * // Or resolve manually from the container: - * $tickets = app(ZammadClient::class)->repo(TicketRepository::class)->all(); + * $tickets = app(ZammadClient::class)->ticket()->all(); * ``` * * **Configuration precedence** diff --git a/src/Bridge/SymfonyBundle.php b/src/Bridge/SymfonyBundle.php index 36934b9..2fb1e66 100644 --- a/src/Bridge/SymfonyBundle.php +++ b/src/Bridge/SymfonyBundle.php @@ -49,7 +49,6 @@ * **Usage** * * ```php - * use ZammadAPIClient\Endpoints\Tickets\TicketRepository; * use ZammadAPIClient\ZammadClient; * * class TicketService @@ -58,13 +57,13 @@ * * public function findTicket(int $id) * { - * return $this->zammad->repo(TicketRepository::class)->find($id); + * return $this->zammad->ticket()->find($id); * } * } * * // Or resolve manually from the container: - * // $container->get(ZammadClient::class)->repo(TicketRepository::class)->all(); - * // $container->get('zammad_client')->repo(TicketRepository::class)->all(); + * // $container->get(ZammadClient::class)->ticket()->all(); + * // $container->get('zammad_client')->ticket()->all(); * ``` * * @see ZammadClient::withToken() diff --git a/src/Core/Contracts/ClientInterface.php b/src/Core/Contracts/ClientInterface.php index 2b70fee..3654c39 100644 --- a/src/Core/Contracts/ClientInterface.php +++ b/src/Core/Contracts/ClientInterface.php @@ -11,6 +11,8 @@ interface ClientInterface /** * Returns a memoized repository for the given endpoint. * + * @internal Use the typed accessors on the concrete client instead. + * * @template T of AbstractRepository * @param class-string $repositoryClass * @return T diff --git a/src/Core/Repository/AbstractRepository.php b/src/Core/Repository/AbstractRepository.php index f59975d..ffcc402 100644 --- a/src/Core/Repository/AbstractRepository.php +++ b/src/Core/Repository/AbstractRepository.php @@ -24,8 +24,9 @@ * the resource list in paginated list responses (varies per endpoint). * 3. Optionally add endpoint-specific convenience methods (e.g. `getForTicket`). * - * All repositories are instantiated via {@see \ZammadAPIClient\ZammadClient::repo()}, - * which injects the shared `RequestHandler` and the wiring defined in + * All repositories are obtained via the typed accessors on the client (e.g. + * {@see \ZammadAPIClient\ZammadClient::ticket()}), which inject the shared + * `RequestHandler` and the wiring defined in * {@see \ZammadAPIClient\Core\Repository\RepositoryRegistry::DEFINITIONS}. * * @template T of DTOInterface diff --git a/src/Core/Repository/RepositoryRegistry.php b/src/Core/Repository/RepositoryRegistry.php index 136220f..e841221 100644 --- a/src/Core/Repository/RepositoryRegistry.php +++ b/src/Core/Repository/RepositoryRegistry.php @@ -33,7 +33,8 @@ * Adding a resource: one entry in DEFINITIONS. * * @internal This class is not intended for direct use by consumers. - * Access repositories via {@see \ZammadAPIClient\ZammadClient::repo()}. + * Access repositories via the client's typed accessors + * (e.g. {@see \ZammadAPIClient\ZammadClient::ticket()}). */ final class RepositoryRegistry { @@ -54,7 +55,7 @@ final class RepositoryRegistry /** * Returns the API path and DTO class wired to the given repository. * - * Used by {@see \ZammadAPIClient\ZammadClient::repo()} to instantiate a + * Used by {@see \ZammadAPIClient\ZammadClient::repo()} (internal) to instantiate a * repository with the correct $resourcePath and $dtoClass arguments. * * @param class-string $repositoryClass Repository class whose wiring is requested. diff --git a/src/Core/Traits/RepositoryAccessors.php b/src/Core/Traits/RepositoryAccessors.php index dfc02c4..e6fe3c1 100644 --- a/src/Core/Traits/RepositoryAccessors.php +++ b/src/Core/Traits/RepositoryAccessors.php @@ -18,6 +18,9 @@ trait RepositoryAccessors { + /** + * @internal The typed accessors below are the public API. + */ abstract public function repo(string $repositoryClass): AbstractRepository; public function ticket(): TicketRepository diff --git a/src/Endpoints/Tickets/TicketUpdateDTO.php b/src/Endpoints/Tickets/TicketUpdateDTO.php index 5a85b4b..4b464be 100644 --- a/src/Endpoints/Tickets/TicketUpdateDTO.php +++ b/src/Endpoints/Tickets/TicketUpdateDTO.php @@ -15,7 +15,7 @@ * * Example — change only the state and owner: * ```php - * $client->repo(TicketRepository::class)->patch(42, new TicketUpdateDTO( + * $client->ticket()->patch(42, new TicketUpdateDTO( * state_id: 3, * owner_id: 7, * )); diff --git a/src/ZammadClient.php b/src/ZammadClient.php index 24c51a8..23f89d9 100644 --- a/src/ZammadClient.php +++ b/src/ZammadClient.php @@ -48,6 +48,8 @@ public function getHandler(): RequestHandlerInterface /** * Returns a memoized repository for the given repository class. * + * @internal Use the typed accessors (e.g. {@see self::ticket()}) instead. + * * @template T of AbstractRepository * @param class-string $repositoryClass * @return T