Разработка PHP программ: методы и практические инструкции
Разработка на PHP: создание приложений
Как создать современное веб-приложение на PHP с использованием лучших практик?
Основное эффективное решение - использование фреймворка Laravel. Он предоставляет маршрутизацию, ORM Eloquent, авторизацию, очереди и встроенные инструменты тестирования. Это ускоряет разработку и повышает безопасность.
Пример: создадим страницу со списком пользователей.
// routes/web.php
Route::get('/users', [UserController::class, 'index']);
// app/Http/Controllers/UserController.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
public function index(): View
{
$users = User::all();
return view('users.index', ['users' => $users]);
}
}
// resources/views/users/index.blade.php
<ul>
@foreach($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
Пояснения: Маршрут связывает URL /users с методом index контроллера UserController. Контроллер получает всех пользователей через модель User (ORM Eloquent) и передает их в шаблон Blade. Шаблон выводит список имен.
- Ошибка Class not found: проверьте namespace и autoload composer.
- Ошибка подключения к БД: настройте .env файл с корректными данными.
- Маршрут не работает: выполните php artisan route:clear и убедитесь, что mod_rewrite включен (для Apache).
Цель использования: Проекты средней и высокой сложности, командная разработка, быстрый прототипирование. Laravel подходит для CRM, интернет-магазинов, SaaS.
Как создать простое приложение без фреймворка, используя процедурный код?
Вариант для изучения основ PHP, небольших скриптов или легаси-проектов. Пример: форма регистрации пользователя.
<?php
// index.php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
if ($name === '') {
$error = 'Имя обязательно';
} else {
$stmt = $pdo->prepare('INSERT INTO users (name) VALUES (:name)');
$stmt->execute(['name' => $name]);
header('Location: index.php');
exit;
}
}
$users = $pdo->query('SELECT * FROM users')->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html><body>
<form method="post">
<input type="text" name="name">
<button>Добавить</button>
<?= $error ?>
</form>
<ul>
<?php foreach ($users as $user): ?>
<li><?= htmlspecialchars($user['name']) ?></li>
<?php endforeach; ?>
</ul>
</body></html>
Пояснение: Весь код находится в одном файле. Используется PDO для безопасного выполнения запросов. После добавления пользователя происходит перенаправление (PRG-паттерн). Вывод экранируется через htmlspecialchars.
- Уязвимость к XSS, если не экранировать вывод.
- Смешение логики и представления - усложняет поддержку.
- Сложно добавлять новые страницы (много дублирования).
Решение: Вынести логику в отдельные файлы (include), использовать шаблонизатор, внедрить автозагрузку классов.
Цель использования: Обучение, прототипирование, одноразовые скрипты, поддержка старого кода.
Как быстро создать REST API на микрофреймворке Slim?
Для легковесных API и микросервисов. Пример: эндпоинт получения списка пользователей.
<?php
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/users', function (Request $request, Response $response) {
$users = [
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob'],
];
$payload = json_encode($users);
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
});
$app->run();
Пояснение: Slim 4 использует PSR-7 для запросов и ответов. Маршрут задаётся методом HTTP. JSON формируется вручную. Фреймворк сам обрабатывает CORS, middleware.
- Отсутствие встроенной ORM (нужно подключать стороннюю).
- Необходимость самостоятельно обрабатывать валидацию и авторизацию.
- Сложность с сериализацией при большом количестве данных.
Решение: Использовать компоненты Symfony для Doctrine, middleware для JWT, библиотеку symfony/serializer.
Цель использования: API-серверы, backend для SPA, мобильные приложения, когда не нужен полный фреймворк.
Как написать консольный скрипт на PHP для обработки файлов?
CLI-утилиты на PHP полезны для автоматизации, миграций, обработки логов. Пример: скрипт, подсчитывающий строки в CSV-файле.
#!/usr/bin/env php
<?php
if ($argc < 2) {
echo "Usage: php countlines.php <filename>\n";
exit(1);
}
$filename = $argv[1];
if (!file_exists($filename)) {
echo "File not found.\n";
exit(1);
}
$handle = fopen($filename, 'r');
$count = 0;
while (fgets($handle) !== false) {
$count++;
}
fclose($handle);
echo "Lines: $count\n";
Пояснение: Используется $argv для доступа к аргументам командной строки. fgets построчно читает файл. Результат выводится в stdout.
- Большие файлы могут потреблять много памяти, если использовать file() вместо fgets.
- Ошибки доступа к файлу (закрыть handle, проверить права).
- Отсутствие обработки бинарных данных.
Решение: Использовать генераторы для потоковой обработки, проверять is_readable, применять библиотеку symfony/console для расширенных CLI-приложений.
Цель использования: Крон-задачи, DevOps скрипты, импорт/экспорт данных.
Как асинхронно обрабатывать задачи с помощью очередей (например, RabbitMQ)?
Для фоновых операций: отправка писем, генерация отчетов, обработка изображений. Пример: продюсер и потребитель на php-amqplib.
// producer.php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('task_queue', false, true, false, false);
$data = json_encode(['email' => 'user@example.com', 'subject' => 'Hello']);
$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);
$channel->basic_publish($msg, '', 'task_queue');
$channel->close();
$connection->close();
// worker.php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('task_queue', false, true, false, false);
$callback = function ($msg) {
$data = json_decode($msg->body, true);
// эмуляция отправки письма
echo "Sending email to {$data['email']}: {$data['subject']}\n";
$msg->ack();
};
$channel->basic_qos(null, 1, null);
$channel->basic_consume('task_queue', '', false, false, false, false, $callback);
while ($channel->is_consuming()) {
$channel->wait();
}
Пояснение: Продюсер отправляет JSON-сообщение в очередь task_queue. Воркер ожидает и подтверждает обработку. persistent mode защищает от потери данных при падении сервера.
- Ошибки подключения к RabbitMQ (проверьте порт, логин/пароль).
- Необработанные сообщения (использовать ack/nack).
- Dead-letter очереди для отбракованных сообщений.
- Сложность управления несколькими воркерами.
Решение: Использовать супервизор (supervisord) для демонизации воркеров, настроить мониторинг.
Цель использования: Высоконагруженные приложения, где требуется асинхронность и отказоустойчивость.
Расширенные примеры PHP кода
Пример 1: REST API с JWT авторизацией на Slim 4 и Firebase JWT
<?php
use Slim\Factory\AppFactory;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->addErrorMiddleware(true, true, true);
$secretKey = 'your-256-bit-secret';
// Регистрация
$app->post('/auth/register', function (Request $request, Response $response) {
$data = json_decode($request->getBody(), true);
// валидация и сохранение в БД
$response->getBody()->write(json_encode(['message' => 'User created']));
return $response->withHeader('Content-Type', 'application/json');
});
// Логин, возвращает JWT
$app->post('/auth/login', function (Request $request, Response $response) use ($secretKey) {
$data = json_decode($request->getBody(), true);
// проверка пользователя
$payload = [
'iss' => 'myapp',
'iat' => time(),
'exp' => time() + 3600,
'sub' => $data['email']
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
$response->getBody()->write(json_encode(['token' => $jwt]));
return $response->withHeader('Content-Type', 'application/json');
});
// Защищенный эндпоинт
$app->get('/api/profile', function (Request $request, Response $response) {
$authHeader = $request->getHeader('Authorization');
$parts = explode(' ', $authHeader[0] ?? '');
$token = $parts[1] ?? '';
// декодирование, проверка
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
$response->getBody()->write(json_encode(['user' => $decoded->sub]));
return $response->withHeader('Content-Type', 'application/json');
});
$app->run();
// Запрос: POST /auth/login { "email": "user@test.com", "password": "pass" }
// Ответ: { "token": "eyJ..." }
// Запрос: GET /api/profile с заголовком Authorization: Bearer eyJ...
// Ответ: { "user": "user@test.com" }
Пример 2: Консольный скрипт с аргументами и обработкой CSV с помощью генератора
#!/usr/bin/env php
<?php
/**
* Скрипт для фильтрации CSV-файла по значению столбца.
* Использование: php filtercsv.php --file input.csv --column 2 --value "active"
*/
$longopts = [
'file:',
'column:',
'value:',
];
$options = getopt('', $longopts);
function readCsvLines(string $filename): Generator
{
$handle = fopen($filename, 'r');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filename");
}
while (($line = fgets($handle)) !== false) {
yield str_getcsv($line);
}
fclose($handle);
}
$filename = $options['file'] ?? '';
$column = (int)($options['column'] ?? 0);
$value = $options['value'] ?? '';
if (!$filename) {
echo "Не указан файл.\n";
exit(1);
}
$generator = readCsvLines($filename);
foreach ($generator as $row) {
if (isset($row[$column]) && $row[$column] === $value) {
echo implode(',', $row) . "\n";
}
}
// Пример CSV: // name,status,date // Alice,active,2023-01-01 // Bob,inactive,2023-01-02 // Результат при --column 1 --value active (столбец 1 - status): // Alice,active,2023-01-01
Пример 3: Использование Doctrine ORM для работы с базой данных в CLI-скрипте
<?php
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
require_once __DIR__ . '/../vendor/autoload.php';
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: [__DIR__ . '/src/Entity'],
isDevMode: true
);
$conn = [
'driver' => 'pdo_mysql',
'host' => 'localhost',
'dbname' => 'test',
'user' => 'root',
'password' => '',
];
$entityManager = EntityManager::create($conn, $config);
// Пример сущности User (в src/Entity/User.php)
// #[\Doctrine\ORM\Mapping\Entity]
// #[\Doctrine\ORM\Mapping\Table(name:'users')]
// class User { ...
$userRepository = $entityManager->getRepository('App\Entity\User');
$users = $userRepository->findBy(['status' => 'active']);
foreach ($users as $user) {
echo $user->getName() . "\n";
}
// Выведет имена активных пользователей из БД.
Пример 4: WebSocket сервер на Ratchet (обмен сообщениями в реальном времени)
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require __DIR__ . '/vendor/autoload.php';
class Chat implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
foreach ($this->clients as $client) {
if ($from !== $client) {
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "Error: {$e->getMessage()}\n";
$conn->close();
}
}
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8080
);
echo "WebSocket server running on ws://localhost:8080\n";
$server->run();
// Подключение: new WebSocket('ws://localhost:8080')
// Отправка сообщения от одного клиента видна всем остальным.