Новейшая страница на PHP: от простого скрипта до компонентного подхода

Раздел: Веб-программирование на PHP -> Создание страниц

Ключевые составляющие современной PHP страницы

Рассмотрим создание страницы с использованием PHP 8.3, автозагрузку классов через Composer, работу с маршрутизацией и шаблонами.

Основное решение: Минимальная структура с автозагрузкой и PSR-7 интерфейсом

Наиболее эффективный способ - организация проекта с Composer, PSR-4, и использование готового HTTP-сообщения (например, symfony/http-foundation или nyholm/psr7).


composer init
composer require nyholm/psr7
composer require http-interop/http-factory-guzzle

Создадим точку входа index.php:


<?php
require 'vendor/autoload.php';

use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;

$psr17Factory = new Psr17Factory();
$request = (new ServerRequestCreator(
    $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory
))->fromGlobals();

// Маршрутизация
$path = $request->getUri()->getPath();
if ($path === '/' || $path === '') {
    $responseBody = '<h1>Добро пожаловать на современную PHP страницу</h1>';
} else {
    $responseBody = '<h1>Страница не найдена</h1>';
}

$response = $psr17Factory->createResponse(200)
    ->withHeader('Content-Type', 'text/html; charset=utf-8');
$response->getBody()->write($responseBody);

// Отправка ответа
http_response_code($response->getStatusCode());
foreach ($response->getHeaders() as $name => $values) {
    foreach ($values as $value) {
        header(sprintf('%s: %s', $name, $value), false);
    }
}
echo $response->getBody();

Проблема: Забыть вызывать header() до вывода контента. Решение: использовать буферизацию вывода (ob_start) или отложенный вывод, как в примере.

Типичная ошибка: Неверные пути в require автозагрузчика. Решение: проверять корректность пути к vendor/autoload.php.

Вариант 1: Как создать простейшую страницу без фреймворков, используя только встроенные функции?

Подходит для изучения основ. Создаем файл index.php с простым выводом HTML.


<?php
$title = 'Моя первая PHP страница';
$content = '<p>Привет, мир!</p>';
?>
<!DOCTYPE html>
<html>
<head>
    <title><?= $title ?></title>
</head>
<body>
    <?= $content ?>
</body>
</html>

Проблема: XSS уязвимость при выводе пользовательских данных без экранирования. Решение: использовать htmlspecialchars() для каждого вывода переменных, которые могут содержать небезопасный HTML.

Типичная ошибка: Смешивание PHP и HTML без разделения логики. Решение: вынести бизнес-логику в отдельный файл и использовать шаблоны.

Вариант 2: Как организовать код с использованием MVC-структуры?

Разделение на Model, View, Controller для лучшей поддерживаемости. Создаем папки app/controllers, app/models, app/views. Точка входа index.php включает автозагрузчик и маршрутизатор.


// index.php
require 'vendor/autoload.php';
require 'routes.php';

// маршрутизация
$controller = new \App\Controllers\HomeController();
$response = $controller->index();
echo $response;

Пример контроллера:


namespace App\Controllers;

class HomeController
{
    public function index(): string
    {
        $data = [
            'title' => 'Главная страница',
            'message' => 'Добро пожаловать!'
        ];
        return view('home', $data);
    }
}

Функция-шаблонизатор view() загружает файл из views и передает переменные с extract().


function view(string $name, array $data = []): string
{
    extract($data);
    ob_start();
    include __DIR__ . '/views/' . $name . '.php';
    return ob_get_clean();
}

Проблема: Сложность поддержки маршрутизации без готового компонента. Решение: использовать Composer-пакет для маршрутизации (например, nikic/fast-route).

Типичная ошибка: Не использовать пространства имен (namespaces) и автозагрузку. Решение: прописать PSR-4 в composer.json.

Вариант 3: Как использовать атрибуты PHP 8 для маршрутизации?

Атрибуты позволяют декларативно указывать маршруты прямо в контроллерах. Установим пакет "php-di/php-di" или "spiral/attributes", но можно и свой простой парсер.


#[Route('/user/{id}', methods: ['GET'])]
public function show(int $id): string
{
    return "Пользователь ID: $id";
}

