System architecture
System context
VerdinCore is a server-rendered CodeIgniter 4 application with three entry surfaces: public HTTP routes, protected administration routes, and a CLI maintenance route. All three use the same services and relational data.
flowchart TB
Visitor[Public visitor]
Member[Authenticated member]
Editor[Editor or administrator]
Scheduler[System scheduler]
subgraph Application[VerdinCore]
Filters[Global and route filters]
Public[Public controllers]
Admin[Admin controllers]
CLI[CLI cron controller]
Services[Application services]
Models[Models and queries]
Views[Theme and admin views]
Config[Brand, theme, feature config]
end
DB[(MariaDB)]
Files[(Media, cache, sessions, logs)]
GitHub[GitHub API, optional]
Visitor --> Filters
Member --> Filters
Editor --> Filters
Filters --> Public
Filters --> Admin
Scheduler --> CLI
Public --> Services
Admin --> Services
CLI --> Services
Services --> Models
Models --> DB
Services --> Files
Services -. configured sync .-> GitHub
Config --> Public
Config --> Views
Public --> Views
Admin --> ViewsApplication layers
Layer | Responsibility |
|---|---|
Routes and filters | Declare entry points, apply HTTPS, CSRF, caching, tracking, performance metrics, sessions, and permissions |
Controllers | Validate request shape, call services or models, choose redirects, JSON responses, or views |
Services | Own editing, sanitization, analytics, feeds, metadata, structured data, blocks, and cron workflows |
Models | Define persistence rules, relationships, visibility scopes, publication queries, and admin lists |
Cells and views | Render reusable server-side components, public pages, auth screens, and dashboard screens |
Configuration | Select the brand and theme, expose feature flags, and tune operational behavior |
Build scripts | Compile Tailwind CSS and bundle public and admin JavaScript |
Business rules sit mainly in services and models. This keeps controllers close to HTTP concerns and makes central workflows testable without rendering pages.
Public request flow
sequenceDiagram
participant Browser
participant Filters
participant Controller
participant Service
participant Model
participant View
Browser->>Filters: GET or HEAD request
Filters->>Filters: HTTPS, CSRF, optional cache lookup
Filters->>Controller: Uncached request
Controller->>Service: Resolve content and presentation data
Service->>Model: Query visible published records
Model-->>Service: Relational data
Service-->>Controller: Prepared result
Controller->>View: Render selected theme
View-->>Filters: HTML response
Filters->>Filters: Optional cache store and tracking
Filters-->>Browser: Final responseNamed routes take precedence. The hierarchical page catch-all is declared last so a page slug cannot shadow /blog, /archive, /feed, /post/..., /topic/..., or /topic-group/....
Audience-aware delivery
Public queries use an explicit visibility scope. Guests can retrieve only public records. Logged-in users may also retrieve members records.
Member-only pages are protected across their full ancestor path. If any page in the path is member-only, a guest receives a 404. Member responses receive private, no-store cache headers and noindex directives, and the application does not emit Schema.org data for them.
Unlisted posts remain directly addressable when published, but are excluded from public listing, navigation, sitemap, and automatic-feature queries.
Administration flow
Every /admin route requires an authenticated session plus dashboard.access. More specific permissions protect dashboard pages and mutations. Editors can create posts and edit their own posts, while broader content, user, and system operations require the corresponding permission.
Editing services sanitize HTML, normalize input, enforce relationships, and use database transactions where one action updates multiple records. Post saves synchronize topics in the same transaction. Block saves synchronize their elements and media references together.
Brand and theme separation
brand.name selects one configuration under brands/<brand>/config.php. verdincore.theme selects public views under app/Views/<theme>/ and front-end sources under themes/<theme>/.
The brand supplies identity and deployment metadata. The theme owns presentation. This allows several sites to share core behavior without sharing a visual identity.
Supported brands are registered in Config\Brand. Version 4.0.1 includes portfolio, tomgineer, verdindynamics, and verdincore.
Data and files
MariaDB stores authentication, content, taxonomy, block definitions, media metadata, analytics, GitHub repository data, settings, and cron history. The application uses a complete SQL schema for new installations rather than application migrations.
Generated files live outside the application layer:
public/images/contains uploaded media and generated variants;public/sitemap.xml,public/sitemap.xml.gz, andpublic/robots.txtare generated operational files;writable/contains cache, logs, sessions, uploads, and framework runtime data;public/assets/themes/contains compiled public and admin bundles.
Caching and tracking order
The page-cache filter runs before and after controllers. It is active only in production, only when enabled, only for public GET or HEAD requests, and only for guests. A controller must also mark the response as cacheable.
Tracking runs after the response. It therefore does not add a visitor cookie to a cached response. Tracking failures are logged and do not block the completed page response.
Technology choices
CodeIgniter 4 provides routing, filters, configuration, services, models, caching, validation, and server-rendered views.
CodeIgniter Shield provides session authentication, activation, groups, and permissions.
MariaDB stores relational and operational data.
Symfony HTML Sanitizer constrains stored rich content.
Tiptap powers the editing experience.
Tailwind CSS and daisyUI provide the UI foundation.
esbuild produces browser bundles.
PHPUnit covers application services and operational behavior.