Создание собственной панели управления сайтом: подходы и примеры на PHP
Реализация панели управления сайтом на PHP
Для создания системы управления сайтом (CMS) на PHP чаще всего применяют архитектуру MVC, маршрутизацию, работу с базой данных через PDO и шаблонизатор. Ниже представлено основное решение, а затем альтернативные подходы.
Основной подход: самописная MVC-админка
Как создать собственную панель управления с нуля, чтобы гибко контролировать логику?
Построим простой роутер, контроллер и модель для работы с записями блога. База данных MySQL, таблица posts (id, title, content, created_at).
// index.php - точка входа
require_once 'Router.php';
$router = new Router();
$router->add('GET', '/admin/posts', 'PostController@index');
$router->add('GET', '/admin/posts/create', 'PostController@create');
$router->add('POST', '/admin/posts/store', 'PostController@store');
$router->dispatch($_SERVER['REQUEST_METHOD'], parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
разработка web приложений php (разработка веб-приложений на php)
// Router.php
class Router {
private $routes = [];
public function add($method, $path, $handler) {
$this->routes[] = compact('method', 'path', 'handler');
}
public function dispatch($method, $uri) {
foreach ($this->routes as $route) {
if ($route['method'] === $method && $route['path'] === $uri) {
list($controller, $action) = explode('@', $route['handler']);
$obj = new $controller();
return $obj->$action();
}
}
http_response_code(404);
echo '404 Not Found';
}
}
управление сайтом php (управление сайтом на php)
// PostController.php
class PostController {
public function index() {
$posts = Post::all();
include 'views/admin/posts/index.php';
}
public function create() {
include 'views/admin/posts/create.php';
}
public function store() {
$data = $_POST;
Post::create($data);
header('Location: /admin/posts');
}
}
Php веб интерфейс (создание веб-интерфейса на php)
// Post.php (модель)
class Post {
public static function all() {
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $db->query('SELECT * FROM posts ORDER BY created_at DESC');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public static function create($data) {
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $db->prepare('INSERT INTO posts (title, content) VALUES (:title, :content)');
$stmt->execute(['title' => $data['title'], 'content' => $data['content']]);
}
}
Частые ошибки:
- Отсутствие проверки входящих данных (SQL-инъекции). Решение: использовать подготовленные запросы (как в примере).
- Неверный путь к файлам представлений. Рекомендуется использовать абсолютные пути или константу __DIR__.
- Проблемы с сессиями при авторизации. Всегда вызывайте session_start() в начале скрипта.
Вариант 1: Микрофреймворк Slim для быстрого API
Как быстро создать REST API для управления контентом без написания роутера вручную?
Slim предоставляет готовую маршрутизацию, middleware и контейнер зависимостей. Установка через composer: composer require slim/slim. Пример маршрута:
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require __DIR__ . '/vendor/autoload.php';
$app = AppFactory::create();
$app->get('/api/posts', function (Request $request, Response $response) {
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $db->query('SELECT * FROM posts');
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response->getBody()->write(json_encode($posts));
return $response->withHeader('Content-Type', 'application/json');
});
$app->run();
Возможные проблемы:
- Не настроен веб-сервер для перенаправления на index.php. В Apache нужен .htaccess с RewriteRule.
- Ошибки автозагрузки классов. Проверьте namespace и пути.
Вариант 2: Готовая админ-панель Orchid (на Laravel)
Как внедрить готовую панель управления с минимальным кодом?
Orchid для Laravel добавляет CRUD-интерфейс, систему ролей, медиа-менеджер. Установка:
composer require orchid/platform
php artisan orchid:install
php artisan serve
После установки создаются ресурсы (Screens) для управления сущностями. Пример:
// app/Orchid/Screens/PostScreen.php
use Orchid\Screen\Screen;
use Orchid\Screen\Actions\Link;
class PostScreen extends Screen {
public function query(): iterable {
return ['posts' => Post::paginate()];
}
public function layout(): iterable {
return [
Layout::table('posts', [
'title' => 'Заголовок',
'actions' => Link::make('Редактировать')->route('platform.post.edit'),
])
];
}
}
Ошибки при внедрении:
- Конфликт с существующим Laravel-приложением. Следуйте официальной документации по миграции.
- Проблемы с кэшем. Выполните php artisan optimize:clear.
Вариант 3: Управление через файлы (без базы данных)
Как организовать простой сайт-визитку без SQL?
Контент хранится в JSON-файлах. Пример файла pages/home.json:
{
"title": "Главная",
"content": "Приветствие
"
}
Чтение и запись:
function getPage($slug) {
$file = __DIR__ . "/pages/{$slug}.json";
if (!file_exists($file)) return null;
return json_decode(file_get_contents($file), true);
}
function updatePage($slug, $data) {
file_put_contents(__DIR__ . "/pages/{$slug}.json", json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
Типичные проблемы:
- Права доступа к файлам. Необходимо установить chmod 755 или 644.
- Конфликты одновременной записи. Для промышленного использования не подходит; требуется блокировка flock.
Расширенные примеры реализации администрирования
Полный CRUD с аутентификацией и постраничной навигацией
Пример для самописной системы с использованием PDO и простой сессионной авторизацией.
// config.php
session_start();
define('DB_HOST', 'localhost');
define('DB_NAME', 'cms');
define('DB_USER', 'root');
define('DB_PASS', '');
function db() {
static $pdo = null;
if ($pdo === null) {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $pdo;
}
// auth.php
class Auth {
public static function login($username, $password) {
$stmt = db()->prepare('SELECT * FROM users WHERE username = ?');
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
return true;
}
return false;
}
public static function check() {
return isset($_SESSION['user_id']);
}
public static function logout() {
session_destroy();
}
}
// index.php (роутер с middleware)
require_once 'config.php';
require_once 'auth.php';
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
// Простая маршрутизация
if ($path === '/admin/login' && $method === 'GET') {
include 'views/admin/login.php';
exit;
}
if ($path === '/admin/login' && $method === 'POST') {
if (Auth::login($_POST['username'], $_POST['password'])) {
header('Location: /admin/posts');
exit;
}
header('Location: /admin/login?error=1');
exit;
}
// Все остальные маршруты требуют авторизации
if (!Auth::check()) {
header('Location: /admin/login');
exit;
}
if ($path === '/admin/posts') {
// Пагинация
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 5;
$offset = ($page - 1) * $perPage;
$totalStmt = db()->query('SELECT COUNT(*) FROM posts');
$total = $totalStmt->fetchColumn();
$pages = ceil($total / $perPage);
$stmt = db()->prepare('SELECT * FROM posts ORDER BY created_at DESC LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
include 'views/admin/posts/index.php';
exit;
}
// views/admin/posts/index.php
<h2>Список записей</h2>
<table>
<tr><th>Заголовок</th><th>Дата</th><th>Действия</th></tr>
<?php foreach ($posts as $post): ?>
<tr>
<td><?= htmlspecialchars($post['title']) ?></td>
<td><?= $post['created_at'] ?></td>
<td>
<a href="/admin/posts/edit/<?= $post['id'] ?>">Редактировать</a>
<a href="/admin/posts/delete/<?= $post['id'] ?>" onclick="return confirm('Удалить?')">Удалить</a>
</td>
</tr>
<?php endforeach; ?>
</table>
<div class="pagination">
<?php for ($i = 1; $i <= $pages; $i++): ?>
<a href="?page=<?= $i ?>" class="<?= $i == $page ? 'active' : '' ?>"><?= $i ?></a>
<?php endfor; ?>
</div>
Результат работы:
- После входа отображается таблица с записями, по 5 на страницу.
- Ссылки пагинации переключают страницы.
- Заголовки экранированы от XSS.
(Сгенерированный HTML: таблица с двумя строками записи, страницы 1,2,3)
Работа с изображениями: загрузка и удаление
// upload.php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
$targetDir = __DIR__ . '/uploads/';
$filename = uniqid() . '_' . basename($_FILES['image']['name']);
$targetFile = $targetDir . $filename;
// Проверка типа файла
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($_FILES['image']['type'], $allowedTypes)) {
echo 'Разрешены только JPEG, PNG, GIF';
exit;
}
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
// Сохраняем путь в БД
$stmt = db()->prepare('INSERT INTO media (filename, path) VALUES (?, ?)');
$stmt->execute([$filename, 'uploads/' . $filename]);
echo 'Файл загружен: ' . $filename;
} else {
echo 'Ошибка загрузки';
}
}
Файл загружен: 67543abc_photo.jpg
Экспорт данных в CSV
// export.php
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="posts.csv"');
$output = fopen('php://output', 'w');
fwrite($output, "\xEF\xBB\xBF"); // BOM для UTF-8 в Excel
fputcsv($output, ['ID', 'Заголовок', 'Контент', 'Дата']);
$stmt = db()->query('SELECT id, title, content, created_at FROM posts');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
fputcsv($output, $row);
}
fclose($output);
Скачивается файл posts.csv с колонками: ID, Заголовок, Контент, Дата и строками данных.