Skip to content

Refactor: Restructure ext-user into a proper DDD module with src/Context/* #3

Description

@Shifrin

Goal

Restructure into a proper DDD module by introducing src/Context/* and proposed structure below for the sub-contexts such as Authentication, Authorisation and Identity. Additionally maximise the PHPStan level from 8.

Problems

Two problems found during analysis:

  1. The UserExtension class (implements Webify\Base\Infrastructure\Contract\ExtensionInterface)
    — the package's top-level entry point — currently has no clean home. It spans all three
    sub-contexts (Identity, Authentication, Authorization), so it must not live inside any one
    of them.

  2. The current Domain/Service classes are actually application-layer use cases mis-housed
    in the Domain layer (RegisterUser, OpenSession, VerifyChallenge, CreateRole, etc.).
    They orchestrate guards, repositories, hashers, ID generators, and event publishers — classic
    Application-layer behavior.

Additionally, several domain value objects are duplicated across contexts and have already
diverged
:

  • UserId — identical in Identity and Authentication.
  • UserStatus — diverged: Identity (Active/Inactive/Deactivated) vs Authentication
    (Active/Unverified).
  • PasswordHash — defined in Identity, consumed cross-context.

Target structure

src/
  Context/
    Identity/...           # was src/Identity
    Authentication/...     # was src/Authentication
    Authorization/...      # was src/Authorization
  Domain/
    ValueObject/           # shared: UserId, UserStatus, PasswordHash (consolidated)
  Application/             # thin: cross-context use cases, filled as they emerge
  Infrastructure/
    Extension/UserExtension.php   # ExtensionInterface entry point

Steps

1. Move sub-contexts under src/Context/*

  • Move src/Identity → src/Context/Identity, and likewise for Authentication and
    Authorization (preserving each internal {Layer}/{Part} tree).
  • Update namespaces from Webify\User\Identity\ to Webify\User\Context\Identity\
    (and the same for Authentication and Authorization).
  • PSR-4 autoload stays Webify\User\ → src/ (no composer.json change needed).
  • Update all use statements and any FQCN string references (including in tests).

2. Move use cases from Domain/Service → Context/*/Application/Action

  • Candidates to relocate as Application use cases:
    • Identity: RegisterUser, UpdateUserEmail, UpdateUserPassword
    • Authentication: OpenSession, RefreshSession, RevokeSession, IssueChallenge,
      VerifyChallenge, ChallengeBase (as an abstract base for use cases)
    • Authorization: CreateRole, AssignRole, RevokeRole, CheckAccess
  • Keep in Domain (true domain services / strategies / policies), e.g.:
    • PasswordBase (authentication strategy implementation)
    • UserStatusTranslator (domain translation logic)
    • Authorization (domain-level authorization logic)
    • ChallengeSecretDeliveryInterface and other interfaces
  • IMPORTANT: review each class individually and classify on its merits rather than blindly
    moving them all. The lists above are starting points, not final.

3. Consolidate duplicated shared value objects into top-level src/Domain/ValueObject

  • Create a single shared UserId (Webify\User\Domain\ValueObject\UserId), removing both
    Identity/Domain/ValueObject/UserId.php and Authentication/Domain/ValueObject/UserId.php.
    Choose one exception namespace; update all importers (including UserIdTest).
  • Create a single shared UserStatus enum (Webify\User\Domain\ValueObject\UserStatus).
    Reconcile the divergent cases first — propose merging to something like
    Active, Inactive, Deactivated, Unverified — then update UserStatusTranslator,
    UserMustBeActive, the User entity, and tests accordingly.
  • Create a single shared PasswordHash (Webify\User\Domain\ValueObject\PasswordHash).
  • Update all references and remove duplicate exception classes (e.g. the two
    InvalidUserIdException classes).

4. Top-level src/Application/

  • Add cross-context use cases here as real cross-context transactions emerge (for example a
    use case that registers a user and then opens a session).
  • Start thin — do not fabricate content that does not exist yet.

5. src/Infrastructure/Extension/UserExtension.php

  • Namespace Webify\User\Infrastructure\Extension\UserExtension.
  • Implements Webify\Base\Infrastructure\Contract\ExtensionInterface.
  • getId(), getSlug(), getName(), getVersion(), getTemplatePath().
  • getProviders() returns each context's service provider (the per-context service providers
    live at src/Context/{X}/Infrastructure/Service/*ServiceProvider.php implementing
    Webify\Base\Infrastructure\Contract\ServiceProviderInterface). Leave a placeholder list
    until those providers are created.

6. Update architecture tests (PHPat)

  • The three specs (IdentitySpecification, AuthenticationSpecification,
    AuthorizationSpecification) reference stale namespaces (some even
    ...\Identity\Identity\Domain) and non-existent Contract namespaces.
  • Rewrite selectors to the new Context/* namespaces.
  • Add a shared-src/Domain spec: the shared kernel must not depend on Context/*,
    Application, or Infrastructure.
  • Verify the PHPat dependency direction (Domain → Application → Infrastructure) still holds
    after moving the services.

7. Update tests

  • Move/update unit tests to mirror the new namespaces and consolidated value objects.
  • Deduplicate UserIdTest / UserStatusTest.
  • Update service tests for relocated use cases.

Acceptance Criteria

  • Verify everything with the following commands:
composer test          # PHPUnit (incl. architecture spec)
composer code-check    # analyse (should be at max level) + sniff
composer code-format   # PHP-CS-Fixer fix

Activity

  1. self-assigned this
    on Oct 3, 2026
  2. added theissue type on Oct 3, 2026
  3. linked a pull request that will close this issueRestructure package into a DDD module #4on Oct 18, 2026
  4. moved this from In progress to In review in WebifyCMSon Oct 18, 2026
  5. moved this from In review to Done in WebifyCMSon Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions