Repository navigation
Refactor: Restructure ext-user into a proper DDD module with src/Context/* #3
Description
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:
-
The
UserExtensionclass (implementsWebify\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. -
The current
Domain/Serviceclasses 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 inIdentityandAuthentication.UserStatus— diverged:Identity(Active/Inactive/Deactivated) vsAuthentication
(Active/Unverified).PasswordHash— defined inIdentity, 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 forAuthenticationand
Authorization(preserving each internal{Layer}/{Part}tree). - Update namespaces from
Webify\User\Identity\toWebify\User\Context\Identity\
(and the same forAuthenticationandAuthorization). - PSR-4 autoload stays
Webify\User\→src/(nocomposer.jsonchange needed). - Update all
usestatements 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
- Identity:
- Keep in Domain (true domain services / strategies / policies), e.g.:
PasswordBase(authentication strategy implementation)UserStatusTranslator(domain translation logic)Authorization(domain-level authorization logic)ChallengeSecretDeliveryInterfaceand 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.phpandAuthentication/Domain/ValueObject/UserId.php.
Choose one exception namespace; update all importers (includingUserIdTest). - Create a single shared
UserStatusenum (Webify\User\Domain\ValueObject\UserStatus).
Reconcile the divergent cases first — propose merging to something like
Active, Inactive, Deactivated, Unverified— then updateUserStatusTranslator,
UserMustBeActive, theUserentity, 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
InvalidUserIdExceptionclasses).
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 atsrc/Context/{X}/Infrastructure/Service/*ServiceProvider.phpimplementing
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-existentContractnamespaces. - Rewrite selectors to the new
Context/*namespaces. - Add a shared-
src/Domainspec: the shared kernel must not depend onContext/*,
Application, orInfrastructure. - 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 fixMetadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity