Skip to content

EEPIC-#79 - v3 refactor: PSR-compliant client with typed DTOs, reposi…#153

Open
derpixler wants to merge 1 commit into
masterfrom
epic-79/pre-merge-develop
Open

EEPIC-#79 - v3 refactor: PSR-compliant client with typed DTOs, reposi…#153
derpixler wants to merge 1 commit into
masterfrom
epic-79/pre-merge-develop

Conversation

@derpixler

Copy link
Copy Markdown
Contributor

EEPIC-#79 — v3: PSR-compliant PHP client with typed DTOs, repositories, and enums

Breaking Changes (v2 → v3)

Architecture

v2 v3
Client (arrays, magic strings) ZammadClient with typed factory methods
Resource + ResourceType enums Repository pattern + typed DTOs
$ticket->getValue('title') $ticket->title (typed property, IDE autocomplete)
$ticket->hasError() / $ticket->getError() Typed exceptions (NotFoundException, etc.)
new Client(['url' => ..., 'http_token' => ...]) ZammadClient::withToken($url, $token)
PHP >= 7.2 PHP >= 8.1

Files removed (~3,900 LOC)

  • src/Client.php, src/HTTPClient.php, src/HTTPClientInterface.php
  • src/Resource/ — all 10 resource classes (AbstractResource, Ticket, User, etc.)
  • src/ResourceType.php
  • src/Exception/ — old exception classes
  • test/ZammadAPIClient/ — old test suite

What's new (~10,200 LOC)

Core layer (src/Core/)

  • ZammadClient — entry point with withToken(), withOAuth2(), withBasicAuth(),
    withClient(). Auto-normalizes URL (appends /api/v1). Memoized repositories via repo().
    Impersonation via performOnBehalfOf() / setOnBehalfOfUser().
  • RequestHandler — PSR-18 HTTP transport with JSON (de)serialization, HTTP status→exception
    mapping, X-On-Behalf-Of header support, configurable retries.
  • RetryAfterMiddleware — PSR-18 decorator: auto-retries on HTTP 429 with Respect-Retry-After
    header parsing (RFC 7231 HTTP-date + delta-seconds).
  • AbstractRepository — generic CRUD with generator-based cursor pagination, explicit page
    methods, resource()/list() Ruby-style accessors, extractItems().
  • PaginatedList — ArrayAccess + IteratorAggregate with page(), pageNext(), pagePrev(),
    each(), totalCount(), allPages().
  • Resource — mutable stateful wrapper with property-level change tracking.
    save() sends only changed fields; destroy() sends DELETE.
  • DtoHydrator — reflection-based hydration: maps API array keys to constructor parameters
    with type-driven coercion via Cast.
  • Cast — safe scalar coercions: dateTime(), string(), intOrNull(), boolOrNull().
  • ConnectionConfig — immutable config DTO (verifySsl, timeout, maxRetries, logger).
  • RepositoryRegistry — single source of truth mapping 10 repositories to API paths and DTOs.
  • ResponseParser — extracts resource arrays from Zammad's {tickets: [...], assets: {...}} wrapper.
  • HttpPageFetcher — PSR-18 page fetcher for PaginatedList.
  • Traits: HasTimestamps, HydratesFromArray, SerializesToArray.

Contracts (src/Core/Contracts/)

  • RepositoryInterface — find, all, search, create, update, patch, delete
  • DTOInterface — fromArray(), toArray(), id()
  • DeletableInterface — delete($id)
  • PatchableInterface — toPatchArray()
  • RequestHandlerInterface, ClientInterface, PageFetcherInterface

Endpoints — 10 repositories + typed DTOs (src/Endpoints/)

  • Tickets: TicketRepository (CRUD + delete + getTicketArticles),
    TicketDTO (14 fields), TicketUpdateDTO (partial update, 8 fields)
  • Users: UserRepository (CRUD + delete + CSV import), UserDTO (13 fields)
  • Organizations: OrganizationRepository (CRUD + delete + CSV import), OrganizationDTO
  • Groups: GroupRepository (CRUD + delete), GroupDTO
  • TicketArticles: TicketArticleRepository (getForTicket, getAttachmentContent),
    TicketArticleDTO (25 fields), TicketArticleType enum (Note, Email, Phone, Sms, Web)
  • TicketStates: TicketStateRepository, TicketStateDTO
  • TicketPriorities: TicketPriorityRepository, TicketPriorityDTO
  • Tags: TagRepository (add, remove, tagSearch), TagDTO
  • TextModules: TextModuleRepository (CRUD + delete + CSV import), TextModuleDTO
  • Links: LinkRepository (add, remove, list), LinkDTO

