Introduction to Building a CMS Without a Framework
In the world of web development, PHP remains a powerful and versatile language for creating dynamic websites and applications. While frameworks like Laravel and Symfony can streamline the development process, there’s a certain satisfaction in building something from the ground up without relying on pre-existing frameworks. In this article, we’ll delve into the process of creating a simple Content Management System (CMS) using pure PHP, highlighting the key components, and providing step-by-step instructions along with code examples.
Why Build Without a Framework?
Before we dive in, it’s worth asking why you might want to build a CMS without a framework. Here are a few reasons:
- Learning Experience: Building from scratch helps you understand the underlying mechanics of PHP and web development.
- Customization: You have complete control over every aspect of your application.
- Lightweight: No unnecessary overhead from a full-fledged framework.
- Challenge: It’s a fun and rewarding challenge for developers looking to test their skills.
Setting Up the Project
To start, create a new project directory with the following structure:
project/
├── public/
│ ├── index.php
│ └── .htaccess
├── src/
│ ├── App/
│ │ ├── Lib/
│ │ │ ├── App.php
│ │ │ └── Router.php
│ │ └── Models/
│ │ └── Post.php
│ └── Config/
│ └── config.php
├── composer.json
└── composer.lock
Composer and Autoloading
Even though we’re not using a framework, Composer is still incredibly useful for managing dependencies and setting up autoloading.
Create a composer.json
file in your project root:
{
"require": {
"monolog/monolog": "1.25.1"
},
"autoload": {
"psr-4": {
"App\\": "src/App/"
}
}
}
Run composer install
to set up the autoloader and install any dependencies.
The Front Controller
The front controller is the central entry point for your application. It handles every incoming request and directs it to the appropriate part of your application.
Create public/index.php
with the following code:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use App\Lib\App;
App::run();
Routing
Routing is crucial for directing requests to the right parts of your application. Here’s a simple router class to get you started.
Create src/App/Lib/Router.php
:
<?php
declare(strict_types=1);
namespace App\Lib;
class Router
{
private $routes = [];
public function addRoute(string $method, string $path, callable $callback)
{
$this->routes[] = [
'method' => $method,
'path' => $path,
'callback' => $callback,
];
}
public function dispatch()
{
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
foreach ($this->routes as $route) {
if ($route['method'] === $method && $route['path'] === $uri) {
return call_user_func($route['callback']);
}
}
http_response_code(404);
echo 'Page not found';
}
}
App Bootstrap
Now, let’s create the App
class that will bootstrap our application and use the router.
Create src/App/Lib/App.php
:
<?php
declare(strict_types=1);
namespace App\Lib;
use App\Lib\Router;
class App
{
public static function run()
{
$router = new Router();
// Example routes
$router->addRoute('GET', '/', function () {
echo 'Welcome to our CMS!';
});
$router->addRoute('GET', '/posts', function () {
// Logic to list posts
echo 'List of posts';
});
$router->dispatch();
}
}
Database and Models
For simplicity, we’ll use a JSON file as our database. This is not suitable for production but works well for a small example.
Create src/App/Models/Post.php
:
<?php
declare(strict_types=1);
namespace App\Models;
class Post
{
private $id;
private $title;
private $body;
public function __construct(int $id, string $title, string $body)
{
$this->id = $id;
$this->title = $title;
$this->body = $body;
}
public function getId(): int
{
return $this->id;
}
public function getTitle(): string
{
return $this->title;
}
public function getBody(): string
{
return $this->body;
}
public static function loadPosts(): array
{
$posts = json_decode(file_get_contents('posts.json'), true);
return array_map(function ($post) {
return new self($post['id'], $post['title'], $post['body']);
}, $posts);
}
public static function savePost(Post $post)
{
$posts = self::loadPosts();
$posts[] = [
'id' => $post->getId(),
'title' => $post->getTitle(),
'body' => $post->getBody(),
];
file_put_contents('posts.json', json_encode($posts));
}
}
Update the App
class to include routes for posts:
// In src/App/Lib/App.php
use App\Models\Post;
// ...
$router->addRoute('GET', '/posts', function () {
$posts = Post::loadPosts();
foreach ($posts as $post) {
echo "ID: {$post->getId()}, Title: {$post->getTitle()}, Body: {$post->getBody()}\n";
}
});
$router->addRoute('POST', '/posts', function () {
$data = json_decode(file_get_contents('php://input'), true);
$post = new Post(
time(), // Simple ID generation
$data['title'],
$data['body']
);
Post::savePost($post);
echo 'Post saved successfully';
});
Testing the Application
To test the application, start PHP’s built-in web server:
php -S localhost:8080 -t public/
Navigate to http://localhost:8080/
in your browser to see the welcome message. You can also use tools like Postman or curl
to test the POST endpoint.
curl -X POST http://localhost:8080/posts -H 'Content-Type: application/json' -d '{"title":"My Post","body":"This is my post"}'
Dependency Injection
As your application grows, dependency injection becomes crucial for managing dependencies between classes. Here’s a simple example using PHP-DI.
Install PHP-DI via Composer:
composer require php-di/php-di
Create a simple container class:
// In src/App/Lib/Container.php
use DI\Container;
class Container extends Container
{
public function __construct()
{
parent::__construct();
$this->set('router', function () {
return new Router();
});
$this->set('postModel', function () {
return new Post(0, '', '');
});
}
}
Update the App
class to use the container:
// In src/App/Lib/App.php
use App\Lib\Container;
class App
{
public static function run()
{
$container = new Container();
$router = $container->get('router');
// Example routes
$router->addRoute('GET', '/', function () {
echo 'Welcome to our CMS!';
});
// ...
$router->dispatch();
}
}
Conclusion
Building a CMS without a framework is a challenging but rewarding experience. It forces you to understand the underlying mechanics of PHP and web development, making you a better developer in the long run. Here’s a quick flowchart to summarize the process:
This article has provided a step-by-step guide to building a simple CMS using pure PHP. From setting up the project structure to implementing routing, models, and dependency injection, you now have the tools to create your own custom CMS without relying on a framework. Happy coding