Services Reference

⚠️ Documentation Under Review: This documentation is currently being updated and verified against the actual implementation. Some information may be incorrect or incomplete. Please verify all code examples against the actual source code before use.

Overview

The CMS implements a service layer that encapsulates business logic and orchestrates operations across repositories and models. Services provide a clean API for complex operations, handle validation, emit events, and enforce business rules.

Service Architecture

Key Principles

Service Organization

Services are organized by domain:

Domain Services Description
Auth Authentication, EmailVerifier, CsrfToken, PasswordResetter Authentication and security workflows
User Creator, Updater, Deleter User account management
Post Creator, Updater, Publisher, Deleter Blog post management
Category Creator, Updater, Deleter Category management
Tag Creator, Resolver Tag management and resolution
Page Creator, Updater, Deleter CMS page management
Email Sender Email composition and delivery
Member RegistrationService Member registration workflow
Content ShortcodeParser, EditorJsRenderer Content parsing and rendering
Widget IWidget, Widget, WidgetRegistry, WidgetRenderer Widget system
Dto DtoFactoryService DTO creation and caching

Auth Services

Authentication and security-related services.

Authentication

Namespace: Neuron\Cms\Services\Auth\Authentication

Handles user authentication, session management, brute force protection, and remember me functionality.

Constructor Dependencies

public function __construct( IUserRepository $userRepository,
    SessionManager $sessionManager,
    PasswordHasher $passwordHasher )
Parameter Type Description
$userRepository IUserRepository User data access
$sessionManager SessionManager Session management
$passwordHasher PasswordHasher Password hashing and verification

Configuration Methods

public function setMaxLoginAttempts( int $maxLoginAttempts ): self
public function setLockoutDuration( int $lockoutDuration ): self

Default Values:

Authentication Methods

attempt( string $username, string $password, bool $remember = false ): bool

Attempt to authenticate a user with username and password.

Parameters:

Returns: true if authentication successful, false otherwise

Behavior:

Example:

$auth = new Authentication( $userRepo, $sessionMgr, $hasher );
$auth->setMaxLoginAttempts( 5 )
     ->setLockoutDuration( 15 );

if( $auth->attempt( 'john', 'password123', true ) )
{
    // Authentication successful
    $user = $auth->user();
}
else
{
    // Authentication failed
}

login( User $user, bool $remember = false ): void

Manually log in a user (bypasses password check).

Parameters:

Behavior:

logout(): void

Log out the current user.

Behavior:

check(): bool

Check if a user is authenticated.

Returns: true if user is logged in (session or remember token), false otherwise

user(): ?User

Get the currently authenticated user.

Returns: Current user or null if not authenticated

id(): ?int

Get the current user's ID.

Returns: User ID or null if not authenticated

validateCredentials( User $user, string $password ): bool

Validate password for a user.

Returns: true if password matches, false otherwise

Role Check Methods

hasRole( string $role ): bool

Check if current user has a specific role.

isAdmin(): bool

Check if current user is an admin.

isEditorOrHigher(): bool

Check if current user is an editor or admin.

isAuthorOrHigher(): bool

Check if current user is an author, editor, or admin.

Remember Me Implementation

The Authentication service implements secure remember me functionality:

  1. Token Generation: 64-character random token (32 bytes)
  2. Token Storage: SHA-256 hash stored in database
  3. Cookie Settings: 30-day lifetime, secure, HTTPOnly
  4. Automatic Login: Cookie validated on subsequent requests

EmailVerifier

Namespace: Neuron\Cms\Services\Auth\EmailVerifier

Handles email verification token generation, validation, and account activation.

Constructor Dependencies

public function __construct( IEmailVerificationTokenRepository $tokenRepository,
    IUserRepository $userRepository,
    SettingManager $settings,
    string $basePath,
    string $verificationUrl )
Parameter Type Description
$tokenRepository IEmailVerificationTokenRepository Token data access
$userRepository IUserRepository User data access
$settings SettingManager Email configuration
$basePath string Base path for email templates
$verificationUrl string Base URL for verification links

Methods

setTokenExpirationMinutes( int $minutes ): self

Set token expiration time (default: 60 minutes).

sendVerificationEmail( User $user ): bool

Send verification email to a user.

Behavior:

Throws: Exception if email sending fails

validateToken( string $plainToken ): ?EmailVerificationToken

Validate a verification token.

Returns: Token object if valid and not expired, null otherwise

verifyEmail( string $plainToken ): bool

Verify email and activate user account.

Behavior:

Returns: true if verification successful, false if token invalid

resendVerification( string $email ): bool

Resend verification email to a user by email address.

Behavior:

cleanupExpiredTokens(): int

Clean up expired tokens from database.

Returns: Number of tokens deleted

Example:

$verifier = new EmailVerifier( $tokenRepo,
    $userRepo,
    $settings,
    '/path/to/app',
    'https://example.com/verify-email' );

$verifier->setTokenExpirationMinutes( 120 );

// Send verification email
$verifier->sendVerificationEmail( $user );

// Verify email from URL parameter
if( $verifier->verifyEmail( $_GET['token'] ) )
{
    // Email verified successfully
}

CsrfToken

Namespace: Neuron\Cms\Services\Auth\CsrfToken

Generates and validates CSRF tokens to prevent Cross-Site Request Forgery attacks.

Constructor Dependencies

public function __construct( SessionManager $sessionManager )

Methods

generate(): string

Generate a new CSRF token (64 characters).

Behavior:

getToken(): string

