Skip to content

refactor: split ZammadClient into three concerns (ClientFactory, Impe…#154

Merged
derpixler merged 2 commits into
epic-79/pre-merge-developfrom
epix-79/refactor-zammad-client-class
Jul 21, 2026
Merged

refactor: split ZammadClient into three concerns (ClientFactory, Impe…#154
derpixler merged 2 commits into
epic-79/pre-merge-developfrom
epix-79/refactor-zammad-client-class

Conversation

@derpixler

Copy link
Copy Markdown
Contributor

refactor: split ZammadClient into three concerns (ClientFactory, ImpersonationHandler, slim client)

Split the monolithic ZammadClient into focused classes, each with a single responsibility:

ClientFactory — the only place that knows about Guzzle. Static factory methods (withToken, withBasicAuth, withOAuth2, withClient) that produce ZammadClient instances. PSR-18-agnostic withClient() path preserved.

ImpersonationHandler — stateless decorator implementing RequestHandlerInterface. Injects From header on every request including getRaw(). No shared mutable state; no footgun.

ZammadClient — slimmed from ~210 to ~75 lines. Only repo(), getHandler(), onBehalfOf() and performOnBehalfOf(). The latter creates a scoped clone (new self()) so repos within the scope run impersonated while the outer client is never touched.

Removals and cleanups:

  • __call, aliasMap, resolveAlias — duplicated RepositoryRegistry mappings, already deprecated; replaced by repo(TicketRepository::class)
  • RequestHandler:: — shared mutable state removed
  • RequestHandlerInterface::setOnBehalfOfUser/getOnBehalfOfUser — removed
  • RequestHandlerInterface::getRaw() extended with parameter for decorator compatibility
  • getListKey() made non-abstract with default ->resourcePath; 10 identical implementations removed from concrete repositories
  • phpstan ignore rule cleaned up

Discoverability: ZammadClient docblock references ClientFactory with code example. ClientFactory lives in the same namespace for IDE discovery.

This is a relocation refactoring — Guzzle decoupling and alias duplication are fixed; the repo() service locator question is deferred to phase 2.

@derpixler
derpixler force-pushed the epix-79/refactor-zammad-client-class branch 13 times, most recently from 135145e to e4f33d7 Compare July 20, 2026 15:24
…rsonationHandler, slim client)

Split the monolithic ZammadClient into focused classes:

- ClientFactory → GuzzleClientFactory implements ClientFactoryInterface
  Guzzle wiring lives exclusively in GuzzleClientFactory::buildClient()
  Non-Guzzle via new ZammadClient(new RequestHandler(...))

- ImpersonationHandler — stateless decorator implementing
  RequestHandlerInterface. Injects From header on every request
  including getRaw(). No shared mutable state.

- ZammadClient — slimmed from ~210 to ~79 lines. Only repo() and
  getHandler(). Repository access via typed, explicit, IDE-friendly
  methods (ticket(), user(), group(), etc.) implemented directly on
  the class.

Removals and cleanups:

- __call, aliasMap, resolveAlias — replaced by explicit typed methods
- RequestHandler:: — shared mutable state removed
- RequestHandlerInterface::setOnBehalfOfUser/getOnBehalfOfUser — removed
- RequestHandlerInterface::getRaw() extended with $headers parameter
  for ImpersonationHandler compatibility
- onBehalfOf() / performOnBehalfOf() — not a Client concern;
  use new ZammadClient(new ImpersonationHandler($handler, $userId))
- getListKey() default $this->resourcePath; 10 identical impls removed

Namespace structure:

  Core/Contracts/   — ClientInterface, ClientFactoryInterface
  Core/Repository/  — AbstractRepository, RepositoryRegistry, PaginatedList, Resource, ResponseParser, DtoHydrator
  Core/Transport/   — RequestHandler, RetryAfterMiddleware, ImpersonationHandler, HttpPageFetcher
  Core/Traits/      — RepositoryAccessors (opt-in for custom ClientInterface impls), HasTimestamps, HydratesFromArray, SerializesToArray
  Factory/          — GuzzleClientFactory

Bug fixes:

- HttpPageFetcher::extractIndexResults() now reads total_count from
  API response instead of hard-coding null
@derpixler
derpixler force-pushed the epix-79/refactor-zammad-client-class branch 2 times, most recently from 9831fd0 to 0e413e6 Compare July 21, 2026 06:15
- Delete monolithic examples/cookbook.php
- 00-plain.php: Guzzle setup, copy-paste-ready
- 00-laravel.php: Laravel service container reference
- 00-symfony.php: Symfony bundle reference
- 00-slim.php: Non-Guzzle setup (Symfony HttpClient + Nyholm)
- 01-quick-start.php: client setup + find()
- 02-crud.php: create, read, delete, error handling
- 03-listing.php: all() streaming, list() pagination, totalCount()
- 04-updates.php: patch(), TicketUpdateDTO, Resource wrapper
- 05-impersonation.php: ImpersonationHandler decoration
- 06-search.php: search(), searchList(), pagination
- CookbookIntegrationTest: executes recipes 01-06 via exec()
- README.md: recipe overview and run instructions
@derpixler
derpixler force-pushed the epix-79/refactor-zammad-client-class branch from 0e413e6 to a09735a Compare July 21, 2026 06:28
@derpixler
derpixler merged commit 725b849 into epic-79/pre-merge-develop Jul 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant