Skip to content

XOOPS 4.0 Architecture

XOOPS 4.0 introduces a modern, clean architecture that embraces Domain-Driven Design (DDD), CQRS patterns, and PSR standards while maintaining backward compatibility with existing modules.

flowchart TB
subgraph "Presentation Layer"
A[Controllers]
B[API Endpoints]
C[CLI Commands]
end
subgraph "Application Layer"
D[Command Handlers]
E[Query Handlers]
F[Application Services]
end
subgraph "Domain Layer"
G[Entities]
H[Value Objects]
I[Domain Services]
J[Domain Events]
end
subgraph "Infrastructure Layer"
K[Repositories]
L[External Services]
M[Persistence]
end
A --> D
A --> E
B --> D
B --> E
C --> D
D --> F
E --> F
F --> G
F --> H
F --> I
I --> J
G --> K
K --> M
F --> L
LayerPurposeDependencies
PresentationHTTP handling, CLI, templatesApplication Layer
ApplicationUse cases, orchestrationDomain Layer
DomainBusiness logic, entitiesNone (pure PHP)
InfrastructureExternal concerns, persistenceAll layers

The domain layer contains pure business logic with no external dependencies:

namespace Xoops\Modules\Article\Domain;
final class Article
{
private function __construct(
private ArticleId $id,
private Title $title,
private Content $content,
private AuthorId $authorId,
private ArticleStatus $status,
private \DateTimeImmutable $createdAt,
private ?\DateTimeImmutable $publishedAt
) {}
public static function create(
Title $title,
Content $content,
AuthorId $authorId
): self {
return new self(
id: ArticleId::generate(),
title: $title,
content: $content,
authorId: $authorId,
status: ArticleStatus::Draft,
createdAt: new \DateTimeImmutable(),
publishedAt: null
);
}
public function publish(): void
{
if ($this->status === ArticleStatus::Published) {
throw new ArticleAlreadyPublishedException($this->id);
}
$this->status = ArticleStatus::Published;
$this->publishedAt = new \DateTimeImmutable();
}
}

The application layer coordinates domain operations:

namespace Xoops\Modules\Article\Application\Commands;
final class PublishArticleCommand
{
public function __construct(
public readonly string $articleId
) {}
}
final class PublishArticleHandler
{
public function __construct(
private readonly ArticleRepository $repository,
private readonly EventDispatcher $events
) {}
public function handle(PublishArticleCommand $command): void
{
$article = $this->repository->findById(
ArticleId::fromString($command->articleId)
);
if (!$article) {
throw new ArticleNotFoundException($command->articleId);
}
$article->publish();
$this->repository->save($article);
$this->events->dispatch(new ArticlePublished($article->getId()));
}
}

All entities use ULIDs for unique identification:

namespace Xoops\Modules\Xmf\Domain\ValueObjects;
final class EntityId
{
private function __construct(
private readonly string $value
) {}
public static function generate(): self
{
return new self(Ulid::generate());
}
public static function fromString(string $value): self
{
if (!Ulid::isValid($value)) {
throw new InvalidEntityIdException($value);
}
return new self($value);
}
}
interface ArticleRepository
{
public function findById(ArticleId $id): ?Article;
public function save(Article $article): void;
public function delete(Article $article): void;
public function findByAuthor(AuthorId $authorId): array;
}

Commands for writes, queries for reads:

// Command (write)
$commandBus->dispatch(new PublishArticleCommand($articleId));
// Query (read)
$articles = $queryBus->dispatch(new GetRecentArticlesQuery(limit: 10));
  • PSR-4: Autoloading
  • PSR-7: HTTP Messages
  • PSR-11: Container Interface
  • PSR-15: HTTP Middleware
  • PSR-14: Event Dispatcher

Legacy modules continue to work through adapters:

// Legacy handler adapted to new repository
class LegacyArticleHandlerAdapter implements ArticleRepository
{
public function __construct(
private readonly XoopsObjectHandler $legacyHandler
) {}
public function findById(ArticleId $id): ?Article
{
$legacyObject = $this->legacyHandler->get($id->toInt());
return $legacyObject ? ArticleMapper::toDomain($legacyObject) : null;
}
}
  • Architecture Diagrams
  • CQRS Implementation
  • Event Sourcing
  • PSR Standards
  • XMF Reference Implementation