Лучшие практики разработки
Руководство по написанию эффективного и надежного кода на Localzet Server.
Архитектурные паттерны
1. Разделение ответственности
Плохо:
$server->onMessage = function($conn, $req) {
    // Вся логика в одном месте
    $db = new PDO(...);
    $user = $db->query("SELECT * FROM users...");
    $result = processUser($user);
    $conn->send(json_encode($result));
};
Хорошо:
// app/Handlers/UserHandler.php
class UserHandler extends ServerAbstract {
    private UserService $userService;
    
    public function __construct(UserService $userService) {
        $this->userService = $userService;
    }
    
    public function onMessage(ConnectionInterface $connection, mixed $request): void {
        if ($request instanceof Request) {
            $this->handleHttp($connection, $request);
        }
    }
    
    private function handleHttp(TcpConnection $conn, Request $req): void {
        $result = $this->userService->processRequest($req);
        $conn->send($result);
    }
}
// Использование
$handler = new UserHandler(new UserService());
localzet_bind($server, $handler);
2. Dependency Injection
class AppHandler extends ServerAbstract {
    public function __construct(
        private Database $db,
        private Cache $cache,
        private Logger $logger
    ) {}
    
    public function onMessage(ConnectionInterface $conn, mixed $req): void {
        // Использование внедренных зависимостей
    }
}
// Создание с зависимостями
$handler = new AppHandler(
    new Database($config['database']),
    new Cache($config['cache']),
    new Logger($config['logger'])
);
localzet_bind($server, $handler);
3. Обработка ошибок
Централизованная обработка:
class ErrorHandler {
    public static function handle(Throwable $e, TcpConnection $conn): void {
        // Логирование
        Server::log("Error: " . $e->getMessage());
        Server::log($e->getTraceAsString());
        
        // Отправка ошибки клиенту (для разработки)
        if ($_ENV['APP_ENV'] === 'development') {
            $conn->send(new Response(500, [], $e->getMessage()));
        } else {
            $conn->send(new Response(500, [], 'Internal Server Error'));
        }
    }
}
$server->onError = function($conn, $code, $reason) {
    ErrorHandler::handle(new Exception($reason), $conn);
};
Управление ресурсами
1. Подключения к БД
Плохо - создание нового соединения для каждого запроса:
$server->onMessage = function($conn, $req) {
    $db = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    // Использование...
};
Хорошо - переиспользование соединений:
class DatabaseManager {
    private static ?PDO $connection = null;
    
    public static function getConnection(): PDO {
        if (self::$connection === null) {
            self::$connection = new PDO(
                'mysql:host=localhost;dbname=test',
                $user,
                $pass,
                [
                    PDO::ATTR_PERSISTENT => true,
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]
            );
        }
        
        // Проверка соединения
        try {
            self::$connection->query('SELECT 1');
        } catch (PDOException $e) {
            self::$connection = null;
            return self::getConnection(); // Переподключение
        }
        
        return self::$connection;
    }
}
$server->onMessage = function($conn, $req) {
    $db = DatabaseManager::getConnection();
    // Использование...
};
2. Кеширование
class CacheManager {
    private static array $cache = [];
    private static array $timestamps = [];
    private const TTL = 300; // 5 минут
    
    public static function get(string $key): mixed {
        if (!isset(self::$cache[$key])) {
            return null;
        }
        
        // Проверка TTL
        if (time() - self::$timestamps[$key] > self::TTL) {
            unset(self::$cache[$key], self::$timestamps[$key]);
            return null;
        }
        
        return self::$cache[$key];
    }
    
    public static function set(string $key, mixed $value): void {
        self::$cache[$key] = $value;
        self::$timestamps[$key] = time();
        
        // Ограничение размера кеша
        if (count(self::$cache) > 1000) {
            // Удаление самых старых записей
            asort(self::$timestamps);
            $oldest = array_keys(self::$timestamps)[0];
            unset(self::$cache[$oldest], self::$timestamps[$oldest]);
        }
    }
}
3. Управление соединениями
// Всегда очищайте соединения при закрытии
$server->onClose = function(TcpConnection $conn) {
    // Очистка таймеров
    if (isset($conn->context->timerId)) {
        Timer::del($conn->context->timerId);
    }
    
    // Очистка из глобальных массивов
    if (isset($globalConnections[$conn->id])) {
        unset($globalConnections[$conn->id]);
    }
    
    // Обнуление ссылок
    $conn->context = null;
};
Безопасность
1. Валидация входных данных
class Validator {
    public static function validateRequest(Request $req): array {
        $errors = [];
        
        // Валидация параметров
        $name = $req->post('name');
        if (empty($name) || strlen($name) > 100) {
            $errors[] = 'Invalid name';
        }
        
        // Валидация email
        $email = $req->post('email');
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = 'Invalid email';
        }
        
        // Защита от XSS
        $message = htmlspecialchars($req->post('message'), ENT_QUOTES, 'UTF-8');
        