Get the current CSRF token (generates if doesn't exist).

validate( string $token ): bool

Validate a CSRF token.

Behavior:

regenerate(): string

Regenerate CSRF token (same as generate).

Example:

$csrf = new CsrfToken( $sessionManager );

// In form rendering
$token = $csrf->getToken();
echo "<input type='hidden' name='csrf_token' value='{$token}'>";

// In form processing
if( !$csrf->validate( $_POST['csrf_token'] ) )
{
    throw new Exception( 'CSRF validation failed' );
}

PasswordResetter

Namespace: Neuron\Cms\Services\Auth\PasswordResetter

Handles password reset token generation, validation, and password updates.

Constructor Dependencies

public function __construct( IPasswordResetTokenRepository $tokenRepository,
    IUserRepository $userRepository,
    PasswordHasher $passwordHasher,
    SettingManager $settings,
    string $basePath,
    string $resetUrl )
Parameter Type Description
$tokenRepository IPasswordResetTokenRepository Token data access
$userRepository IUserRepository User data access
$passwordHasher PasswordHasher Password hashing
$settings SettingManager Email configuration
$basePath string Base path for email templates
$resetUrl string Base URL for reset links

Methods

setTokenExpirationMinutes( int $minutes ): self

Set token expiration time (default: 60 minutes).

requestReset( string $email ): bool

Request a password reset for an email address.

Behavior:

Throws: Exception if email sending fails

validateToken( string $plainToken ): ?PasswordResetToken

Validate a reset token.

Returns: Token object if valid and not expired, null otherwise

resetPassword( string $plainToken, string $newPassword ): bool

Reset password using a valid token.

Behavior:

Returns: true if password reset successful, false if token invalid

Throws: Exception if password doesn't meet requirements

cleanupExpiredTokens(): int

Clean up expired tokens from database.

Returns: Number of tokens deleted

Example:

$resetter = new PasswordResetter( $tokenRepo,
    $userRepo,
    $hasher,
    $settings,
    '/path/to/app',
    'https://example.com/reset-password' );

// Request reset
$resetter->requestReset( '[email protected]' );

// Reset password from URL parameter
try
{
    if( $resetter->resetPassword( $_GET['token'], $_POST['new_password'] ) )
    {
        // Password reset successfully
    }
}
catch( Exception $e )
{
    // Password validation failed
    echo $e->getMessage();
}

User Services

User account management services.

User Creator

Namespace: Neuron\Cms\Services\User\Creator

Creates new users with password hashing and validation.

Constructor Dependencies

public function __construct( IUserRepository $userRepository,
    PasswordHasher $passwordHasher )

Methods

create( string $username, string $email, string $password, string $role ): User

Create a new user.

Parameters:

Returns: Created user with ID

Behavior:

Throws: Exception if password doesn't meet requirements or creation fails

Example:

$creator = new Creator( $userRepo, $passwordHasher );

try
{
    $user = $creator->create( 'john_doe',
        '[email protected]',
        'SecurePassword123!',
        User::ROLE_AUTHOR );
}
catch( Exception $e )
{
    echo "User creation failed: " . $e->getMessage();
}

User Updater

Namespace: Neuron\Cms\Services\User\Updater

Updates existing users and their passwords.

Constructor Dependencies

public function __construct( IUserRepository $userRepository,
    PasswordHasher $passwordHasher )

Methods

update( User $user, string $username, string $email, string $role, ?string $password = null, ?string $timezone = null ): User

Update an existing user.

Parameters:

Returns: Updated user

Behavior:

Throws: Exception if password doesn't meet requirements or update fails

Example:

$updater = new Updater( $userRepo, $passwordHasher );

$updater->update( $user,
    'john_doe',
    '[email protected]',
    User::ROLE_EDITOR,
    'NewPassword123!', // Optional: update password
    'America/New_York'  // Optional: update timezone );

User Deleter

Namespace: Neuron\Cms\Services\User\Deleter

Handles safe deletion of users.

Constructor Dependencies

public function __construct( IUserRepository $userRepository )

Methods

delete( int $userId ): bool

Delete a user by ID.

Returns: true if deletion successful

Behavior:

Throws: Exception if user not found

Example:

$deleter = new Deleter( $userRepo );

try
{
    if( $deleter->delete( 123 ) )
    {
        echo "User deleted successfully";
    }
}
catch( Exception $e )
{
    echo "Deletion failed: " . $e->getMessage();
}

Post Services

Blog post management services.

Post Creator

Namespace: Neuron\Cms\Services\Post\Creator

Creates new blog posts with categories and tags relationships.

Constructor Dependencies

public function __construct( IPostRepository $postRepository,
    ICategoryRepository $categoryRepository,
    TagResolver $tagResolver )

Methods

create( string $title, string $content, int $authorId, string $status, ?string $slug = null, ?string $excerpt = null, ?string $featuredImage = null, array $categoryIds = [], string $tagNames = '' ): Post

Create a new post.

Parameters:

Parameter Type Description
$title string Post title
$content string Editor.js JSON content
$authorId int Author user ID
$status string Post status (draft, published, scheduled)
$slug ?string Optional custom slug (auto-generated from title if not provided)
$excerpt ?string Optional excerpt
$featuredImage ?string Optional featured image URL
$categoryIds array Array of category IDs
$tagNames string Comma-separated tag names

Returns: Created post with ID and relationships

Behavior:

Example:

$creator = new Creator( $postRepo, $categoryRepo, $tagResolver );

$post = $creator->create( 'My First Post',
    '{"blocks":[{"type":"paragraph","data":{"text":"Hello World"}}]}',
    $user->getId(),
    Post::STATUS_PUBLISHED,
    'my-first-post',
    'This is my first blog post',
    '/images/featured.jpg',
    [1, 2], // Category IDs
    'php, tutorial, beginner' // Tags
);

Post Updater

Namespace: Neuron\Cms\Services\Post\Updater

Updates existing posts and their relationships.

Constructor Dependencies

public function __construct( IPostRepository $postRepository,
    ICategoryRepository $categoryRepository,
    TagResolver $tagResolver )

Methods

update( Post $post, string $title, string $content, string $status, ?string $slug = null, ?string $excerpt = null, ?string $featuredImage = null, array $categoryIds = [], string $tagNames = '' ): Post

Update an existing post.

Parameters: Same as Post Creator create() method, except first parameter is the post to update

Returns: Updated post

Behavior:

Example:

$updater = new Updater( $postRepo, $categoryRepo, $tagResolver );

$updater->update( $post,
    'Updated Title',
    '{"blocks":[...]}',
    Post::STATUS_PUBLISHED,
    null, // Keep existing slug
    'Updated excerpt',
    null, // Keep existing featured image
    [1, 3], // New category IDs
    'php, advanced' // New tags );

Post Publisher

Namespace: Neuron\Cms\Services\Post\Publisher

Handles publishing, unpublishing, and scheduling posts.

Constructor Dependencies

public function __construct( IPostRepository $postRepository )

Methods

publish( Post $post ): Post

Publish a draft post.

Behavior:

Returns: Updated post

Throws: Exception if post already published or persistence fails

unpublish( Post $post ): Post

Unpublish a post (revert to draft).

Behavior:

Returns: Updated post

Throws: Exception if post not published or persistence fails

schedule( Post $post, DateTimeImmutable $publishAt ): Post

Schedule a post for future publication.

Parameters:

Behavior:

Returns: Updated post

Throws: Exception if date is in the past or persistence fails

Example:

$publisher = new Publisher( $postRepo );

// Publish immediately
$publisher->publish( $post );

// Schedule for future
$futureDate = new DateTimeImmutable( '+7 days' );
$publisher->schedule( $post, $futureDate );

// Unpublish
$publisher->unpublish( $post );

Post Deleter

Namespace: Neuron\Cms\Services\Post\Deleter

Handles safe deletion of posts.

Constructor Dependencies

public function __construct( IPostRepository $postRepository )

Methods

delete( Post $post ): bool

Delete a post.

Returns: true if deletion successful

deleteById( int $id ): bool

Delete a post by ID.

Returns: true if deletion successful

Example:

$deleter = new Deleter( $postRepo );

// Delete by object
$deleter->delete( $post );

// Delete by ID
$deleter->deleteById( 123 );

Category Services

Category management services.

Category Creator

Namespace: Neuron\Cms\Services\Category\Creator

Creates new categories with slug generation.

Constructor Dependencies

public function __construct( ICategoryRepository $categoryRepository )

Methods

create( string $name, string $slug, ?string $description = null ): Category

Create a new category.

Parameters:

Returns: Created category with ID

Behavior:

Example:

$creator = new Creator( $categoryRepo );

$category = $creator->create( 'Web Development',
    'web-development',
    'Articles about web development' );

Category Updater

Namespace: Neuron\Cms\Services\Category\Updater

Updates existing categories.

Constructor Dependencies

public function __construct( ICategoryRepository $categoryRepository )

Methods

update( Category $category, string $name, string $slug, ?string $description = null ): Category

Update an existing category.

Parameters: Same as Category Creator, except first parameter is the category to update

Returns: Updated category

Behavior:

Example:

$updater = new Updater( $categoryRepo );

$updater->update( $category,
    'Frontend Development',
    'frontend-development',
    'Updated description' );

Category Deleter

Namespace: Neuron\Cms\Services\Category\Deleter

Handles safe deletion of categories.

Constructor Dependencies

public function __construct( ICategoryRepository $categoryRepository )

Methods

delete( int $categoryId ): bool

Delete a category by ID.

Returns: true if deletion successful

Behavior:

Throws: Exception if category not found

Example:

$deleter = new Deleter( $categoryRepo );

try
{
    $deleter->delete( 5 );
}
catch( Exception $e )
{
    echo "Deletion failed: " . $e->getMessage();
}

Tag Services

Tag management and resolution services.

Tag Creator

Namespace: Neuron\Cms\Services\Tag\Creator

Creates individual tags with slug generation.

Constructor Dependencies

public function __construct( ITagRepository $tagRepository )

Methods

create( string $name, ?string $slug = null ): Tag

Create a new tag.

Parameters:

Returns: Created tag with ID

Behavior:

Example:

$creator = new Creator( $tagRepo );

$tag = $creator->create( 'PHP 8', 'php-8' );

Tag Resolver

Namespace: Neuron\Cms\Services\Tag\Resolver

Resolves comma-separated tag names to Tag objects, creating tags that don't exist.

Constructor Dependencies

public function __construct( ITagRepository $tagRepository,
    Creator $tagCreator )

Methods

resolveFromString( string $tagNames ): array

Resolve comma-separated tag names to Tag objects.

Parameters:

Returns: Array of Tag objects

Behavior:

Example:

$resolver = new Resolver( $tagRepo, $tagCreator );

$tags = $resolver->resolveFromString( 'php, tutorial, beginner' );
// Returns: [Tag( 'php' ), Tag( 'tutorial' ), Tag( 'beginner' )]
// Creates any tags that didn't already exist

$post->setTags( $tags );

Page Services

CMS page management services.

Page Creator

Namespace: Neuron\Cms\Services\Page\Creator

Creates new pages with validation and slug generation.

Constructor Dependencies

public function __construct( IPageRepository $pageRepository )

Methods

create( string $title, string $content, int $authorId, string $status, ?string $slug = null, string $template = Page::TEMPLATE_DEFAULT, ?string $metaTitle = null, ?string $metaDescription = null, ?string $metaKeywords = null ): Page

Create a new page.

Parameters:

Parameter Type Description
$title string Page title
$content string Editor.js JSON content
$authorId int Author user ID
$status string Page status (draft, published)
$slug ?string Optional custom slug (auto-generated if not provided)
$template string Template name (default, full-width, sidebar, landing)
$metaTitle ?string SEO meta title
$metaDescription ?string SEO meta description
$metaKeywords ?string SEO meta keywords

Returns: Created page with ID

Behavior:

Example:

$creator = new Creator( $pageRepo );

$page = $creator->create( 'About Us',
    '{"blocks":[...]}',
    $user->getId(),
    Page::STATUS_PUBLISHED,
    'about',
    Page::TEMPLATE_DEFAULT,
    'About Our Company',
    'Learn more about our company and mission',
    'company, about, mission'
);

Page Updater

Namespace: Neuron\Cms\Services\Page\Updater

Updates existing pages with validation.

Constructor Dependencies

public function __construct( IPageRepository $pageRepository )

Methods

update( Page $page, string $title, string $content, string $status, ?string $slug = null, string $template = Page::TEMPLATE_DEFAULT, ?string $metaTitle = null, ?string $metaDescription = null, ?string $metaKeywords = null ): bool

Update an existing page.

Parameters: Same as Page Creator, except first parameter is the page to update

Returns: true if update successful

Behavior:

Example:

$updater = new Updater( $pageRepo );

$updater->update( $page,
    'About Our Company',
    '{"blocks":[...]}',
    Page::STATUS_PUBLISHED,
    'about',
    Page::TEMPLATE_FULL_WIDTH,
    'Updated Meta Title',
    'Updated meta description',
    'updated, keywords' );

Page Deleter

Namespace: Neuron\Cms\Services\Page\Deleter

Handles page deletion.

Constructor Dependencies

public function __construct( IPageRepository $pageRepository )

Methods

delete( Page $page ): bool

Delete a page.

Returns: true if deletion successful

Example:

$deleter = new Deleter( $pageRepo );

$deleter->delete( $page );

Event Services

Event creation, modification, and deletion for calendar/event management.

Event Creator

Namespace: Neuron\Cms\Services\Event\Creator

Creates new calendar events with validation.

Constructor Dependencies

public function __construct( IEventRepository $eventRepository,
    IEventCategoryRepository $categoryRepository,
    ?IRandom $random = null )
Parameter Type Description
$eventRepository IEventRepository Event data access
$categoryRepository IEventCategoryRepository Category validation
$random ?IRandom Random ID generator for slug fallback

Methods

create( ... ): Event

Create a new event.

Parameters:

Parameter Type Required Default Description
$title string Yes - Event title
$startDate DateTimeImmutable Yes - Event start date/time
$createdBy int Yes - Creator user ID
$status string Yes - Status (draft, published)
$slug ?string No null Custom slug (auto-generated if null)
$description ?string No null Short description
$contentRaw string No {"blocks":[]} Editor.js JSON content
$location ?string No null Event location
$endDate ?DateTimeImmutable No null End date/time
$allDay bool No false All-day event flag
$categoryId ?int No null Event category ID
$featuredImage ?string No null Featured image URL
$organizer ?string No null Organizer name
$contactEmail ?string No null Contact email
$contactPhone ?string No null Contact phone

Returns: Created Event with ID

Throws: RuntimeException if slug exists or category not found

Example:

use Neuron\Cms\Services\Event\Creator;
use Neuron\Cms\Models\Event;

$creator = new Creator( $eventRepo, $categoryRepo );

// Create simple event
$event = $creator->create( title: 'PHP Conference 2025',
    startDate: new DateTimeImmutable( '2025-06-15 09:00:00' ),
    createdBy: $userId,
    status: Event::STATUS_PUBLISHED,
    location: 'Convention Center',
    description: 'Annual PHP developers conference'
);

// Create all-day multi-day event
$event = $creator->create( title: 'Company Retreat',
    startDate: new DateTimeImmutable( '2025-07-01' ),
    createdBy: $userId,
    status: Event::STATUS_PUBLISHED,
    endDate: new DateTimeImmutable( '2025-07-03' ),
    allDay: true,
    categoryId: $categoryId
);

// Create event with full details
$content = json_encode( [
    'blocks' => [
        ['type' => 'header', 'data' => ['text' => 'Event Details', 'level' => 2]],
        ['type' => 'paragraph', 'data' => ['text' => 'Join us for...']]
    ]
] );

$event = $creator->create( title: 'Workshop: Modern PHP',
    startDate: new DateTimeImmutable( '2025-08-10 14:00:00' ),
    createdBy: $userId,
    status: Event::STATUS_DRAFT,
    slug: 'modern-php-workshop',
    description: 'Learn PHP 8.4+ features',
    contentRaw: $content,
    location: 'Tech Hub, Room 203',
    categoryId: $workshopCategoryId,
    organizer: 'PHP User Group',
    contactEmail: '[email protected]',
    contactPhone: '555-0123'
);

Event Updater

Namespace: Neuron\Cms\Services\Event\Updater

Updates existing events with validation.

Constructor Dependencies

public function __construct( IEventRepository $eventRepository,
    IEventCategoryRepository $categoryRepository )

Methods

update( Event $event, ... ): Event

Update an event.

Parameters: Same as Creator (except no $createdBy)

Returns: Updated Event

Throws: RuntimeException if slug exists for another event or category not found

Example:

$updater = new Updater( $eventRepo, $categoryRepo );

$event = $eventRepo->findById( $eventId );

$updatedEvent = $updater->update( event: $event,
    title: 'PHP Conference 2025 (Updated )',
    startDate: new DateTimeImmutable( '2025-06-16 09:00:00' ),
    status: Event::STATUS_PUBLISHED,
    location: 'New Venue, Downtown'
);

Event Deleter

Namespace: Neuron\Cms\Services\Event\Deleter

Handles event deletion.

Constructor Dependencies

public function __construct( IEventRepository $repository )

Methods

delete( Event $event ): bool

Delete an event.

Returns: true if deletion successful

Example:

$deleter = new Deleter( $eventRepo );

$deleter->delete( $event );

Event Category Services

Event category creation, modification, and deletion.

EventCategory Creator

Namespace: Neuron\Cms\Services\EventCategory\Creator

Creates new event categories with validation.

Constructor Dependencies

public function __construct( IEventCategoryRepository $repository )

Methods

create( string $name, ?string $slug = null, string $color = '#3b82f6', ?string $description = null ): EventCategory

Create a new event category.

Parameters:

Returns: Created EventCategory with ID

Throws: RuntimeException if slug already exists

Example:

use Neuron\Cms\Services\EventCategory\Creator;

$creator = new Creator( $categoryRepo );

// Create with defaults
$category = $creator->create( 'Conferences' );

// Create with custom color
$category = $creator->create( name: 'Workshops',
    slug: 'workshops',
    color: '#ff6b6b',
    description: 'Hands-on technical workshops' );

// Create multiple categories with different colors
$conferences = $creator->create( 'Conferences', color: '#3b82f6' );
$workshops = $creator->create( 'Workshops', color: '#4ecdc4' );
$meetups = $creator->create( 'Meetups', color: '#ffd93d' );

EventCategory Updater

Namespace: Neuron\Cms\Services\EventCategory\Updater

Updates existing event categories.

Constructor Dependencies

public function __construct( IEventCategoryRepository $repository )

Methods

update( EventCategory $category, string $name, string $slug, string $color, ?string $description = null ): EventCategory

Update an event category.

Returns: Updated EventCategory

Throws: RuntimeException if slug exists for another category

Example:

$updater = new Updater( $categoryRepo );

$category = $categoryRepo->findById( $categoryId );

$updatedCategory = $updater->update( category: $category,
    name: 'Tech Conferences',
    slug: 'tech-conferences',
    color: '#0066cc',
    description: 'Technology industry conferences' );

EventCategory Deleter

Namespace: Neuron\Cms\Services\EventCategory\Deleter

Handles event category deletion.

Constructor Dependencies

public function __construct( IEventCategoryRepository $repository )

Methods

delete( EventCategory $category ): bool

Delete an event category.

Returns: true if deletion successful

Example:

$deleter = new Deleter( $categoryRepo );

$deleter->delete( $category );

Email Services

Email composition and delivery.

Sender

Namespace: Neuron\Cms\Services\Email\Sender

Sends emails with templates using PHPMailer.

Constructor Dependencies

public function __construct( ?SettingManager $settings = null,
    string $basePath = '' )
Parameter Type Description
$settings ?SettingManager Email configuration (SMTP, credentials)
$basePath string Base path for template loading

Configuration

Email settings in config/neuron.yaml:

email:
  driver: smtp          # smtp, sendmail, or mail
  host: smtp.example.com
  port: 587
  username: [email protected]
  password: secret
  encryption: tls       # tls or ssl
  from_address: [email protected]
  from_name: Neuron CMS
  test_mode: false      # If true, logs instead of sending

Methods

Recipient Methods (Fluent Interface):

public function to( string $email, string $name = '' ): self
public function cc( string $email, string $name = '' ): self
public function bcc( string $email, string $name = '' ): self

Content Methods (Fluent Interface):

public function subject( string $subject ): self
public function body( string $body, bool $isHtml = true ): self
public function replyTo( string $email, string $name = '' ): self
public function attach( string $filePath, string $name = '' ): self

Template Rendering:

template( string $templatePath, array $data = [] ): self

Render an email template.

Parameters:

Behavior:

Template Location: {basePath}/resources/views/{templatePath}.php

Sending:

send(): bool

Send the email.

Returns: true if email sent successfully, false otherwise

Behavior:

Example:

$sender = new Sender( $settings, '/path/to/app' );

// Simple email
$sender->to( '[email protected]', 'John Doe' )
       ->subject( 'Welcome!' )
       ->body( '<h1>Welcome to our site!</h1>' )
       ->send();

// Email with template
$sender->to( '[email protected]' )
       ->subject( 'Password Reset Request' )
       ->template( 'emails/password-reset', [
           'ResetLink' => 'https://example.com/reset?token=abc123',
           'ExpirationMinutes' => 60
       ] )
       ->send();

// Email with attachments
$sender->to( '[email protected]' )
       ->subject( 'Your Invoice' )
       ->body( 'Please find your invoice attached.' )
       ->attach( '/path/to/invoice.pdf', 'invoice.pdf' )
       ->send();

Test Mode:

When email.test_mode is true, emails are logged instead of sent:

[INFO] TEST MODE - Email not sent
[INFO]   To: [email protected]
[INFO]   Subject: Password Reset Request
[INFO]   Body: Please click the following link to reset your password...

Member Services

Member registration workflow.

RegistrationService

Namespace: Neuron\Cms\Services\Member\RegistrationService

Handles user registration, validation, and email verification.

Constructor Dependencies

public function __construct( IUserRepository $userRepository,
    PasswordHasher $passwordHasher,
    EmailVerifier $emailVerifier,
    SettingManager $settings,
    ?Emitter $emitter = null )

Configuration

Registration settings in config/neuron.yaml:

member:
  registration_enabled: true
  require_email_verification: true
  default_role: subscriber

Methods

isRegistrationEnabled(): bool

Check if registration is enabled.

Returns: true if registration enabled, false otherwise

register( string $username, string $email, string $password, string $passwordConfirmation ): User

Register a new user.

Parameters:

Returns: Created user

Behavior:

Throws: Exception if validation fails or registration disabled

registerWithDto( Dto $dto ): User

Register a new user using a RegisterUser DTO.

DTO Fields:

Behavior: Same as register(), but extracts data from DTO

Validation Rules:

Field Rules
Username Required, 3-50 characters, alphanumeric + underscore, unique
Email Required, valid email format, unique
Password Required, meets password requirements
Password Confirmation Required, matches password

Password Requirements (from PasswordHasher):

Example:

$registration = new RegistrationService( $userRepo,
    $passwordHasher,
    $emailVerifier,
    $settings,
    $emitter );

// Check if registration is enabled
if( !$registration->isRegistrationEnabled() )
{
    throw new Exception( 'Registration is currently disabled' );
}

// Register with direct parameters
try
{
    $user = $registration->register( 'john_doe',
        '[email protected]',
        'SecurePassword123!',
        'SecurePassword123!' );

    if( !$user->isEmailVerified() )
    {
        echo "Please check your email to verify your account.";
    }
}
catch( Exception $e )
{
    echo "Registration failed: " . $e->getMessage();
}

// Register with DTO
$dto = $dtoFactory->createRegisterUser();
$dto->username = $_POST['username'];
$dto->email = $_POST['email'];
$dto->password = $_POST['password'];
$dto->password_confirmation = $_POST['password_confirmation'];

try
{
    $user = $registration->registerWithDto( $dto );
}
catch( Exception $e )
{
    echo "Registration failed: " . $e->getMessage();
}

Content Services

Content parsing and rendering.

ShortcodeParser

Namespace: Neuron\Cms\Services\Content\ShortcodeParser

Parses and renders WordPress-style shortcodes in content.

Shortcode Syntax

[shortcode attr="value" attr2="value2"]
[latest-posts limit="5"]
[calendar id="main" max="10"]

Constructor Dependencies

public function __construct( ?WidgetRenderer $widgetRenderer = null )

Methods

register( string $shortcode, callable $handler ): void

Register a custom shortcode handler.

Parameters:

unregister( string $shortcode ): void

Unregister a shortcode.

hasShortcode( string $shortcode ): bool

Check if shortcode is registered.

parse( string $content ): string

Parse shortcodes in content.

Returns: Content with shortcodes replaced by rendered HTML

Behavior:

Built-in Shortcodes:

Example:

$parser = new ShortcodeParser( $widgetRenderer );

// Register custom shortcode
$parser->register( 'greeting', function( $attrs ) {
    $name = $attrs['name'] ?? 'Guest';
    return "<p>Hello, {$name}!</p>";
});

// Parse content
$content = "Welcome! [greeting name=\"John\"] [latest-posts limit=\"5\"]";
$rendered = $parser->parse( $content );

Custom Shortcode Example:

// Register property listings shortcode
$parser->register( 'property-listings', function( $attrs ) use ( $propertyRepo ) {
    $type = $attrs['type'] ?? 'all';
    $limit = $attrs['limit'] ?? 10;

    $properties = $propertyRepo->findByType( $type, $limit );

    $html = '<div class="property-listings">';
    foreach( $properties as $property )
{
        $html .= '<div class="property-card">...</div>';
    }
    $html .= '</div>';

    return $html;
});

// Use in content
$content = "Available properties: [property-listings type=\"apartment\" limit=\"5\"]";
$rendered = $parser->parse( $content );

EditorJsRenderer

Namespace: Neuron\Cms\Services\Content\EditorJsRenderer

Renders Editor.js JSON content to HTML with Bootstrap classes.

Constructor Dependencies

public function __construct( ?ShortcodeParser $shortcodeParser = null )

Methods

render( array $editorData ): string

Render Editor.js JSON data to HTML.

Parameters:

Returns: Rendered HTML with Bootstrap classes

Supported Block Types:

Block Type HTML Output Classes
header <h1> to <h6> my-4
paragraph <p> mb-3
list <ul> or <ol> mb-3
image <figure><img> img-fluid, border, bg-light, etc.
quote <blockquote> blockquote, my-4, alignment classes
code <pre><code> my-4
delimiter <hr> my-4
raw Raw HTML (sanitized) None

Behavior:

Example:

$renderer = new EditorJsRenderer( $shortcodeParser );

$editorData = [
    'blocks' => [
        [
            'type' => 'header',
            'data' => [
                'text' => 'My Article Title',
                'level' => 1
            ]
        ],
        [
            'type' => 'paragraph',
            'data' => [
                'text' => 'This is the first paragraph with <strong>bold</strong> text.'
            ]
        ],
        [
            'type' => 'list',
            'data' => [
                'style' => 'unordered',
                'items' => ['Item 1', 'Item 2', 'Item 3']
            ]
        ]
    ]
];

$html = $renderer->render( $editorData );

Output:

<h1 class='my-4'>My Article Title</h1>
<p class='mb-3'>This is the first paragraph with <strong>bold</strong> text.</p>
<ul class='mb-3'>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

Image Block Example:

[
    'type' => 'image',
    'data' => [
        'file' => ['url' => '/images/photo.jpg'],
        'caption' => 'Photo caption',
        'stretched' => false,
        'withBorder' => true,
        'withBackground' => false
    ]
]

Output:

<figure class='my-4'>
  <img src='/images/photo.jpg' class='img-fluid border' alt='Photo caption'>
  <figcaption class='text-center text-muted mt-2'>Photo caption</figcaption>
</figure>

Widget Services

Extensible widget/shortcode system.

IWidget

Namespace: Neuron\Cms\Services\Widget\IWidget

Widget interface for creating custom widgets that can be used as shortcodes.

Methods

public function getName(): string
public function render( array $attrs ): string
public function getDescription(): string
public function getAttributes(): array

Example Implementation:

class LatestPostsWidget implements IWidget
{
    private IPostRepository $_postRepo;

    public function __construct( IPostRepository $postRepo )
    {
        $this->_postRepo = $postRepo;
    }

    public function getName(): string
    {
        return 'latest-posts';
    }

    public function render( array $attrs ): string
    {
        $limit = $attrs['limit'] ?? 5;
        $posts = $this->_postRepo->getPublished( $limit );

        $html = '<div class="latest-posts">';
        foreach( $posts as $post )
{
            $html .= '<h3>' . htmlspecialchars( $post->getTitle() ) . '</h3>';
        }
        $html .= '</div>';

        return $html;
    }

    public function getDescription(): string
    {
        return 'Display latest published posts';
    }

    public function getAttributes(): array
    {
        return [
            'limit' => 'Number of posts to display (default: 5)'
        ];
    }
}

Widget

Namespace: Neuron\Cms\Services\Widget\Widget

Abstract base class for widgets with helper methods.

Methods

getDescription(): string

Get widget description (override if needed).

getAttributes(): array

Get supported attributes (override if needed).

attr( array $attrs, string $key, mixed $default = null ): mixed

Helper to get attribute with default value.

view( string $template, array $data = [] ): string

Helper to render a view template.

sanitizeHtml( string $html ): string

Sanitize HTML to prevent XSS.

Example:

class PricingTableWidget extends Widget
{
    public function getName(): string
    {
        return 'pricing-table';
    }

    public function render( array $attrs ): string
    {
        $plan = $this->attr( $attrs, 'plan', 'basic' );
        $currency = $this->attr( $attrs, 'currency', '$' );

        return $this->view( '/path/to/pricing-table.php', [
            'plan' => $plan,
            'currency' => $currency
        ] );
    }

    public function getDescription(): string
    {
        return 'Display a pricing table';
    }

    public function getAttributes(): array
    {
        return [
            'plan' => 'Pricing plan to display (default: basic)',
            'currency' => 'Currency symbol (default: $)'
        ];
    }
}

WidgetRegistry

Namespace: Neuron\Cms\Services\Widget\WidgetRegistry

Registry for managing widgets with automatic shortcode parser integration.

Constructor Dependencies

public function __construct( ShortcodeParser $parser )

Methods

register( IWidget $widget ): void

Register a widget.

Behavior:

unregister( string $name ): void

Unregister a widget.

getAll(): array

Get all registered widgets (for documentation).

Returns: Array of IWidget objects

get( string $name ): ?IWidget

Get widget by name.

has( string $name ): bool

Check if widget exists.

Example:

$parser = new ShortcodeParser( $widgetRenderer );
$registry = new WidgetRegistry( $parser );

// Register widgets
$registry->register( new LatestPostsWidget( $postRepo ) );
$registry->register( new CalendarWidget( $eventRepo, $categoryRepo ) );
$registry->register( new PropertyListingsWidget( $propertyRepo ) );

// Use in content
$content = "Check out our [latest-posts limit=\"5\"] and [calendar limit=\"10\" upcoming=\"true\"]";
$rendered = $parser->parse( $content );

// List all registered widgets for documentation
foreach( $registry->getAll() as $widget ) {
    echo $widget->getName() . ': ' . $widget->getDescription() . "\n";
    foreach( $widget->getAttributes() as $attr => $description ) {
        echo "  - {$attr}: {$description}\n";
    }
}

WidgetRenderer

Namespace: Neuron\Cms\Services\Widget\WidgetRenderer

Renders built-in CMS widgets.

Constructor Dependencies

public function __construct( ?IPostRepository $postRepository = null )

Methods

render( string $widgetType, array $config ): string

Render a widget by type.

Built-in Widgets:

latest-posts

Render latest published posts.

Attributes:

Example:

$renderer = new WidgetRenderer( $postRepo );

// Render latest posts
$html = $renderer->render( 'latest-posts', ['limit' => 5] );

Dto Services

DTO creation and caching.

DtoFactoryService

Namespace: Neuron\Cms\Services\Dto\DtoFactoryService

Centralized service for creating and caching DTOs from YAML definitions.

Constructor Dependencies

public function __construct( ?string $dtoDirectory = null )

Default DTO Directory: {cms}/src/Cms/Dtos

Methods

create( string $name ): Dto

Get a DTO instance by name.

Parameters:

Returns: Fresh DTO instance (cloned from cache)

Behavior:

Throws: Exception if DTO file doesn't exist

Convenience Methods:

public function createRegisterUser(): Dto
public function createCreateUser(): Dto
public function createUpdateUser(): Dto
public function createCreateCategory(): Dto
public function createUpdateCategory(): Dto
public function createCreatePost(): Dto
public function createUpdatePost(): Dto

Cache Management:

public function clearCache(): void
public function getDtoDirectory(): string

Example:

$dtoFactory = new DtoFactoryService();

// Create DTO
$dto = $dtoFactory->createRegisterUser();
$dto->username = 'john_doe';
$dto->email = '[email protected]';
$dto->password = 'SecurePassword123!';
$dto->password_confirmation = 'SecurePassword123!';

// Validate DTO
if( !$dto->validate() ) {
    print_r( $dto->getValidationErrors() );
}

// Use with service
$user = $registrationService->registerWithDto( $dto );

Available DTOs:

DTO Name File Description
RegisterUser RegisterUserDto.yaml Member registration
CreateUser CreateUserDto.yaml Admin user creation
UpdateUser UpdateUserDto.yaml User updates
CreateCategory CreateCategoryDto.yaml Category creation
UpdateCategory UpdateCategoryDto.yaml Category updates
CreatePost CreatePostDto.yaml Post creation
UpdatePost UpdatePostDto.yaml Post updates

Service Injection Patterns

Constructor Injection

Services use constructor injection for dependencies:

class MyController
{
    private Authentication $_auth;
    private IUserRepository $_userRepo;

    public function __construct( Authentication $auth,
        IUserRepository $userRepo )
    {
        $this->_auth = $auth;
        $this->_userRepo = $userRepo;
    }

    public function dashboard()
    {
        if( !$this->_auth->check() ) {
            // Redirect to login
        }

        $user = $this->_auth->user();
        // ...
    }
}

Manual Service Creation

// Create dependencies
$userRepo = new UserRepository( $db );
$sessionMgr = new SessionManager();
$passwordHasher = new PasswordHasher();

// Create service
$auth = new Authentication( $userRepo, $sessionMgr, $passwordHasher );

// Use service
if( $auth->attempt( $_POST['username'], $_POST['password'], true ) ) {
    // Login successful
}

Service Composition

Services can be composed together:

// Base services
$userRepo = new UserRepository( $db );
$passwordHasher = new PasswordHasher();
$sessionMgr = new SessionManager();
$settings = new SettingManager( $source );
$emitter = new Emitter();

// Authentication services
$auth = new Authentication( $userRepo, $sessionMgr, $passwordHasher );
$emailVerifier = new EmailVerifier( $tokenRepo,
    $userRepo,
    $settings,
    $basePath,
    $verificationUrl );

// Registration service (composes multiple services)
$registration = new RegistrationService( $userRepo,
    $passwordHasher,
    $emailVerifier,
    $settings,
    $emitter );

// Use composed service
$user = $registration->register( $_POST['username'],
    $_POST['email'],
    $_POST['password'],
    $_POST['password_confirmation'] );

Testing Services

Unit Testing with Mocks

use PHPUnit\Framework\TestCase;

class UserCreatorTest extends TestCase
{
    public function testCreateUser()
    {
        // Create mocks
        $userRepo = $this->createMock( IUserRepository::class );
        $passwordHasher = $this->createMock( PasswordHasher::class );

        // Configure mocks
        $passwordHasher->method( 'meetsRequirements' )
                       ->willReturn( true );
        $passwordHasher->method( 'hash' )
                       ->willReturn( 'hashed_password' );

        $userRepo->method( 'create' )
                 ->willReturnCallback( function( $user ) {
                     $user->setId( 1 );
                     return $user;
                 });

        // Create service
        $creator = new Creator( $userRepo, $passwordHasher );

        // Test
        $user = $creator->create( 'john_doe',
            '[email protected]',
            'Password123!',
            User::ROLE_AUTHOR );

        $this->assertEquals( 1, $user->getId() );
        $this->assertEquals( 'john_doe', $user->getUsername() );
        $this->assertEquals( 'hashed_password', $user->getPasswordHash() );
    }
}

Integration Testing

class RegistrationServiceIntegrationTest extends TestCase
{
    private RegistrationService $registration;
    private IUserRepository $userRepo;

    protected function setUp(): void
    {
        // Create real database connection
        $db = new Database( /* test database config */ );
        $this->userRepo = new UserRepository( $db );

        // Create real services
        $passwordHasher = new PasswordHasher();
        $emailVerifier = new EmailVerifier( /* ... */ );
        $settings = new SettingManager( /* ... */ );

        $this->registration = new RegistrationService( $this->userRepo,
            $passwordHasher,
            $emailVerifier,
            $settings );

        // Clean database
        $db->exec( 'TRUNCATE TABLE users' );
    }

    public function testCompleteRegistrationWorkflow()
    {
        // Register user
        $user = $this->registration->register( 'john_doe',
            '[email protected]',
            'Password123!',
            'Password123!' );

        // Verify user in database
        $this->assertNotNull( $user->getId() );

        $dbUser = $this->userRepo->findById( $user->getId() );
        $this->assertEquals( 'john_doe', $dbUser->getUsername() );
        $this->assertEquals( User::ROLE_SUBSCRIBER, $dbUser->getRole() );
    }
}

Best Practices

Service Design

  1. Single Responsibility: Each service handles one entity or workflow
  2. Dependency Injection: Use constructor injection for dependencies
  3. Interface Dependencies: Depend on interfaces, not concrete classes
  4. Event Emission: Emit events for important state changes
  5. Transaction Safety: Design operations to be atomic where possible

Error Handling

try
{
    $user = $userCreator->create( $username, $email, $password, $role );
}
catch( Exception $e )
{
    Log::error( "User creation failed: " . $e->getMessage() );
    // Handle error appropriately
}

Validation

Validate input before calling service methods:

// Validate DTO before passing to service
$dto = $dtoFactory->createRegisterUser();
$dto->username = $_POST['username'];
$dto->email = $_POST['email'];
$dto->password = $_POST['password'];
$dto->password_confirmation = $_POST['password_confirmation'];

if( !$dto->validate() )
{
    $errors = $dto->getValidationErrors();
    // Display errors to user
    return;
}

// DTO is valid, proceed with registration
$user = $registration->registerWithDto( $dto );

Logging

Services should log important operations:

Log::info( "User created: {$user->getUsername()}" );
Log::warning( "Failed login attempt for: {$username}" );
Log::error( "Email sending failed: " . $e->getMessage() );

Security

  1. Password Hashing: Always hash passwords using PasswordHasher (Argon2id)
  2. Token Security: Use SHA-256 hashes for storing tokens
  3. Timing Attacks: Use hash_equals() for token comparison
  4. CSRF Protection: Validate CSRF tokens on state-changing requests
  5. Input Sanitization: Sanitize HTML to prevent XSS

Performance

  1. DTO Caching: DtoFactoryService caches DTOs for performance
  2. Lazy Loading: Only load relationships when needed
  3. Batch Operations: Use repository methods that support batch operations
  4. Token Cleanup: Periodically clean up expired tokens

Common Patterns

Service Factory Pattern

class ServiceFactory
{
    private array $services = [];

    public function getAuthentication(): Authentication
    {
        if( !isset( $this->services['auth'] ) )
        {
            $this->services['auth'] = new Authentication( $this->getUserRepository(),
                $this->getSessionManager(),
                $this->getPasswordHasher() );
        }
        return $this->services['auth'];
    }

    // ... other service getters
}

Service Locator Pattern (Registry)

// Register services
Registry::getInstance()->set( 'Authentication', $auth );
Registry::getInstance()->set( 'UserCreator', $userCreator );

// Retrieve services
$auth = Registry::getInstance()->get( 'Authentication' );
$userCreator = Registry::getInstance()->get( 'UserCreator' );

Middleware Pattern

class AuthenticationFilter implements IFilter
{
    private Authentication $_auth;

    public function __construct( Authentication $auth )
    {
        $this->_auth = $auth;
    }

    public function execute( Request $request ): bool
    {
        if( !$this->_auth->check() )
        {
            // Redirect to login
            header( 'Location: /login' );
            return false;
        }
        return true;
    }
}

Additional Resources