Введение в создание CMS без фреймворка

В мире веб-разработки PHP остаётся мощным и универсальным языком для создания динамических веб-сайтов и приложений. В то время как фреймворки вроде Laravel и Symfony могут ускорить процесс разработки, есть определённое удовлетворение в создании чего-то с нуля без использования готовых фреймворков. В этой статье мы подробно рассмотрим процесс создания простой системы управления контентом (CMS) с использованием чистого PHP, выделим ключевые компоненты и предоставим пошаговые инструкции вместе с примерами кода.

Зачем создавать без фреймворка?

Прежде чем углубляться, стоит задать вопрос: зачем вам может понадобиться создавать CMS без фреймворка? Вот несколько причин:

  • Обучение: Создание с нуля помогает понять основные механизмы PHP и веб-разработку.
  • Кастомизация: Вы полностью контролируете каждый аспект своего приложения.
  • Лёгкость: Нет ненужных накладных расходов от полноценного фреймворка.
  • Вызов: Это интересное и полезное испытание для разработчиков, желающих проверить свои навыки.

Настройка проекта

Чтобы начать, создайте новый проект со следующей структурой:

project/
├── public/
│   ├── index.php
│   └── .htaccess
├── src/
│   ├── App/
│   │   ├── Lib/
│   │   │   ├── App.php
│   │   │   └── Router.php
│   │   └── Models/
│   │       └── Post.php
│   └── Config/
│       └── config.php
├── composer.json
└── composer.lock

Composer и автозагрузка

Несмотря на то, что мы не используем фреймворк, Composer по-прежнему невероятно полезен для управления зависимостями и настройки автозагрузки.

Создайте файл composer.json в корне вашего проекта:

{
    "require": {
        "monolog/monolog": "1.25.1"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/App/"
        }
    }
}

Запустите composer install, чтобы настроить автозагрузчик и установить зависимости.

Фронт контроллер

Фронт-контроллер является центральным входом в ваше приложение. Он обрабатывает каждый входящий запрос и направляет его к соответствующей части вашего приложения.

Создайте public/index.php со следующим кодом:

<?php
declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use App\Lib\App;

App::run();

Маршрутизация

Маршрутизация имеет решающее значение для направления запросов к нужным частям вашего приложения. Вот простой класс маршрутизатора для начала работы.

Создайте 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, который будет запускать наше приложение и использовать маршрутизатор.

Создайте 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();

        // Пример маршрутов
        $router->addRoute('GET', '/', function () {
            echo 'Добро пожаловать в нашу CMS!';
        });

        $router->addRoute('GET', '/posts', function () {
            // Логика для списка сообщений
            echo 'Список сообщений';
        });

        $router->dispatch();
    }
}

База данных и модели

Для простоты мы будем использовать файл JSON в качестве базы данных. Это не подходит для продакшена, но хорошо работает для небольшого примера.

Создайте 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));
    }
}

Обновите класс App для включения маршрутов для сообщений:

// В 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(), // Простой генерация ID
        $data['title'],
        $data['body']
    );
    Post::savePost($post);
    echo 'Сообщение сохранено успешно';
});

Тестирование приложения

Чтобы протестировать приложение, запустите встроенный веб-сервер PHP:

php -S localhost:8080 -t public/

Перейдите по адресу http://localhost:8080/ в браузере, чтобы увидеть приветственное сообщение. Вы также можете использовать такие инструменты, как Postman или curl, чтобы протестировать POST-конечную точку.

curl -X POST http://localhost:8080/posts -H 'Content-Type: application/json' -d '{"title":"Мой пост","body":"Это мой пост"}'