        return ['errors' => $errors, 'data' => compact('name', 'email', 'message')];
    }
}
$server->onMessage = function($conn, $req) {
    $validation = Validator::validateRequest($req);
    
    if (!empty($validation['errors'])) {
        $conn->send(new Response(400, [], json_encode($validation['errors'])));
        return;
    }
    
    // Обработка валидных данных
};
2. Защита от SQL Injection
// Всегда используйте подготовленные запросы
$stmt = $db->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $req->get('id')]);
$user = $stmt->fetch();
// НИКОГДА не делайте так:
// $db->query("SELECT * FROM users WHERE id = " . $req->get('id'));
3. Защита сессий
// Генерация безопасного session ID
function generateSessionId(): string {
    return bin2hex(random_bytes(32));
}
// Установка безопасных cookies
$response->cookie('session_id', $sessionId, [
    'httponly' => true,
    'secure' => true,
    'samesite' => 'Strict',
    'max-age' => 3600
]);
Производительность
1. Избегайте блокирующих операций
Плохо:
$server->onMessage = function($conn, $req) {
    // Блокирует весь процесс!
    $data = file_get_contents('large_file.txt');
    $conn->send($data);
};
Хорошо:
$server->onMessage = function($conn, $req) {
    // Асинхронное чтение файла
    $file = fopen('large_file.txt', 'r');
    
    $eventLoop = Server::getEventLoop();
    $eventLoop->onReadable($file, function($file) use ($conn, $eventLoop) {
        $chunk = fread($file, 8192);
        
        if ($chunk === false || feof($file)) {
            $eventLoop->offReadable($file);
            fclose($file);
        } else {
            $conn->send($chunk, true); // raw mode
        }
    });
};
2. Оптимизация протоколов
// Кеширование результатов парсинга
class OptimizedProtocol implements ProtocolInterface {
    private static array $decodeCache = [];
    
    public static function decode(string $buffer, ConnectionInterface $conn): mixed {
        // Использование кеша для повторяющихся данных
        if (isset(self::$decodeCache[$buffer])) {
            return clone self::$decodeCache[$buffer];
        }
        
        $result = self::parseBuffer($buffer);
        
        // Кеширование (с ограничением размера)
        if (count(self::$decodeCache) < 512) {
            self::$decodeCache[$buffer] = $result;
        }
        
        return $result;
    }
}
3. Минимизация аллокаций
// Переиспользование объектов
class ObjectPool {
    private array $pool = [];
    
    public function get(): object {
        if (empty($this->pool)) {
            return new MyObject();
        }
        return array_pop($this->pool);
    }
    
    public function release(object $obj): void {
        $obj->reset(); // Сброс состояния
        $this->pool[] = $obj;
    }
}
Тестирование
Unit тестирование
// tests/Unit/UserServiceTest.php
class UserServiceTest extends TestCase {
    public function testGetUser(): void {
        $service = new UserService(new MockDatabase());
        $user = $service->getUser(1);
        
        $this->assertNotNull($user);
        $this->assertEquals(1, $user->id);
    }
}
Интеграционное тестирование
// tests/Integration/ServerTest.php
class ServerTest extends TestCase {
    public function testHttpRequest(): void {
        $server = new Server('http://127.0.0.1:0'); // Динамический порт
        $server->count = 1;
        
        $server->onMessage = function($conn, $req) {
            $conn->send('OK');
        };
        
        $server->listen();
        $address = $server->getSocketName();
        
        // Тестирование через клиент
        $client = stream_socket_client($address);
        fwrite($client, "GET / HTTP/1.1\r\n\r\n");
        $response = fread($client, 1024);
        
        $this->assertStringContainsString('OK', $response);
    }
}
Логирование
Структурированное логирование
class Logger {
    public static function info(string $message, array $context = []): void {
        $log = [
            'timestamp' => date('c'),
            'level' => 'info',
            'message' => $message,
            'context' => $context
        ];
        
        Server::log(json_encode($log));
    }
    
    public static function error(Throwable $e, array $context = []): void {
        $log = [
            'timestamp' => date('c'),
            'level' => 'error',
            'message' => $e->getMessage(),
            'trace' => $e->getTraceAsString(),
            'context' => $context
        ];
        
        Server::log(json_encode($log));
    }
}
// Использование
Logger::info('User connected', ['user_id' => 123, 'ip' => $conn->getRemoteIp()]);
Код-стиль
PSR стандарты
- PSR-1: Basic Coding Standard
 - PSR-12: Extended Coding Style
 - PSR-4: Autoloading Standard
 
Рекомендации
- Используйте strict types:
 
<?php declare(strict_types=1);
- Типизация:
 
// Всегда указывайте типы
public function process(Request $request): Response
- Именование:
 
// CamelCase для методов
public function getUserById(int $id): User
// snake_case для констант
const MAX_CONNECTIONS = 1000;
- Документация:
 
/**
 * Обрабатывает HTTP запрос.
 *
 * @param TcpConnection $connection Соединение с клиентом
 * @param Request $request HTTP запрос
 * @throws InvalidArgumentException Если запрос невалиден
 */
public function onMessage(ConnectionInterface $connection, mixed $request): void
Чеклист разработки
- Код следует PSR стандартам
 - Все методы имеют типизацию
 - Входные данные валидируются
 - Ошибки обрабатываются централизованно
 - Ресурсы правильно управляются
 - Нет блокирующих операций
 - Код покрыт тестами
 - Документация актуальна
 - Логирование настроено
 - Безопасность учтена
 