Exceptions (src/Exceptions/) — typed hierarchy

  • AuthenticationException (401), ForbiddenException (403), NotFoundException (404)
  • ValidationException (422, with $errors array per-field)
  • RateLimitException (429, with $retryAfterSeconds, auto-retried)
  • ServerErrorException (5xx), NetworkException (DNS, timeout, connection refused)

Bridge (src/Bridge/)

  • LaravelServiceProvider — auto-registers ZammadClient as singleton
  • SymfonyBundle — Symfony DI integration

Tests — 227 unit tests, 12 integration test classes

  • Unit: AbstractRepository, Cast, DtoHydrator, PaginatedList, RequestHandler, Resource,
    RepositoryRegistry, RetryAfterMiddleware, all DTOs, all repositories, ZammadClient
  • Integration: Tickets, Users, Groups, Organizations, Tags, Links, TicketArticles,
    Pagination, Search, ErrorHandling, ReferenceData, User cleanup

Documentation

  • docs/migration-v3.md — step-by-step v2→v3 migration guide
  • docs/migration-v3-examples.md — 15 side-by-side before/after code examples
  • docs/v2-reference.md — preserved v2 documentation
  • docs/alternative-clients.md — non-Guzzle transport setup
  • examples/cookbook.php — 9 runnable v3 recipes
  • README.md — rewritten with DTO field tables, update method decision guide,
    error handling table (HTTP→exception mapping + auto-retry), paradigm guide

CI

  • New GitHub Actions workflow: PSR-12 lint, PHPStan level=max, 227 unit tests,
    integration tests against live Zammad instance

@derpixler
derpixler force-pushed the epic-79/pre-merge-develop branch 8 times, most recently from ee24898 to 44542a8 Compare July 18, 2026 02:44
…tories, enums, PHP 8.1+

## Breaking Changes (v2 → v3)

### Architecture
- ZammadClient with typed factory methods replaces Client (arrays, magic strings)
- Repository pattern + typed DTOs replaces Resource + ResourceType enums
- Typed exceptions (NotFoundException, etc.) replaces hasError()/getError()
- PHP >= 8.1 (was PHP >= 7.2)

### Files removed (~3,900 LOC)
- src/Client.php, src/HTTPClient.php, src/HTTPClientInterface.php
- src/Resource/ — all 10 resource classes
- src/ResourceType.php, src/Exception/
- test/ZammadAPIClient/ — old test suite
- examples/ticket.php, examples/user.php, examples/v2-usage.php

## What's new (~10,200 LOC)

### Core (src/Core/)
- ZammadClient, RequestHandler, RetryAfterMiddleware, AbstractRepository,
  PaginatedList, Resource, DtoHydrator, Cast, ConnectionConfig,
  RepositoryRegistry, ResponseParser, HttpPageFetcher, traits

### Contracts (src/Core/Contracts/)
- RepositoryInterface, DTOInterface, DeletableInterface, PatchableInterface,
  RequestHandlerInterface, ClientInterface, PageFetcherInterface

### Endpoints — 10 repositories + typed DTOs (src/Endpoints/)
- Tickets (CRUD + delete + getTicketArticles), TicketDTO, TicketUpdateDTO
- Users (CRUD + delete + CSV import), UserDTO
- Organizations (CRUD + delete + CSV import), OrganizationDTO
- Groups (CRUD + delete), GroupDTO
- TicketArticles (getForTicket, getAttachmentContent), TicketArticleDTO,
  TicketArticleType enum (Note, Email, Phone, Sms, Web)
- TicketStates (read-only), TicketStateDTO
- TicketPriorities (read-only), TicketPriorityDTO
- Tags (add, remove, tagSearch), TagDTO
- TextModules (CRUD + delete + CSV import), TextModuleDTO
- Links (add, remove, list), LinkDTO

### Exceptions — typed hierarchy (src/Exceptions/)
- AuthenticationException (401), ForbiddenException (403),
  NotFoundException (404), ValidationException (422, with $errors array),
  RateLimitException (429, auto-retried), ServerErrorException (5xx),
  NetworkException

### Bridge (src/Bridge/)
- LaravelServiceProvider, SymfonyBundle

### Tests — 227 unit tests, 12 integration test classes

### Documentation
- docs/migration-v3.md, docs/migration-v3-examples.md (15 side-by-side examples)
- docs/v2-reference.md, docs/alternative-clients.md
- examples/cookbook.php (9 runnable v3 recipes)
- README.md with DTO field tables, update decision guide, error mapping,
  paradigm guide, delete() availability table
@derpixler
derpixler force-pushed the epic-79/pre-merge-develop branch from 44542a8 to ac65aee Compare July 19, 2026 05:52
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