A lightweight, robust, Object-Oriented PHP blogging engine and content management system. Built using a modern MVC (Model-View-Controller) architecture, Twig templating, Tailwind CSS for styling, a clean Singleton MySQLi wrapper, Doctrine Migrations for schema versioning, and a fully typed DTO-driven seeder system.
Designed for speed, ease of configuration, and flexibility, Nexus CMS serves as a production-ready starting template for custom PHP web applications or a showcase of modern OOP PHP design patterns.
- Dynamic Post Stream: Paginated feeds of blog posts with categories, taglines, and rich read-more details.
- Flexible Category Navigation: Automatically generated category menus filtering relevant posts.
- Search System: Search through post content and titles with sanitization.
- Contact Portal: Secure contact form that processes user inquiries directly to the administrative inbox.
- Custom Pages: Admin-defined static pages (e.g., About Us, Privacy Policy) rendered dynamically.
- Full Blog Post CRUD: Author, edit, delete, and view posts complete with image uploading.
- Category Management: Add, update, and manage taxonomy.
- Dynamic Slider Controller: Configure home-page sliders and promotional carousels.
- Custom Page Builder: Add new pages and update content without touching the codebase.
- Central Inbox: Read and reply directly to incoming inquiries sent via the frontend contact form.
- Site Settings Control: Real-time updates for site titles, descriptions, SEO metadata, slogans, and copyright tags.
- User & Session Management: Access controls, password resetting, and user profiles with multi-tier role authorization.
| Layer | Technology |
|---|---|
| Runtime | PHP 8.0+ β OOP MVC Architecture, PSR-4 autoloading |
| Frontend | Twig Templating Engine & Tailwind CSS v3 |
| Rich Text | Jodit Editor (Open Source, MIT) |
| Database | MySQL / MariaDB |
| App DB layer | Singleton mysqli wrapper (app/Core/Database.php) |
| Migrations | Doctrine Migrations 3.x + Doctrine DBAL 3.x |
| Seeder | PDO prepared statements, DTO value objects |
| Environment | vlucas/phpdotenv |
| CLI | Symfony Console (bundled with Doctrine Migrations) |
| Web Server | Apache/Laragon (.htaccess) or php -S |
| Security | OWASP Top 10 enforced β see Security Practices |
graph TD
A[Public / Admin Routes] --> B(app/bootstrap.php)
B --> C[Composer Autoloader]
B --> D[Dotenv safeLoad]
B --> E[Singleton Database & Twig Init]
B --> F[Controllers Layer]
F --> G[Models Layer]
G --> H[Post Β· User Β· Category Β· Page Β· Contact Β· Site]
F --> V[Twig Views resources/views/]
CLI1[bin/migrate] --> I[Doctrine Migrations]
I --> J[AbstractMigration subclasses]
J --> K[(MySQL β schema)]
CLI2[db-seed.php] --> L[DatabaseSeeder]
L --> M[SeederInterface]
M --> N[CategorySeeder / UserSeeder / ...]
N --> O[DTOs β readonly value objects]
O --> K
| Class | Path | Responsibility |
|---|---|---|
Database |
app/Core/Database.php |
Singleton mysqli wrapper β one connection per request |
Session |
app/Core/Session.php |
Auth checks, flash messages, session lifecycle |
Format |
app/Helpers/Format.php |
Content sanitization, text trimming |
dump() |
app/Helpers/Debug.php |
Development dump helper β renders variable, last PHP error, $_GET/$_POST/$_SERVER in a styled card (loaded only when APP_ENV=development) |
Models map domain logic and database operations, keeping script files clean:
| Model | Table | Key Methods |
|---|---|---|
Post |
posts |
getPaginated, getById, search, create, update, delete |
Category |
categories |
getAll, getById, create, update, delete |
User |
users |
getByUsername, create, update, delete |
Page |
pages |
getByName, create, update |
Contact |
contacts |
getAll, create, markRead |
Site |
settings |
getInfo, getAllSiteInfo, getSiteInfoById |
Entity Relationship Diagram (ERD)
erDiagram
users {
int id PK
string name
string username
string email
string password
text details
int role
}
categories {
int id PK
string name
}
posts {
int id PK
int cat FK "-> categories.id"
string title
text body
string image
string author
string tags
datetime date
int userid FK "-> users.id"
}
members {
int id PK
string name
string email
string username
string password
}
pages {
int id PK
string name
text body
}
contacts {
int id PK
string fname
string lname
string email
text msg
smallint status
datetime created
}
settings {
int id PK
string logo
string title
string slogan
}
sliders {
int id PK
string title
string image
datetime timestamp
}
socials {
int id PK
string fb
string tw
string ln
}
footers {
int id PK
string note
}
themes {
int id PK
string theme
}
users ||--o{ posts : creates
categories ||--o{ posts : contains
Migrations β Schema versioning via Doctrine Migrations 3.x:
| File | Class | Creates |
|---|---|---|
2026_07_04_000003_create_contacts_table.php |
CreateContactsTable |
contacts |
2026_07_04_000004_create_footers_table.php |
CreateFootersTable |
footers |
2026_07_04_000005_create_pages_table.php |
CreatePagesTable |
pages |
2026_07_04_000006_create_settings_table.php |
CreateSettingsTable |
settings |
2026_07_04_000007_create_sliders_table.php |
CreateSlidersTable |
sliders |
2026_07_04_000008_create_categories_table.php |
CreateCategoriesTable |
categories |
2026_07_04_000009_create_users_table.php |
CreateUsersTable |
users |
2026_07_04_000010_create_socials_table.php |
CreateSocialsTable |
socials |
2026_07_04_000011_create_posts_table.php |
CreatePostsTable |
posts |
2026_07_04_000012_create_themes_table.php |
CreateThemesTable |
themes |
2026_07_04_000013_create_members_table.php |
CreateMembersTable |
members |
Seeders β Fully typed, DTO-driven population system:
| Class | Path | Responsibility |
|---|---|---|
SeederInterface |
app/Database/SeederInterface.php |
Contract every seeder must implement |
DatabaseSeeder |
app/Database/DatabaseSeeder.php |
Orchestrator β truncates tables, calls all seeders in dependency order |
| DTOs | app/Database/DTOs/ |
Immutable readonly value objects β one per table |
| Seeders | app/Database/Seeders/ |
Concrete implementations using DTOs + PDO prepared statements |
Seeder execution order (respects FK constraints):
categories β users β members β posts β pages β sliders β settings β socials β footers β themes β contacts
βββ admin/ # Legacy admin entry points (wrappers) β βββ index.php # Routes to Admin\DashboardController β βββ ... # Other routed scripts βββ resources/ # Frontend and UI β βββ views/ # Twig Templates β βββ frontend/ # Public facing views β βββ dashboard/ # Admin dashboard views βββ app/ # OOP core & business logic β βββ Controllers/ # MVC Controllers (Admin & Frontend) β βββ Core/ # Singleton DB & session handling β β βββ Database.php β β βββ Session.php β βββ Database/ # Database layer β β βββ DTOs/ # Immutable value objects (one per table) β β β βββ CategoryDTO.php Β· ContactDTO.php Β· ... (11 total) β β βββ Migrations/ # Doctrine migration files (Laravel-style names) β β β βββ 2026_07_04_000003_create_contacts_table.php β β β βββ 2026_07_04_000006_create_settings_table.php β β β βββ ... (11 migrations total) β β βββ Seeders/ # Concrete seeder implementations β β β βββ CategorySeeder.php Β· UserSeeder.php Β· ... (11 total) β β βββ DatabaseSeeder.php # Master seeder orchestrator β β βββ SeederInterface.php # Seeder contract β βββ Helpers/ # Utility helpers β β βββ Debug.php # dump() β dev-only debug helper β β βββ Format.php # Content sanitization & formatting β βββ Models/ # Database-mapped PHP classes β β βββ Post.php Β· User.php Β· Category.php Β· ... (6 total) β βββ bootstrap.php # App entry bootstrap & dependency injection βββ bin/ # CLI entry points β βββ make-migration # Generate Laravel-style migration file β βββ make-model # Generate Model class (+ optional migration) β βββ migrate # Doctrine Migrations CLI runner βββ config/ # Static configuration loader βββ css/ # Frontend styles βββ js/ # Frontend scripts βββ vendor/ # Composer vendor packages βββ .agents/ # Agent rules & project conventions β βββ AGENTS.md # OWASP rules, coding standards βββ .env # Active environment (never commit) βββ .env.example # Environment configuration template βββ .gitignore # Files excluded from version control βββ composer.json # Composer config & script shortcuts βββ db-seed.php # CLI entry point β runs DatabaseSeeder βββ migrations.php # Doctrine Migrations config (pure PHP array)
---
## βοΈ Installation & Setup
### Prerequisites
* **PHP:** v8.0 or higher
* **Composer:** For managing dependencies
* **Database:** MySQL / MariaDB
### Steps
1. **Clone the Repository**
```bash
git clone https://github.com/mhannan-dev/php-blog.git
cd php-blog
-
Install Dependencies
composer install
-
Configure Environment Variables
cp .env.example .env
Open
.envand fill in your credentials:DB_HOST=localhost DB_USER=root DB_PASS=your_db_password DB_NAME=blg APP_ENV=development TITLE="Nexus CMS" META_DESC="A blog developed by Muhammad Hannan using PHP & MySQL." KEYWORDS="PHP, Laravel, Vue JS, WordPress, plugin"
-
Create the Database Schema (Migrations)
composer migrate
This runs all Doctrine Migrations in order, creating all 11 application tables.
-
Seed the Database
composer db:seed
Runs the full
DatabaseSeederpipeline β truncates all tables and populates them with sample data using typed DTOs and PDO prepared statements. -
Start the Local Development Server
composer start php -S localhost:8888
App runs at
http://localhost:8888.
All project operations are available as Composer shortcuts:
| Command | Description |
|---|---|
composer migrate |
Apply all pending migrations |
composer migrate:status |
Show applied / pending migration versions |
composer migrate:gen |
Generate a blank Doctrine migration class |
composer migrate:diff |
Generate migration from schema diff |
composer migrate:down |
Roll back all applied migrations |
| Command | Description |
|---|---|
composer make:migration create_posts_table |
Generate a Laravel-style migration file |
composer make:model Post |
Generate a Model class |
composer make:model Post -- -m |
Generate a Model and its migration |
Migration naming convention (identical to Laravel):
YYYY_MM_DD_HHMMSS_action_description.php β class ActionDescription
Examples:
composer make:migration create_orders_table # β CreateOrdersTable
composer make:migration add_published_at_to_posts # β AddPublishedAtToPosts
composer make:migration drop_themes_table # β DropThemesTableModel name β table name (auto-derived):
| Model name | Table |
|---|---|
Post |
posts |
Category |
categories |
ProductCategory |
product_categories |
| Command | Description |
|---|---|
composer db:seed |
Truncate all tables and re-seed with sample data |
| Command | Description |
|---|---|
composer start |
Start PHP built-in server on localhost:8888 |
This project follows the OWASP Top 10 guidelines as hard requirements. Key practices enforced throughout the codebase:
| # | Risk | Mitigation |
|---|---|---|
| A01 | Broken Access Control | Server-side role checks on every admin route; least-privilege model |
| A02 | Cryptographic Failures | password_hash() / password_verify() (bcrypt); secrets in .env only |
| A03 | Injection | PDO prepared statements with bound parameters; htmlspecialchars() on all output |
| A04 | Insecure Design | Defense-in-depth; rate limiting on login and contact form |
| A05 | Security Misconfiguration | display_errors=Off in production; .env/.git denied via .htaccess; HttpOnly+SameSite cookies |
| A06 | Vulnerable Components | MIT-licensed deps only; version-pinned in composer.json |
| A07 | Auth Failures | session_regenerate_id(true) on login; account lockout after repeated failures |
| A08 | Integrity Failures | CSP headers; no unsafe deserialization |
| A09 | Logging & Monitoring | Auth events logged with timestamp + IP; logs outside webroot |
| A10 | SSRF | URL allowlist; internal IP ranges blocked for any outbound HTTP |
Muhammad Hannan π§ Email: mdhannan.info@gmail.com π GitHub: @mhannan-dev
This project is licensed under the MIT License β see the LICENSE file for details.