A lightweight CMS built with PHP, MySQL, and PDO. This project demonstrates clean architecture with separated concerns, type safety, and modern PHP practices.
This repo is structured as a tutorial: the same CMS, rebuilt in progressively more organized stages. Each stage is a branch you can check out and run on its own. main always holds the latest stage's code.
| Stage | Branch | What it shows | Key skills |
|---|---|---|---|
| 0 | stage-0-flat-script |
Everything in one index.php - no classes, no models, no views. |
PDO basics, request handling |
| 1 | stage-1-views |
Same procedural script, but the markup moves into views/. Still no classes. |
Presentation vs. logic, output buffering |
| 2 | stage-2-basic-mvc |
Model classes in includes/; routing still inline in index.php. |
Classes, type hints, autoloading |
| 3 | stage-3-front-controller |
Front Controller pattern: a Router and dedicated Controller classes handle requests. |
Routing, controller pattern |
| 4 | stage-4-view-layer |
A View class replaces manual ob_start()/include in index.php for rendering templates and layouts. |
Template rendering, layouts |
Stage branches 3 and up are frozen checkpoints of main as of the commit where that stage was completed - main keeps evolving past the latest one, so check main's own commit history for anything added since.
git diff stage-0-flat-script stage-1-views # Views extracted
git diff stage-1-views stage-2-basic-mvc # Classes and models
git diff stage-2-basic-mvc stage-3-front-controller # Routing
git diff stage-3-front-controller stage-4-view-layer # Rendering- User Management: Registration, login/logout, role-based access (admin/user)
- Post Management: Create, read, update, delete posts with user ownership
- PDO Database: Secure database access with prepared statements
- Singleton Pattern: Efficient single database connection
- MVC-like Structure: Separated views from business logic
- Type Hints: Full PHP type declarations for better IDE support
php-newbie/
├── config.php # Configuration (DB, site settings, autoloader)
├── index.php # Entry point: dispatches the router, hands data to the View
├── setup.sql # MySQL database schema + sample data
├── README.md
├── docs/
│ └── connection.md # Database connection documentation
├── includes/
│ ├── Database.php # PDO connection with Singleton pattern
│ ├── User.php # User model with auth
│ ├── Post.php # Post model with CRUD
│ ├── Router.php # Maps GET/POST actions to controller handlers
│ ├── View.php # Renders templates and composes them with a layout
│ └── controllers/
│ ├── AuthController.php # Login/logout
│ └── HomeController.php # Home page data
└── views/
├── layout.php # Base template
├── home.php # Posts and users display
└── login.php # Login form
- PHP 8.0+
- MySQL 5.7+
- PDO MySQL extension enabled
- GitHub CLI (
gh) for deployment (optional)
git clone https://github.com/imagewize/php-newbie.git
cd php-newbieImport the database schema and sample data:
mysql -u root -p < setup.sqlOr use phpMyAdmin to import setup.sql file.
Edit config.php with your MySQL credentials:
define('DB_HOST', 'localhost');
define('DB_NAME', 'cms_db');
define('DB_USER', 'your_username');
define('DB_PASS', 'your_password');Point your web server to the project directory and visit:
http://localhost/php-newbie
Use the demo credentials:
- Email: admin@example.com
- Password: password123
| Constant | Description | Default |
|---|---|---|
DB_HOST |
MySQL host | localhost |
DB_NAME |
Database name | cms_db |
DB_USER |
Database user | root |
DB_PASS |
Database password | (empty) |
SITE_NAME |
Site title | My CMS |
SITE_URL |
Site URL | http://localhost/php-newbie |
| Column | Type | Description |
|---|---|---|
| id | INT (PK, AI) | User ID |
| name | VARCHAR(100) | Display name |
| VARCHAR(100) | Unique email | |
| password | VARCHAR(255) | Hashed password |
| role | ENUM('admin', 'user') | User role |
| created_at | TIMESTAMP | Record creation time |
| updated_at | TIMESTAMP | Last update time |
| Column | Type | Description |
|---|---|---|
| id | INT (PK, AI) | Post ID |
| title | VARCHAR(255) | Post title |
| content | TEXT | Post content |
| user_id | INT (FK) | Author ID |
| status | ENUM('draft', 'published') | Post status |
| created_at | TIMESTAMP | Record creation time |
| updated_at | TIMESTAMP | Last update time |
$user = new User();
$user->login('email@example.com', 'password');$user = new User();
$allUsers = $user->getAll();$post = new Post();
$posts = $post->getPublished();$post = new Post();
$post->create([
'title' => 'My Post',
'content' => 'Post content here',
'status' => 'published'
]);The Database class uses the Singleton pattern to ensure only one database connection exists per request:
$db = Database::getInstance();- Routing:
Router.phpmaps GET/POST actions to controller handlers - Controllers:
includes/controllers/*.phphandle requests and return data - Models:
User.php,Post.phphandle data and business logic - Views:
View.phprendersviews/*.phptemplates and composes them with a layout - Configuration:
config.phpcentralizes settings
All classes use PHP type hints for properties, parameters, and return values:
class Database {
private PDO $connection;
private static ?self $instance = null;
public static function getInstance(): self { ... }
public function query(string $sql, array $params = []): PDOStatement { ... }
}- Prepared Statements: All SQL queries use PDO prepared statements
- Password Hashing: Uses
password_hash()andpassword_verify() - HTML Escaping: Uses
htmlspecialchars()for output - CSRF Protection: Recommended to add for forms in production
- SQL Injection: Prevented by PDO prepared statements with
ATTR_EMULATE_PREPARES => false
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is open-source and available under the MIT License.
Built with PHP and MySQL. Maintained by imagewize.