Реализация простого диспетчера, который сканирует атрибуты классов:


// Пример кода диспетчера не приводим из-за сложности, но идея в том,
// что атрибуты читаются через ReflectionMethod::getAttributes().

Проблема: Производительность при парсинге атрибутов на каждый запрос. Решение: кэшировать маршруты в файл.

Типичная ошибка: Неправильное пространство имен для атрибутов. Решение: указывать полное имя класса атрибута с использованием use.

Расширенные примеры кода для современной PHP страницы

Пример 1: Работа с базой данных через PDO с типизированными свойствами.

Пример

// src/User.php
class User {
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email
    ) {}
}

class UserRepository {
    private PDO $pdo;
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    public function find(int $id): ?User {
        $stmt = $this->pdo->prepare('SELECT id, name, email FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row === false) return null;
        return new User(
            id: (int)$row['id'],
            name: $row['name'],
            email: $row['email']
        );
    }
}
// Вызов: $user = $repository->find(42);
// Результат: объект User с id=42, name='John', email='john@example.com'

Пример 2: Обработка статусов с enum и match.

Пример

enum OrderStatus: string {
    case Pending = 'pending';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Cancelled = 'cancelled';

    public function label(): string {
        return match($this) {
            self::Pending => 'Ожидает оплаты',
            self::Paid => 'Оплачен',
            self::Shipped => 'Отправлен',
            self::Cancelled => 'Отменён',
        };
    }
}

function processOrderStatus(string $status): string {
    try {
        $statusEnum = OrderStatus::from($status);
    } catch (\ValueError $e) {
        return 'Неизвестный статус';
    }
    return match ($statusEnum) {
        OrderStatus::Pending => "Статус: {$statusEnum->label()} - необходимо оплатить.",
        OrderStatus::Paid => "Статус: {$statusEnum->label()} - заказ готовится к отправке.",
        OrderStatus::Shipped => "Статус: {$statusEnum->label()} - отслеживание: ...",
        OrderStatus::Cancelled => "Статус: {$statusEnum->label()} - заказ отменён.",
    };
}
echo processOrderStatus('paid');
// Выведет: "Статус: Оплачен - заказ готовится к отправке."

Пример 3: Реализация middleware-цепочки для авторизации.

Пример

interface Middleware {
    public function handle(callable $next);
}

class AuthMiddleware implements Middleware {
    public function handle(callable $next) {
        if (!isset($_SESSION['user'])) {
            header('Location: /login');
            exit;
        }
        $next();
    }
}

class LoggingMiddleware implements Middleware {
    public function handle(callable $next) {
        error_log(sprintf('Request to %s', $_SERVER['REQUEST_URI']));
        $next();
    }
}

$middlewares = [
    new LoggingMiddleware(),
    new AuthMiddleware()
];

$core = function() {
    echo "Доступ разрешён.";
};

// Запуск цепочки
$pipeline = array_reduce(
    array_reverse($middlewares),
    function($next, $middleware) {
        return function() use ($middleware, $next) {
            $middleware->handle($next);
        };
    },
    $core
);
$pipeline();
// При выполнении на реальном сервере: если пользователь авторизован, выведет "Доступ разрешён."
// Если нет, произойдет редирект на /login.

Пример 4: Работа с curl multi для параллельных запросов (асинхронность).

Пример

$urls = [
    'https://api.example.com/endpoint1',
    'https://api.example.com/endpoint2'
];
$multiCurl = curl_multi_init();
$curlHandles = [];
foreach ($urls as $id => $url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($multiCurl, $ch);
    $curlHandles[$id] = $ch;
}
$running = null;
do {
    curl_multi_exec($multiCurl, $running);
    curl_multi_select($multiCurl);
} while ($running > 0);
foreach ($curlHandles as $id => $ch) {
    $response = curl_multi_getcontent($ch);
    echo "Ответ от endpoint{$id}: $response\n";
    curl_multi_remove_handle($multiCurl, $ch);
    curl_close($ch);
}
curl_multi_close($multiCurl);
// Результат: выведет содержимое обоих URL.

Новейшая страница PHP - comments

En
Newest php page (php)