Publication Type: Technical Review (Whitepaper) & Architectural Roadmap (RFC)
Version: v1.0.0-draft
Audience: Senior PHP Developers, System Architects, Open Source Contributors, and AI Agents
Author / Founder: Ali Çömez (slaweally)
Technology Stack: PHP 8.2+, Symfony 7.4 LTS, Doctrine ORM, AssetMapper, Tailwind CSS Standalone Binary
Introduction: Theory, Practice, and the Pain of "Reinventing the Wheel"
When starting a new project in the modern web ecosystem, developers always find themselves on a double-edged sword. On one side are classic CMS platforms (WordPress, Drupal) packed with ready-made plugin and theme systems, yet burdened with clumsy database schemas, mountains of technical debt, and security weaknesses. On the other side are pure modern frameworks (Symfony, Laravel) that force you to start from scratch—rewriting auth, ACL, file management, and admin panels on every project and burning time in the process.
Building your own MVC stack from zero may sound romantic, but it is not rational by today's standards. A framework is not just routing and controllers; it carries a massive "invisible iceberg" of security (CSRF, XSS, SQLi prevention), Dependency Injection (DI) Container management, and the HTTP layer.
CPalius was born to bridge these two worlds—to free developers from reinventing the wheel while giving them a world-class, optimized, secure, and extensible foundation. This document is a detailed account of CPalius's technical journey: from the cleanest skeleton install, through a "never-crashing" operating-system architecture, to the path from a CMS toward an Enterprise Application Framework.
Section 1: "Pristine Root" and Folder Architecture
In a standard Symfony project, as third-party packages and recipes are installed, the project root turns into a dump of files and folders. The developer's own code mixes with the framework's system files. From day one, CPalius declared war on that chaos and adopted the "Pristine Root" policy.
We isolated the entire architecture so that the root retains only four fundamental folders that make clear what lives where, plus the .env file and composer.json.
Folder Hierarchy
CPalius/ (Root)
|
|-- cp-core/ # === KERNEL & SYSTEM SPACE ===
| |-- bin/ # Console command tool (bin/console)
| |-- config/ # Core configuration (bundles, packages, routes)
| |-- migrations/ # Core database migration files (tracked in Git)
| |-- src/ # Core PHP classes (App\ namespace)
| `-- var/ # Cache, logs, and SQLite files (writable, gitignored)
|
|-- cp-includes/ # === DEPENDENCIES SPACE ===
| `-- vendor/ # Composer packages (Symfony and libraries)
|
|-- cp-content/ # === USER & DEVELOPER SPACE ===
| |-- config/ # Config Sync area (infrastructure shipped as YAML)
| |-- modules/ # Independent modules/plugins (Modules\ namespace)
| |-- themes/ # User interface themes
| `-- translations/ # Global UI translations (tr, en)
|
|-- public/ # === WEB ROOT === (the only publicly exposed folder)
| |-- index.php # Front Controller (single entry point)
| `-- assets/ # Compiled / symlink'ed frontend assets
|
|-- .env # Environment configuration
`-- composer.json # CPalius custom path mappings
Bending Symfony Flex's Internals
To make this layout work, we pushed Symfony Flex and Composer's configuration capabilities to their limits. We configured composer.json so Flex would follow our folder structure: the vendor-dir, bin-dir, and the extra block parameters (config-dir, src-dir, var-dir, public-dir, and the runtime project_dir / dotenv_path).
Technical Note: Changing only config.vendor-dir is not enough. Commands inside Symfony Flex (cache:clear, assets:install) resolve paths from parameters under the extra block—not from Composer's standard config. Without those parameters, the system crashed at compile time. This guarantees that all Flex recipes install directly under cp-core.
Section 2: "Core Never Dies" Architecture
When building a CMF, the worst nightmare is poorly written or broken code from the user space (cp-content/modules) locking up the entire system. Because PHP has no true OS-level sandbox, CPalius developed a multi-tier Active Defense and Quarantine Line.
Defense Line 1: Solving the Chicken-and-Egg Problem (bundles.php)
When Symfony boots, it first reads config/bundles.php. At that stage the database connection, service container, and autoloader are not fully up yet. If we tried to read module active state directly from the database, the system would lock before it could even boot.
Solution: When a module is activated or deactivated, CPalius generates a static, PHP opcache-friendly array file at cp-core/config/active_modules.php. bundles.php only reads that file; the core bundles are always present and each declared module class is added only when class_exists() succeeds.
Defense Line 2: Runtime Isolation (Kernel::boot Override)
A class physically existing (class_exists) does not mean that module's code runs without errors. A TypeError or RuntimeException thrown inside the module's own boot() method can crash the entire system. To prevent that, Kernel::boot() is overridden so every module-level boot process is wrapped in a try/catch (\Throwable) shield; a failing module is written to the quarantine log while the core keeps running.
Defense Line 3: Compile-Time Lock Protection & Dry-Run
If a plugin's services.yaml has invalid YAML syntax or a wrong Dependency Injection (autowire) definition, the Symfony container cannot compile. The system then crashes, and you cannot even run cp:module:deactivate from the terminal.
Solution: An isolated Dry-Run check is integrated into module activation. The module class is written temporarily, a fully isolated subprocess is started with symfony/process, and it runs cache:clear --no-warmup → lint:yaml → lint:container. On a non-zero exit code the main process restores the file in a finally block, cancels activation, and permanently quarantines the module. The file is never left in a broken state.
Defense Line 4: Isolated Route Loader
In standard Symfony, a syntax error in a module's routes.yaml crashes the whole system at route-loading time. SafeModuleRouteLoader loads each module's routes inside an isolated try/catch block, so a faulty module is skipped while the rest of the system stays fully available.
Section 3: Hybrid Data Model and High-Performance Flat Index System
Both classic approaches to content management are problematic:
- EAV (Entity-Attribute-Value) Model (Drupal): Every new field opens a new database table. Fetching a page with 15 custom fields requires 15 JOIN queries; the database locks up.
- Postmeta / Serialized Model (WordPress): All custom fields live row-by-row in a single meta table. Filtering and sorting become a performance disaster.
CPalius Hybrid Data Model: Frequently queried, filtered, and indexed core fields (ID, Title, Slug, Type, Status, Locale, Dates) are stored as real database columns. All dynamic, flexible, project-specific content fields (body text, featured image, gallery fields, SEO data) live in a single SQL json column (data).
The Indexing Deadlock Across SQLite, MySQL, and Postgres
If you want to query dynamic JSON data at the database level, SQLite, MySQL, and Postgres require completely different SQL syntax. Building indexes on generated columns is also extremely fragile with Doctrine ORM schema tools; Doctrine tries to drop those indexes on every schema update.
Solution: NodeFieldIndex and the Dynamic Indexing Engine
CPalius solves this with a Flat Field Index table and a Doctrine Event Listener. A standalone index table carries type-appropriate columns (value_string, value_int, value_decimal, value_datetime) for queryable dynamic fields, each backed by a compound index on field_name.
According to the developer-defined QueryableFieldsRegistry configuration, when a Node is saved or updated, the NodeIndexListener automatically parses the JSON data column, clears old indexes, and writes them idempotently to the helper table on postPersist / postUpdate.
Regardless of which database engine is used, JSON-backed data can then be queried at lightning speed through indexes with a single standard SQL/DQL query via NodeRepository::findByIndexedField() (type, locale, fieldName, value, valueColumn, operator).
Section 4: Multilingual Structure and Composite Uniqueness Constraints
When multilingual (i18n) support is bolted on later, it collapses the entire data model. CPalius baked multilanguage into the core from day one.
Composite Unique Constraints (Uniqueness Guarantee)
In a multilingual setup, classic unique: true constraints lock the system. For example, Turkish /tr/hakkimizda and English /en/hakkimizda must be allowed to exist at the same time. A single global unique slug constraint blocks that.
CPalius Solution: We use composite uniqueness constraints in the database schema:
- Routing uniqueness: The same slug must be unique only within the same locale:
UNIQUE(slug, locale). - Translation group uniqueness: Within the same translation group, only one piece of content may exist per locale:
UNIQUE(translation_group_id, locale).
The same guarantee is carried to four more entities (categories, tags, menu_items, forum_sections) via UNIQUE(translation_group_id, locale), so the database itself enforces "one record per locale per translation group" even under concurrent requests.
Section 5: From "CMS" to "Application Framework"
CPalius is not merely a content management system (CMS); it is a flexible application platform that can power Car Dealership, Travel Agency, Staff Management, or CRM/ERP systems. To enable that shift, we defined two classes of entities:
| Aspect | Content Entities (Node) | Business Records (Resource) |
|---|---|---|
| Conceptual mapping | Page, Post, Listing showcase, Blog | Vehicle, Invoice, Reservation, Staff |
| Traits | Has slug, multilanguage, publish status, SEO. | No slug, no locale, no publish; has a state machine (workflow). |
| Common ground | Same Capability model, same Twig components, same CLI management, same Config Sync. | |
The #[CpResource] Revolution
To bring coding business processes down to seconds, we designed a custom PHP Attribute. With a single annotation we wire a raw Doctrine class in the database to the full power of the platform:
#[CpResource(
name: 'vehicle',
module: 'oto-galeri',
capabilities: ['create', 'edit', 'delete', 'view'],
auditable: true,
multiTenant: true,
workflow: 'vehicle_lifecycle'
)]
#[ORM\Entity]
class Vehicle
{
private ?int $id = null;
private ?string $plate = null;
private ?string $brand = null;
private ?int $price = 0; // Stored in cents (never Float!)
}
Thanks to this single attribute:
- Dynamic capabilities are registered automatically into the role/capability matrix (
vehicle.create,vehicle.edit, etc.). - Automatic CRUD forms and list screens are derived dynamically from this definition.
- For SaaS projects, multi-tenant isolation (
multiTenant: true) is applied automatically in the background. - Every change on the entity is recorded instantly in the version history (audit log).
Section 6: Security and Performance Constitution
Security and performance are not polish added at the end of a project; they are the system's foundational building blocks.
1. Capability-Based Access Control (CBAC)
Standard Symfony ROLE_ADMIN or ROLE_USER approaches are clumsy and hard to extend later. In CPalius, role names are never checked in code; dynamically generated capabilities are always checked instead via denyAccessUnlessGranted('system.module.manage').
- Roles = Config (YAML): Role definitions and their capability matrices live as YAML under
cp-content/config/sync/and travel with Git. - Users = Content (DB): Real user records stay in the database and are never exported.
2. SaaS Data Leak Protection (Automatic Tenant SQLFilter)
In multi-tenant (SaaS) systems, the biggest security hole is a developer forgetting to append WHERE tenant_id = ? to a query. In CPalius that risk is eliminated at the platform level: a Doctrine SQLFilter injects the tenant constraint into every query for entities marked multi-tenant.
3. N+1 Query Guard (Dev-Mode N+1 Guard)
To stop N+1 query mistakes—the most common cause of database bloat and slowness—a Doctrine DBAL middleware fires only in the dev environment. If the number of queries to the same table in a single HTTP request exceeds a set limit, it throws MaxQueriesExceededException immediately. Developers cannot ship code to production until they fix that error locally.
Section 7: Future Roadmap and Technical Consultation (RFC)
The core security, performance, and architectural backbone of CPalius is complete. Since this whitepaper's first draft, several roadmap items have shipped: the AACP Safe Mode / Recovery Console, the isolated Hook system, the unified Cron engine, the REST API Gateway, the fourth defense line (SafeModuleRouteLoader), the on-demand image pipeline (cp_thumb), and the #[CpResource] audit log.
In upcoming development sprints we will build the following systems:
- Workflow & State Machine: A mechanism that manages transition processes for business records such as invoices, vehicles, and reservations (
draft → preparation → sold) via YAML definitions, and automatically ties every transition to the audit log and notification queue. - Pimcore-style Independent Asset System: A modern media library that frees media from being a Node subtype and offers on-the-fly image derivation over URLs via Flysystem (S3, MinIO).
- Messenger Async Queue: Wiring a real transport (Doctrine/Redis) so email and notification work move onto an asynchronous queue.
Questions and Feedback (Community Consultation)
To refine this architecture further, we welcome feedback from you valued developers on these topics:
- Flat Field Index Model: How do you think this model—designed for SQLite, MySQL, and Postgres compatibility—will perform at massive data volumes? Should the index table be partitioned?
- Config Sync Approach: What do you think of our idea to emulate Drupal's config sync system using Symfony's TreeBuilder?
- Zero Node.js Stance: Will our decision to use AssetMapper + Standalone Tailwind in the developer and admin panels constrain us when writing highly complex frontend components later?
We eagerly await your ideas, critiques, and architectural suggestions. With the strength of the community, CPalius will become the most solid application framework.