⚠️ 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.
The CMS provides data models representing core entities: users, posts, pages, events, categories, tags, event categories, and authentication tokens. Models encapsulate business logic, define relationships, and provide data persistence through the Neuron ORM.
All models extend Neuron\Orm\Model and use PHP 8 attributes for ORM configuration.
Class: Neuron\Cms\Models\User
Table: users
Description: Represents a user account with authentication, authorization, and profile attributes. Supports role-based access control, brute force protection, two-factor authentication, and remember me functionality.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_username |
string |
- | Unique username (3-50 characters) |
$_email |
string |
- | Unique email address |
$_passwordHash |
string |
- | Hashed password (Argon2id/Bcrypt) |
$_role |
string |
subscriber |
User role (admin, editor, author, subscriber) |
$_status |
string |
active |
Account status (active, inactive, suspended) |
$_emailVerified |
bool |
false |
Email verification status |
$_rememberToken |
?string |
null |
Remember me token (hashed SHA-256) |
$_twoFactorSecret |
?string |
null |
TOTP secret for 2FA |
$_twoFactorRecoveryCodes |
?array |
null |
Recovery codes for 2FA |
$_failedLoginAttempts |
int |
0 |
Failed login counter for brute force protection |
$_lockedUntil |
?DateTimeImmutable |
null |
Account lockout expiration timestamp |
$_createdAt |
?DateTimeImmutable |
Current time | User creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
$_lastLoginAt |
?DateTimeImmutable |
null |
Last successful login timestamp |
$_timezone |
string |
UTC |
User's timezone for date/time display |
| Relationship | Type | Target | Foreign Key | Description |
|---|---|---|---|---|
$_posts |
HasMany |
Post |
author_id |
Posts authored by this user |
UserRole Enum (Neuron\Cms\Enums\UserRole):
| Case | Value | Description |
|---|---|---|
UserRole::Admin |
admin |
Administrator with full system access |
UserRole::Editor |
editor |
Editor with content management access |
UserRole::Author |
author |
Author with own content management access |
UserRole::Subscriber |
subscriber |
Subscriber with read-only access (default) |
UserStatus Enum (Neuron\Cms\Enums\UserStatus):
| Case | Value | Description |
|---|---|---|
UserStatus::Active |
active |
Active account, can log in |
UserStatus::Inactive |
inactive |
Inactive account (pending email verification) |
UserStatus::Suspended |
suspended |
Suspended account, cannot log in |
Note: The deprecated constants (ROLE_, STATUS_) still exist in the User model for backward compatibility but should not be used in new code.
Identity Methods:
getId(): ?int
setId( int $id ): self
getUsername(): string
setUsername( string $username ): self
getEmail(): string
setEmail( string $email ): self
Authentication Methods:
getPasswordHash(): string
setPasswordHash( string $passwordHash ): self
getRememberToken(): ?string
setRememberToken( ?string $rememberToken ): self
getTwoFactorSecret(): ?string
setTwoFactorSecret( ?string $twoFactorSecret ): self
hasTwoFactorEnabled(): bool // Returns true if 2FA is enabled
getTwoFactorRecoveryCodes(): ?array
setTwoFactorRecoveryCodes( ?array $twoFactorRecoveryCodes ): self
Role Methods:
getRole(): string
setRole( string $role ): self
isAdmin(): bool // Check if role is admin
isEditor(): bool // Check if role is editor
isAuthor(): bool // Check if role is author
Status Methods:
getStatus(): string
setStatus( string $status ): self
isActive(): bool // Check if status is active
isSuspended(): bool // Check if status is suspended
Email Verification Methods:
isEmailVerified(): bool
setEmailVerified( bool $emailVerified ): self
Brute Force Protection Methods:
getFailedLoginAttempts(): int
setFailedLoginAttempts( int $failedLoginAttempts ): self
incrementFailedLoginAttempts(): self
resetFailedLoginAttempts(): self // Resets attempts and clears lockout
getLockedUntil(): ?DateTimeImmutable
setLockedUntil( ?DateTimeImmutable $lockedUntil ): self
isLockedOut(): bool // Returns true if currently locked
Timestamp Methods:
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
getLastLoginAt(): ?DateTimeImmutable
setLastLoginAt( ?DateTimeImmutable $lastLoginAt ): self
Utility Methods:
getTimezone(): string
setTimezone( string $timezone ): self
toArray(): array // Convert user to array (for database)
static fromArray( array $data ): static // Create user from array
Creating a new user:
use Neuron\Cms\Models\User;
$user = new User();
$user->setUsername( 'john_doe' );
$user->setEmail( '[email protected]' );
$user->setPasswordHash( password_hash( 'SecurePass123', PASSWORD_ARGON2ID ) );
$user->setRole( User::ROLE_AUTHOR );
$user->setStatus( User::STATUS_ACTIVE );
$user->setEmailVerified( true );
Checking user roles:
if( $user->isAdmin() )
{
// Grant admin access
}
if( $user->getRole() === User::ROLE_EDITOR )
{
// Grant editor access
}
Handling failed logins:
// Increment failed attempts
$user->incrementFailedLoginAttempts();
// Check if lockout threshold reached (5 attempts)
if( $user->getFailedLoginAttempts() >= 5 )
{
$lockoutDuration = 15; // minutes
$lockedUntil = (new DateTimeImmutable())->modify( "+{$lockoutDuration} minutes" );
$user->setLockedUntil( $lockedUntil );
}
// Check if user is locked
if( $user->isLockedOut() )
{
throw new Exception( 'Account is temporarily locked' );
}
// Reset on successful login
$user->resetFailedLoginAttempts();
$user->setLastLoginAt( new DateTimeImmutable() );
Enable two-factor authentication:
// Generate and store TOTP secret
$secret = $twoFactorAuthenticator->generateSecret();
$user->setTwoFactorSecret( $secret );
// Generate recovery codes
$recoveryCodes = $twoFactorAuthenticator->generateRecoveryCodes();
$user->setTwoFactorRecoveryCodes( $recoveryCodes );
// Check if 2FA is enabled
if( $user->hasTwoFactorEnabled() )
{
// Require 2FA code
}
Class: Neuron\Cms\Models\Post
Table: posts
Description: Represents a blog post with Editor.js content, metadata, and relationships to categories, tags, and author.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_title |
string |
- | Post title |
$_slug |
string |
- | URL-friendly slug (unique) |
$_body |
string |
'' |
Plain text content (derived from Editor.js JSON) |
$_contentRaw |
string |
{"blocks":[]} |
Editor.js JSON content string |
$_excerpt |
?string |
null |
Post excerpt/summary |
$_featuredImage |
?string |
null |
Featured image URL/path |
$_authorId |
int |
- | Author user ID (foreign key) |
$_status |
string |
draft |
Publication status (draft, published, scheduled) |
$_publishedAt |
?DateTimeImmutable |
null |
Publication timestamp |
$_viewCount |
int |
0 |
View counter for analytics |
$_createdAt |
?DateTimeImmutable |
Current time | Creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
| Relationship | Type | Target | Pivot Table | Description |
|---|---|---|---|---|
$_author |
BelongsTo |
User |
- | Post author |
$_categories |
BelongsToMany |
Category |
post_categories |
Post categories (many-to-many) |
$_tags |
BelongsToMany |
Tag |
post_tags |
Post tags (many-to-many) |
| Constant | Value | Description |
|---|---|---|
STATUS_DRAFT |
draft |
Draft status (not publicly visible) |
STATUS_PUBLISHED |
published |
Published status (publicly visible) |
STATUS_SCHEDULED |
scheduled |
Scheduled for future publication |
Identity & Content Methods:
getId(): ?int
setId( int $id ): self
getTitle(): string
setTitle( string $title ): self
getSlug(): string
setSlug( string $slug ): self
getBody(): string // Plain text content
setBody( string $body ): self
getContent(): array // Decoded Editor.js JSON as array
getContentRaw(): string // Raw Editor.js JSON string
setContent( string $jsonContent ): self // Set from JSON string
setContentArray( array $content ): self // Set from array (encodes to JSON)
getExcerpt(): ?string
setExcerpt( ?string $excerpt ): self
getFeaturedImage(): ?string
setFeaturedImage( ?string $featuredImage ): self
Author Methods:
getAuthorId(): int
setAuthorId( int $authorId ): self
getAuthor(): ?User
setAuthor( ?User $author ): self // Also sets author_id
Status & Publishing Methods:
getStatus(): string
setStatus( string $status ): self
isPublished(): bool
isDraft(): bool
isScheduled(): bool
getPublishedAt(): ?DateTimeImmutable
setPublishedAt( ?DateTimeImmutable $publishedAt ): self
View Tracking Methods:
getViewCount(): int
setViewCount( int $viewCount ): self
incrementViewCount(): self // Increments view count by 1
Category Methods:
getCategories(): array // Returns Category[]
setCategories( array $categories ): self // Set all categories
addCategory( Category $category ): self // Add single category
removeCategory( Category $category ): self // Remove single category
hasCategory( Category $category ): bool // Check if post has category
Tag Methods:
getTags(): array // Returns Tag[]
setTags( array $tags ): self // Set all tags
addTag( Tag $tag ): self // Add single tag
removeTag( Tag $tag ): self // Remove single tag
hasTag( Tag $tag ): bool // Check if post has tag
Timestamp Methods:
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
Utility Methods:
toArray(): array // Convert post to array
static fromArray( array $data ): static // Create post from array
Creating a new post:
use Neuron\Cms\Models\Post;
$post = new Post();
$post->setTitle( 'Getting Started with Neuron CMS' );
$post->setSlug( 'getting-started-with-neuron-cms' );
// Set content using Editor.js JSON
$editorContent = [
'blocks' => [
[
'type' => 'header',
'data' => [
'text' => 'Welcome to Neuron CMS',
'level' => 2
]
],
[
'type' => 'paragraph',
'data' => [
'text' => 'This is a comprehensive guide...'
]
]
]
];
$post->setContentArray( $editorContent );
$post->setExcerpt( 'Learn the basics of Neuron CMS...' );
$post->setAuthorId( $user->getId() );
$post->setStatus( ContentStatus::Published );
$post->setPublishedAt( new DateTimeImmutable() );
Working with categories and tags:
// Add categories
$post->addCategory( $wordpressCategory );
$post->addCategory( $phpCategory );
// Add tags
$post->addTag( $tutorialTag );
$post->addTag( $beginnerTag );
// Check relationships
if( $post->hasCategory( $phpCategory ) )
{
// Post is in PHP category
}
// Get all categories
$categories = $post->getCategories();
foreach( $categories as $category )
{
echo $category->getName();
}
Publishing workflow:
// Save as draft
$post->setStatus( ContentStatus::Draft );
// Publish immediately
$post->setStatus( ContentStatus::Published );
$post->setPublishedAt( new DateTimeImmutable() );
// Schedule for future publication
$publishDate = new DateTimeImmutable( '2025-12-01 09:00:00' );
$post->setStatus( ContentStatus::Scheduled );
$post->setPublishedAt( $publishDate );
// Check status
if( $post->isPublished() )
{
// Display on site
}
Tracking views:
// Increment view count when post is viewed
$post->incrementViewCount();
$postRepository->update( $post );
// Get view statistics
$popularPosts = $postRepository->findByViewCount( $limit = 10 );
Class: Neuron\Cms\Models\Category
Table: categories
Description: Represents a blog post category for organizing content.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_name |
string |
- | Category name |
$_slug |
string |
- | URL-friendly slug (unique) |
$_description |
?string |
null |
Category description |
$_createdAt |
?DateTimeImmutable |
Current time | Creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
| Relationship | Type | Target | Pivot Table | Description |
|---|---|---|---|---|
$_posts |
BelongsToMany |
Post |
post_categories |
Posts in this category |
getId(): ?int
setId( int $id ): self
getName(): string
setName( string $name ): self
getSlug(): string
setSlug( string $slug ): self
getDescription(): ?string
setDescription( ?string $description ): self
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
toArray(): array
static fromArray( array $data ): static
use Neuron\Cms\Models\Category;
$category = new Category();
$category->setName( 'PHP Development' );
$category->setSlug( 'php-development' );
$category->setDescription( 'Articles about PHP programming and frameworks' );
Class: Neuron\Cms\Models\Tag
Table: tags
Description: Represents a blog post tag for content classification and filtering.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_name |
string |
- | Tag name |
$_slug |
string |
- | URL-friendly slug (unique) |
$_createdAt |
?DateTimeImmutable |
Current time | Creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
| Relationship | Type | Target | Pivot Table | Description |
|---|---|---|---|---|
$_posts |
BelongsToMany |
Post |
post_tags |
Posts with this tag |
getId(): ?int
setId( int $id ): self
getName(): string
setName( string $name ): self
getSlug(): string
setSlug( string $slug ): self
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
toArray(): array
static fromArray( array $data ): static
use Neuron\Cms\Models\Tag;
$tag = new Tag();
$tag->setName( 'Tutorial' );
$tag->setSlug( 'tutorial' );
Class: Neuron\Cms\Models\Page
Table: pages
Description: Represents a CMS page with Editor.js content, templates, and SEO metadata.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_title |
string |
- | Page title |
$_slug |
string |
- | URL-friendly slug (unique) |
$_contentRaw |
string |
{"blocks":[]} |
Editor.js JSON content string |
$_template |
string |
default |
Page template name |
$_metaTitle |
?string |
null |
SEO meta title |
$_metaDescription |
?string |
null |
SEO meta description |
$_metaKeywords |
?string |
null |
SEO meta keywords |
$_authorId |
int |
- | Author user ID (foreign key) |
$_status |
string |
draft |
Publication status (draft, published) |
$_publishedAt |
?DateTimeImmutable |
null |
Publication timestamp |
$_viewCount |
int |
0 |
View counter |
$_createdAt |
?DateTimeImmutable |
Current time | Creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
| Relationship | Type | Target | Description |
|---|---|---|---|
$_author |
BelongsTo |
User |
Page author |
Status Constants:
| Constant | Value | Description |
|---|---|---|
STATUS_DRAFT |
draft |
Draft status (not publicly visible) |
STATUS_PUBLISHED |
published |
Published status (publicly visible) |
Template Constants:
| Constant | Value | Description |
|---|---|---|
TEMPLATE_DEFAULT |
default |
Default page template |
TEMPLATE_FULL_WIDTH |
full-width |
Full-width page template (no sidebar) |
TEMPLATE_SIDEBAR |
sidebar |
Page template with sidebar |
TEMPLATE_LANDING |
landing |
Landing page template |
getId(): ?int
setId( int $id ): self
getTitle(): string
setTitle( string $title ): self
getSlug(): string
setSlug( string $slug ): self
getContent(): array // Decoded Editor.js JSON as array
getContentRaw(): string // Raw Editor.js JSON string
setContent( string $jsonContent ): self
setContentArray( array $content ): self
getTemplate(): string
setTemplate( string $template ): self
getMetaTitle(): ?string
setMetaTitle( ?string $metaTitle ): self
getMetaDescription(): ?string
setMetaDescription( ?string $metaDescription ): self
getMetaKeywords(): ?string
setMetaKeywords( ?string $metaKeywords ): self
getAuthorId(): int
setAuthorId( int $authorId ): self
getAuthor(): ?User
setAuthor( ?User $author ): self
getStatus(): string
setStatus( string $status ): self
isPublished(): bool
isDraft(): bool
getPublishedAt(): ?DateTimeImmutable
setPublishedAt( ?DateTimeImmutable $publishedAt ): self
getViewCount(): int
setViewCount( int $viewCount ): self
incrementViewCount(): self
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
toArray(): array
static fromArray( array $data ): static
use Neuron\Cms\Models\Page;
$page = new Page();
$page->setTitle( 'About Us' );
$page->setSlug( 'about-us' );
$page->setTemplate( Page::TEMPLATE_FULL_WIDTH );
// Set SEO metadata
$page->setMetaTitle( 'About Our Company - Neuron CMS' );
$page->setMetaDescription( 'Learn about our mission, vision, and values...' );
$page->setMetaKeywords( 'company, about, mission, values' );
// Set content
$content = [
'blocks' => [
[
'type' => 'header',
'data' => ['text' => 'Our Story', 'level' => 1]
],
[
'type' => 'paragraph',
'data' => ['text' => 'Founded in 2020...']
]
]
];
$page->setContentArray( $content );
$page->setStatus( Page::STATUS_PUBLISHED );
$page->setAuthorId( $user->getId() );
Class: Neuron\Cms\Models\Event
Table: events
Description: Represents a calendar event with date/time, location, category, and Editor.js content. Events can be all-day or timed, single or multi-day, and support rich content with images and contact information.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_title |
string |
- | Event title |
$_slug |
string |
- | URL-friendly slug (unique) |
$_description |
?string |
null |
Short event description/summary |
$_contentRaw |
string |
{"blocks":[]} |
Editor.js JSON content string |
$_location |
?string |
null |
Event location (venue, address, etc.) |
$_startDate |
DateTimeImmutable |
- | Event start date and time |
$_endDate |
?DateTimeImmutable |
null |
Event end date and time (optional for single-day events) |
$_allDay |
bool |
false |
Whether event is all-day (no specific time) |
$_categoryId |
?int |
null |
Event category ID (foreign key) |
$_status |
string |
draft |
Publication status (draft, published) |
$_featuredImage |
?string |
null |
Featured image URL/path |
$_organizer |
?string |
null |
Event organizer name |
$_contactEmail |
?string |
null |
Contact email for event inquiries |
$_contactPhone |
?string |
null |
Contact phone for event inquiries |
$_createdBy |
?int |
null |
Creator user ID (foreign key) |
$_viewCount |
int |
0 |
View counter |
$_createdAt |
?DateTimeImmutable |
Current time | Creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
| Relationship | Type | Target | Description |
|---|---|---|---|
$_creator |
BelongsTo |
User |
Event creator |
$_category |
BelongsTo |
EventCategory |
Event category |
Status Constants:
| Constant | Value | Description |
|---|---|---|
STATUS_DRAFT |
draft |
Draft status (not publicly visible) |
STATUS_PUBLISHED |
published |
Published status (publicly visible) |
getId(): ?int
setId( int $id ): self
getTitle(): string
setTitle( string $title ): self
getSlug(): string
setSlug( string $slug ): self
getDescription(): ?string
setDescription( ?string $description ): self
getContent(): array // Decoded Editor.js JSON as array
getContentRaw(): string // Raw Editor.js JSON string
setContent( string $jsonContent ): self
setContentArray( array $content ): self
getLocation(): ?string
setLocation( ?string $location ): self
getStartDate(): DateTimeImmutable
setStartDate( DateTimeImmutable $startDate ): self
getEndDate(): ?DateTimeImmutable
setEndDate( ?DateTimeImmutable $endDate ): self
isAllDay(): bool
setAllDay( bool $allDay ): self
getCategoryId(): ?int
setCategoryId( ?int $categoryId ): self
getCategory(): ?EventCategory
setCategory( ?EventCategory $category ): self
getStatus(): string
setStatus( string $status ): self
isPublished(): bool
isDraft(): bool
getFeaturedImage(): ?string
setFeaturedImage( ?string $featuredImage ): self
getOrganizer(): ?string
setOrganizer( ?string $organizer ): self
getContactEmail(): ?string
setContactEmail( ?string $contactEmail ): self
getContactPhone(): ?string
setContactPhone( ?string $contactPhone ): self
getCreatedBy(): ?int
setCreatedBy( int $createdBy ): self
getCreator(): ?User
setCreator( ?User $creator ): self
getViewCount(): int
setViewCount( int $viewCount ): self
incrementViewCount(): self
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
// Event timing helper methods
isUpcoming(): bool // Event starts in the future
isPast(): bool // Event ended in the past
isOngoing(): bool // Event is currently happening
toArray(): array
static fromArray( array $data ): static
use Neuron\Cms\Models\Event;
use Neuron\Cms\Models\EventCategory;
// Create a single-day event
$event = new Event();
$event->setTitle( 'PHP Conference 2025' );
$event->setSlug( 'php-conference-2025' );
$event->setDescription( 'Annual PHP developers conference' );
$event->setLocation( 'Convention Center, Downtown' );
$event->setStartDate( new DateTimeImmutable( '2025-06-15 09:00:00' ) );
$event->setEndDate( new DateTimeImmutable( '2025-06-15 17:00:00' ) );
$event->setStatus( Event::STATUS_PUBLISHED );
// Create an all-day event
$event = new Event();
$event->setTitle( 'Company Retreat' );
$event->setStartDate( new DateTimeImmutable( '2025-07-01' ) );
$event->setEndDate( new DateTimeImmutable( '2025-07-03' ) );
$event->setAllDay( true );
// Set event content with Editor.js
$content = [
'blocks' => [
[
'type' => 'header',
'data' => ['text' => 'Event Details', 'level' => 2]
],
[
'type' => 'paragraph',
'data' => ['text' => 'Join us for an exciting day of learning...']
]
]
];
$event->setContentArray( $content );
// Set organizer and contact info
$event->setOrganizer( 'PHP User Group' );
$event->setContactEmail( '[email protected]' );
$event->setContactPhone( '555-0123' );
// Assign category
$event->setCategory( $conferenceCategory );
// Check event timing
if( $event->isUpcoming() ) {
echo "Event is coming soon!";
}
if( $event->isOngoing() ) {
echo "Event is happening now!";
}
Class: Neuron\Cms\Models\EventCategory
Table: event_categories
Description: Represents a category for organizing events (e.g., "Conferences", "Workshops", "Meetups"). Categories can have colors for visual distinction and are used to filter and organize calendar events.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_name |
string |
- | Category name |
$_slug |
string |
- | URL-friendly slug (unique) |
$_color |
string |
#3b82f6 |
Hex color code for visual distinction |
$_description |
?string |
null |
Category description |
$_createdAt |
?DateTimeImmutable |
Current time | Creation timestamp |
$_updatedAt |
?DateTimeImmutable |
null |
Last update timestamp |
| Relationship | Type | Target | Description |
|---|---|---|---|
$_events |
BelongsToMany |
Event |
Events in this category |
getId(): ?int
setId( int $id ): self
getName(): string
setName( string $name ): self
getSlug(): string
setSlug( string $slug ): self
getColor(): string
setColor( string $color ): self
getDescription(): ?string
setDescription( ?string $description ): self
getCreatedAt(): ?DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getUpdatedAt(): ?DateTimeImmutable
setUpdatedAt( ?DateTimeImmutable $updatedAt ): self
getEvents(): array // Event[]
setEvents( array $events ): self
toArray(): array
static fromArray( array $data ): static
use Neuron\Cms\Models\EventCategory;
// Create a category
$category = new EventCategory();
$category->setName( 'Conferences' );
$category->setSlug( 'conferences' );
$category->setColor( '#ff6b6b' ); // Red color
$category->setDescription( 'Tech conferences and industry events' );
// Create categories with different colors
$workshop = new EventCategory();
$workshop->setName( 'Workshops' )
->setSlug( 'workshops' )
->setColor( '#4ecdc4' ); // Teal
$meetup = new EventCategory();
$meetup->setName( 'Meetups' )
->setSlug( 'meetups' )
->setColor( '#ffd93d' ); // Yellow
// Use in calendar views for color-coding events
foreach( $events as $event )
{
$category = $event->getCategory();
echo "<div style='border-left: 4px solid {$category->getColor()}'>";
echo $event->getTitle();
echo "</div>";
}
Class: Neuron\Cms\Models\PasswordResetToken
Table: password_reset_tokens
Description: Represents a one-time password reset token with expiration.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_email |
string |
- | User email address |
$_token |
string |
- | Hashed token (SHA-256) |
$_createdAt |
DateTimeImmutable |
Current time | Token creation timestamp |
$_expiresAt |
DateTimeImmutable |
Created + 60 min | Token expiration timestamp |
__construct( string $email = '', string $token = '', int $expirationMinutes = 60 )
getId(): ?int
setId( int $id ): self
getEmail(): string
setEmail( string $email ): self
getToken(): string // Returns hashed token
setToken( string $token ): self
getCreatedAt(): DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getExpiresAt(): DateTimeImmutable
setExpiresAt( DateTimeImmutable $expiresAt ): self
isExpired(): bool // Returns true if token has expired
toArray(): array
static fromArray( array $data ): self
use Neuron\Cms\Models\PasswordResetToken;
// Generate token
$plainToken = bin2hex( random_bytes( 32 ) ); // 64-character hex string
$hashedToken = hash( 'sha256', $plainToken );
// Create token entity
$token = new PasswordResetToken( email: '[email protected]',
token: $hashedToken,
expirationMinutes: 60 );
// Check if expired
if( $token->isExpired() )
{
throw new Exception( 'Password reset token has expired' );
}
Class: Neuron\Cms\Models\EmailVerificationToken
Table: email_verification_tokens
Description: Represents a one-time email verification token with expiration.
| Property | Type | Default | Description |
|---|---|---|---|
$_id |
?int |
null |
Primary key (auto-increment) |
$_userId |
int |
- | User ID (foreign key) |
$_token |
string |
- | Hashed token (SHA-256) |
$_createdAt |
DateTimeImmutable |
Current time | Token creation timestamp |
$_expiresAt |
DateTimeImmutable |
Created + 60 min | Token expiration timestamp |
__construct( int $userId = 0, string $token = '', int $expirationMinutes = 60 )
getId(): ?int
setId( int $id ): self
getUserId(): int
setUserId( int $userId ): self
getToken(): string // Returns hashed token
setToken( string $token ): self
getCreatedAt(): DateTimeImmutable
setCreatedAt( DateTimeImmutable $createdAt ): self
getExpiresAt(): DateTimeImmutable
setExpiresAt( DateTimeImmutable $expiresAt ): self
isExpired(): bool // Returns true if token has expired
toArray(): array
static fromArray( array $data ): self
use Neuron\Cms\Models\EmailVerificationToken;
// Generate token
$plainToken = bin2hex( random_bytes( 32 ) ); // 64-character hex string
$hashedToken = hash( 'sha256', $plainToken );
// Create token entity
$token = new EmailVerificationToken( userId: $user->getId(),
token: $hashedToken,
expirationMinutes: 60
);
// Check if expired
if( $token->isExpired() )
{
throw new Exception( 'Email verification token has expired' );
}
User has many Posts:
class User extends Model
{
#[HasMany( Post::class, foreignKey: 'author_id' )]
private array $_posts = [];
}
Usage:
// Get all posts by user
$posts = $userRepository->findPostsByUserId( $user->getId() );
User has many Pages:
class Page extends Model
{
#[BelongsTo( User::class, foreignKey: 'author_id' )]
private ?User $_author = null;
}
Usage:
// Get page author
$author = $page->getAuthor();
Post belongs to many Categories:
class Post extends Model
{
#[BelongsToMany( Category::class, pivotTable: 'post_categories' )]
private array $_categories = [];
}
Usage:
// Add category to post
$post->addCategory( $category );
// Get all categories for post
$categories = $post->getCategories();
Post belongs to many Tags:
class Post extends Model
{
#[BelongsToMany( Tag::class, pivotTable: 'post_tags' )]
private array $_tags = [];
}
Usage:
// Add tag to post
$post->addTag( $tag );
// Get all tags for post
$tags = $post->getTags();
Many-to-many relationships require pivot tables:
post_categories table:
| Column | Type | Description |
|---|---|---|
post_id |
int |
Foreign key to posts table |
category_id |
int |
Foreign key to categories table |
post_tags table:
| Column | Type | Description |
|---|---|---|
post_id |
int |
Foreign key to posts table |
tag_id |
int |
Foreign key to tags table |
Models do not perform validation directly. Use DTOs (Data Transfer Objects) and repositories for validation:
Example with DTO:
use Neuron\Dto\Dto;
class PostDto extends Dto
{
protected function getRules(): array
{
return [
'title' => ['required', 'string', 'min:3', 'max:255'],
'slug' => ['required', 'string', 'regex:/^[a-z0-9-]+$/'],
'body' => ['required', 'string'],
'author_id' => ['required', 'integer', 'min:1'],
'status' => ['required', 'in:draft,published,scheduled']
];
}
}
Usage:
try
{
$dto = PostDto::fromArray( $request->post() );
// Create post from validated DTO
$post = Post::fromArray( $dto->toArray() );
$postRepository->create( $post );
}
catch( ValidationException $e )
{
// Handle validation errors
$errors = $e->getErrors();
}
Create custom validation in services:
class PostService
{
public function validatePost( Post $post ): void
{
// Custom slug validation
if( !$this->isSlugUnique( $post->getSlug(), $post->getId() ) )
{
throw new ValidationException( 'Slug must be unique' );
}
// Custom author validation
$author = $this->userRepository->findById( $post->getAuthorId() );
if( !$author || !$author->isActive() )
{
throw new ValidationException( 'Invalid author' );
}
// Custom publication date validation
if( $post->getStatus() === ContentStatus::Scheduled )
{
if( !$post->getPublishedAt() )
{
throw new ValidationException( 'Scheduled posts must have publication date' );
}
if( $post->getPublishedAt() < new DateTimeImmutable() )
{
throw new ValidationException( 'Publication date must be in the future' );
}
}
}
private function isSlugUnique( string $slug, ?int $excludeId = null ): bool
{
$existing = $this->postRepository->findBySlug( $slug );
if( !$existing )
{
return true;
}
// Allow same slug if it's the same post being updated
if( $excludeId && $existing->getId() === $excludeId )
{
return true;
}
return false;
}
}
Extend models to add application-specific properties:
namespace App\Models;
use Neuron\Cms\Models\User as BaseUser;
use Neuron\Orm\Attributes\Table;
#[Table( 'users' )]
class User extends BaseUser
{
private ?string $_phoneNumber = null;
private ?string $_company = null;
private ?string $_jobTitle = null;
public function getPhoneNumber(): ?string
{
return $this->_phoneNumber;
}
public function setPhoneNumber( ?string $phoneNumber ): self
{
$this->_phoneNumber = $phoneNumber;
return $this;
}
public function getCompany(): ?string
{
return $this->_company;
}
public function setCompany( ?string $company ): self
{
$this->_company = $company;
return $this;
}
public function getJobTitle(): ?string
{
return $this->_jobTitle;
}
public function setJobTitle( ?string $jobTitle ): self
{
$this->_jobTitle = $jobTitle;
return $this;
}
public function toArray(): array
{
return array_merge( parent::toArray(),
[
'phone_number' => $this->_phoneNumber,
'company' => $this->_company,
'job_title' => $this->_jobTitle
] );
}
}
Add business logic methods to extended models:
namespace App\Models;
use Neuron\Cms\Models\Post as BasePost;
class Post extends BasePost
{
/**
* Get estimated reading time in minutes
*/
public function getReadingTime(): int
{
$wordCount = str_word_count( $this->getBody() );
$wordsPerMinute = 200;
return (int) ceil( $wordCount / $wordsPerMinute );
}
/**
* Check if post was published within the last 7 days
*/
public function isNew(): bool
{
if( !$this->getPublishedAt() )
{
return false;
}
$sevenDaysAgo = (new \DateTimeImmutable())->modify( '-7 days' );
return $this->getPublishedAt() > $sevenDaysAgo;
}
/**
* Get related posts by shared categories
*/
public function getRelatedPosts( int $limit = 5 ): array
{
// Implementation would use repository
return $this->postRepository->findRelatedByCategories( $this, $limit );
}
/**
* Generate excerpt from body if not set
*/
public function getExcerpt(): ?string
{
$excerpt = parent::getExcerpt();
if( $excerpt )
{
return $excerpt;
}
// Auto-generate from body (first 200 characters)
$body = $this->getBody();
if( strlen( $body ) > 200 )
{
return substr( $body, 0, 197 ) . '...';
}
return $body;
}
}
Create completely new models for custom entities:
namespace App\Models;
use DateTimeImmutable;
use Neuron\Orm\Model;
use Neuron\Orm\Attributes\Table;
#[Table( 'products' )]
class Product extends Model
{
private ?int $_id = null;
private string $_name;
private string $_sku;
private float $_price;
private int $_stockQuantity = 0;
private ?DateTimeImmutable $_createdAt = null;
private ?DateTimeImmutable $_updatedAt = null;
public function __construct()
{
$this->_createdAt = new DateTimeImmutable();
}
public function getId(): ?int
{
return $this->_id;
}
public function setId( int $id ): self
{
$this->_id = $id;
return $this;
}
public function getName(): string
{
return $this->_name;
}
public function setName( string $name ): self
{
$this->_name = $name;
return $this;
}
public function getSku(): string
{
return $this->_sku;
}
public function setSku( string $sku ): self
{
$this->_sku = $sku;
return $this;
}
public function getPrice(): float
{
return $this->_price;
}
public function setPrice( float $price ): self
{
$this->_price = $price;
return $this;
}
public function getStockQuantity(): int
{
return $this->_stockQuantity;
}
public function setStockQuantity( int $stockQuantity ): self
{
$this->_stockQuantity = $stockQuantity;
return $this;
}
public function isInStock(): bool
{
return $this->_stockQuantity > 0;
}
public function toArray(): array
{
return [
'id' => $this->_id,
'name' => $this->_name,
'sku' => $this->_sku,
'price' => $this->_price,
'stock_quantity' => $this->_stockQuantity,
'created_at' => $this->_createdAt?->format( 'Y-m-d H:i:s' ),
'updated_at' => $this->_updatedAt?->format( 'Y-m-d H:i:s' )
];
}
public static function fromArray( array $data ): static
{
$product = new self();
if( isset( $data['id'] ) )
{
$product->setId( (int ) $data['id'] );
}
$product->setName( $data['name'] ?? '' );
$product->setSku( $data['sku'] ?? '' );
$product->setPrice( (float )($data['price'] ?? 0.0) );
$product->setStockQuantity( (int )($data['stock_quantity'] ?? 0) );
if( isset( $data['created_at'] ) && $data['created_at'] )
{
$product->_createdAt = new DateTimeImmutable( $data['created_at'] );
}
if( isset( $data['updated_at'] ) && $data['updated_at'] )
{
$product->_updatedAt = new DateTimeImmutable( $data['updated_at'] );
}
return $product;
}
}
Use DateTimeImmutable instead of DateTime for timestamps to prevent unintended mutations:
// Good
private ?DateTimeImmutable $_createdAt = null;
// Bad
private ?DateTime $_createdAt = null;
Return $this from setter methods to enable method chaining:
$user->setUsername( 'john' )
->setEmail( '[email protected]' )
->setRole( User::ROLE_AUTHOR );
Use strict types for properties and method signatures:
private string $_username; // Not ?string if required
private ?string $_excerpt = null; // Nullable for optional fields
public function setUsername( string $username ): self // Strict type hint
Keep validation logic in services, not models:
// Good
class UserService
{
public function validateUser( User $user ): void
{
// Validation logic
}
}
// Bad
class User extends Model
{
public function validate(): void
{
// Don't put validation in models
}
}
Use constants for enum-like values:
class Post extends Model
{
public const STATUS_DRAFT = 'draft';
public const STATUS_PUBLISHED = 'published';
public const STATUS_SCHEDULED = 'scheduled';
}
// Usage
$post->setStatus( ContentStatus::Published );
Handle null values appropriately:
// Good
public function getExcerpt(): ?string
{
return $this->_excerpt;
}
// Good - conditional logic
$excerpt = $post->getExcerpt() ?? 'No excerpt available';
// Bad - will throw error if null
$length = strlen( $post->getExcerpt() );