Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Thumbs.db
# composer
/vendor/
composer.phar
composer.local.json
composer.local.lock

# Mac DS_Store Files
.DS_Store
Expand Down
52 changes: 52 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# WebifyCMS User Extension

Package `webifycms/ext-user` (composer type `webifycms-extension`). Manages the
**user bounded context** — Identity, Authentication, and Authorization. An
extension of WebifyCMS (not a standalone app). Requires `webifycms/ext-base`.

## Layout

```
src/
Context/{Context}/{Layer}/{Part}
Domain/{Exception,ValueObject} # shared kernel, below every context
Application/ # cross-context use cases, as they emerge
Infrastructure/Extension/

Contexts: Identity, Authentication, Authorization
Layers: Application, Domain, Infrastructure
Parts: e.g. Application/{Query,Service,Strategy},
Domain/{Collection,Entity,Event,Exception,Guard,Policy,Repository,Service,ValueObject}
```

- `Identity/` — user identity/policies
- `Authentication/` — login/registration, guards, strategies
- `Authorization/` — roles, permissions, role assignment (user bounded context)
- `src/Domain/` — shared kernel: `UserId`, `UserStatus`, `PasswordHash` and their
exceptions. It sits below every context and must not depend on `Context`, `Application`,
or `Infrastructure` — enforced by `test/Architecture/DomainSpecification.php`.
- `src/Infrastructure/Extension/` — the `ExtensionInterface` entry point. It spans all three
contexts, which is why it cannot live inside any one of them.

## Conventions

- Namespace `Webify\User\*` (autoload `Webify\User\` → `src/`); tests `Webify\User\Test\` → `test/`.
- Follow Clean Architecture layering; Domain defines interfaces, Infrastructure implements them.
Domain must not depend on Application or Infrastructure — anything that orchestrates
repositories, event publishers, hashers, or ID generators is a use case and belongs in
`Application`. Enforced per context by the PHPat specs in `test/Architecture/`.
- Shared-kernel value objects are consumed across contexts directly; do not re-declare a
per-context copy.
- Extensions register themselves via the ext-base extension/service-provider contracts
(see `extensions/ext-base/AGENTS.md`).
- A ServiceProvider bootstrap for the context lives under `src/{Context}/Infrastructure/Service/`.

## Verification

```bash
composer test # PHPUnit
composer analyse # PHPStan level 10 + PHPat architecture rules
composer sniff # PHP-CS-Fixer dry-run
composer code-check # analyse + sniff
composer code-format # PHP-CS-Fixer fix
```
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

[![PHP Version](https://img.shields.io/badge/PHP-8.4%2B-blue)](https://www.php.net)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
[![Version](https://img.shields.io/badge/version-0.1.0--alpha-orange)](https://github.com/webifycms/ext-base)
[![Type](https://img.shields.io/badge/type-extension-green)]()

> 👋 Hey there! We're still in the early stage of development, so you may not find all the features just yet.
Expand Down
66 changes: 66 additions & 0 deletions composer.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "webifycms/ext-user",
"description": "The WebifyCMS user extension handles all user-related tasks, including user management, profile and account management, login, and registration.",
"type": "webifycms-extension",
"authors": [
{
"name": "Mohammed Shifreen",
"email": "mshifreen@gmail.com"
}
],
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.4",
"webifycms/ext-base": "*"
},
"require-dev": {
"webifycms/dev-tools": "*",
"phpunit/phpunit": "^13.1",
"fakerphp/faker": "^1.24",
"phpat/phpat": "^0.12",
"filp/whoops": "^2.18",
"middlewares/whoops": "^2.1",
"roave/security-advisories": "dev-latest"
},
"autoload": {
"psr-4": {
"Webify\\User\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Webify\\User\\Test\\": "test/"
}
},
"optimize-autoloader": true,
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
},
{
"type": "path",
"url": "../ext-base"
},
{
"type": "path",
"url": "../../dev-tools"
}
],
"scripts": {
"analyse": [
"phpstan"
],
"sniff": [
"php-cs-fixer fix --show-progress=dots --diff --dry-run"
],
"test": [
"phpunit test"
],
"code-check": ["@analyse", "@sniff"],
"code-format": [
"php-cs-fixer fix --show-progress=dots"
]
}
}
File renamed without changes.
6 changes: 6 additions & 0 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

includes:
- vendor/webifycms/dev-tools/phpstan-default.neon
- vendor/phpat/phpat/extension.neon

parameters:
level: 10
paths:
- src
- test
Expand All @@ -18,6 +20,10 @@ parameters:
reportUnmatchedIgnoredErrors: false

services:
-
class: Webify\User\Test\Architecture\DomainSpecification
tags:
- phpat.test
-
class: Webify\User\Test\Architecture\AuthenticationSpecification
tags:
Expand Down
15 changes: 11 additions & 4 deletions phpunit.dist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
Expand All @@ -16,16 +15,24 @@
testdox="true"
>
<testsuites>
<testsuite name="shared-kernel">
<directory>test/Unit/Domain</directory>
</testsuite>

<testsuite name="authentication">
<directory>test/User/Authentication</directory>
<directory>test/Unit/Context/Authentication</directory>
</testsuite>

<testsuite name="authorization">
<directory>test/User/Authorization</directory>
<directory>test/Unit/Context/Authorization</directory>
</testsuite>

<testsuite name="identity">
<directory>test/User/Identity</directory>
<directory>test/Unit/Context/Identity</directory>
</testsuite>

<testsuite name="infrastructure">
<directory>test/Unit/Infrastructure</directory>
</testsuite>
</testsuites>

Expand Down
9 changes: 9 additions & 0 deletions src/Application/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The top-level `Application` layer is reserved for cross-context use cases.
#
# Nothing lives here yet by design: cross-context transactions (for example a use case that
# registers a user and then opens a session) have not emerged. Add use cases under
# `src/Application/{Part}/` as they do.
#
# This directory is referenced by `DomainSpecification`, which forbids the shared
# kernel in `src/Domain` from depending on it. It is a guarded empty namespace, not a
# forgotten one.
52 changes: 0 additions & 52 deletions src/Authentication/Domain/Exception/InvalidUserIdException.php

This file was deleted.

39 changes: 0 additions & 39 deletions src/Authentication/Domain/Service/UserStatusTranslator.php

This file was deleted.

33 changes: 0 additions & 33 deletions src/Authentication/Domain/ValueObject/UserId.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
*/
declare(strict_types=1);

namespace Webify\User\Authentication\Application\Query\Session;
namespace Webify\User\Context\Authentication\Application\Query\Session;

use Webify\User\Authentication\Domain\ValueObject\SessionId;
use Webify\User\Context\Authentication\Domain\ValueObject\SessionId;

/**
* Query class defines the contract for a query to find a session by its identifier.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
*/
declare(strict_types=1);

namespace Webify\User\Authentication\Application\Query\Session;
namespace Webify\User\Context\Authentication\Application\Query\Session;

use Webify\User\Authentication\Domain\ValueObject\ChallengeSecretInterface;
use Webify\User\Context\Authentication\Domain\ValueObject\ChallengeSecretInterface;

/**
* Query class defines the contract for a query to find sessions by user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
declare(strict_types=1);

namespace Webify\User\Authentication\Application\Query\Session;
namespace Webify\User\Context\Authentication\Application\Query\Session;

/**
* Session read model.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
declare(strict_types=1);

namespace Webify\User\Authentication\Application\Query\Session;
namespace Webify\User\Context\Authentication\Application\Query\Session;

use Webify\Base\Contract\Collection\Collection;

Expand Down
Loading
Loading