Метка: PHP

  • Как создать уникальные индексы в Doctrine и MySQL: Полное руководство

    В этой статье мы разберем, как создавать уникальные индексы в Doctrine (для Symfony) и MySQL. Вы узнаете:

    • Как определять уникальные поля через Doctrine ORM
    • Особенности работы UNIQUE-индексов с NULL-значениями
    • Как создавать составные уникальные индексы
    • Различия между MySQL и другими СУБД

    1. Создание уникальных индексов в Doctrine

    1.1. Через атрибуты (PHP 8+)

    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity]
    #[ORM\Table(name: 'users')]
    #[ORM\UniqueConstraint(name: 'unique_email', columns: ['email'])]
    class User
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(type: 'string', length: 255, unique: true)]
        private string $email;
    }

    1.2. Через YAML-конфигурацию

    App\Entity\User:
      type: entity
      table: users
      uniqueConstraints:
        unique_email:
          columns: [email]
      fields:
        email:
          type: string
          unique: true

    1.3. Составные уникальные индексы

    #[ORM\Entity]
    #[ORM\Table(uniqueConstraints: [
        new ORM\UniqueConstraint(
            name: 'unique_user_product', 
            columns: ['user_id', 'product_id']
        )
    ])]
    class CartItem
    {
        #[ORM\ManyToOne(targetEntity: User::class)]
        private User $user;
    
        #[ORM\ManyToOne(targetEntity: Product::class)]
        private Product $product;
    }

    2. Особенности работы с NULL в MySQL

    2.1. Поведение NULL в UNIQUE-индексах

    CREATE TABLE users (
        email VARCHAR(255) UNIQUE
    );
    
    INSERT INTO users (email) VALUES (NULL), (NULL); -- Разрешено в MySQL

    2.2. Как запретить дубликаты NULL

    Вариант 1: Использовать NOT NULL

    CREATE TABLE users (
        email VARCHAR(255) NOT NULL DEFAULT '',
        UNIQUE (email)
    );

    Вариант 2: Триггер для проверки

    DELIMITER //
    CREATE TRIGGER prevent_null_duplicates
    BEFORE INSERT ON users
    FOR EACH ROW
    BEGIN
        IF NEW.email IS NULL AND EXISTS (
            SELECT 1 FROM users WHERE email IS NULL
        ) THEN
            SIGNAL SQLSTATE '45000' 
            SET MESSAGE_TEXT = 'Duplicate NULL values not allowed';
        END IF;
    END//
    DELIMITER ;

    Минус триггера, дополнительная нагрузка на БД и время выполнения запроса.

    3. Различия между СУБД

    СУБДПоведение NULL в UNIQUE
    MySQLРазрешает несколько NULL
    PostgreSQLРазрешает только один NULL

    4. Проверка индексов

    В Doctrine:

    php bin/console doctrine:schema:validate

    В MySQL:

    SHOW INDEX FROM users WHERE Non_unique = 0;

    Заключение

    • Используйте unique: true или UniqueConstraint в Doctrine
    • Помните о различиях в обработке NULL разными СУБД
    • Для строгой уникальности заменяйте NULL на пустые строки
    • Составные индексы работают по-разному в MySQL 8.0+

    Совет: Всегда проверяйте поведение UNIQUE-индексов в вашей версии СУБД перед развертыванием в production.

  • Основные настройки PHP-FPM для веб-сервера

    Правильная конфигурация PHP-FPM критически важна для безопасности и стабильности работы веб-приложений. Разберём ключевые параметры из файла конфигурации пула (обычно /etc/php/8.x/fpm/pool.d/www.conf).

    1. Пользователь и группа (user/group)

    user = www-data
    group = www-data
    • Назначение: Определяет, от какого пользователя будут выполняться PHP-скрипты
    • Рекомендации:
      • Всегда используйте отдельного пользователя (не root!)
      • www-data — стандартный пользователь для веб-серверов в Ubuntu/Debian
      • Для изоляции разных сайтов создавайте отдельных пользователей
    • Безопасность: Ограничивает права PHP-скриптов в системе

    2. Группа сокета (listen.group)

    listen.group = www-data
    • Назначение: Указывает группу, которой доступен Unix-сокет PHP-FPM
    • Важно: Веб-сервер Nginx должен быть в этой же группе
    • Проверка:
      ps aux | grep nginx | grep -v grep
      groups www-data

    3. Права доступа к сокету (listen.mode)

    listen.mode = 0660
    ПраваВладелецГруппаОстальные
    0660rw-rw-
    • Безопасность: Запрещает доступ всем, кроме владельца и группы
    • Альтернативы:
      • 0666 — небезопасно (доступ всем)
      • 0600 — только владельцу (могут быть проблемы с веб-сервером)

    4. Дополнительные важные настройки

    ПараметрРекомендуемое значениеОписание
    pmondemandРежим управления процессами (для серверов с переменной нагрузкой)
    pm.max_children50Максимальное число процессов (зависит от памяти сервера)
    request_terminate_timeout30sМаксимальное время выполнения скрипта
    security.limit_extensions.phpОграничивает выполнение только PHP-файлов

    5. Проверка и применение настроек

    # Проверка синтаксиса
    sudo php-fpm8.x -t
    
    # Перезагрузка PHP-FPM
    sudo systemctl restart php8.x-fpm
    
    # Проверка прав сокета
    ls -la /run/php/php8.x-fpm.sock

    Вывод

    • Используйте отдельного пользователя/группу для PHP-FPM
    • Ограничивайте права доступа к сокету (0660 — оптимально)
    • Регулярно проверяйте логи PHP-FPM на ошибки
    • Настройки зависят от нагрузки и специфики приложения

    Эти настройки обеспечат баланс между безопасностью и производительностью вашего веб-сервера.

  • Как добавить пользователя с правами www-data на Ubuntu 22.04 для веб-разработки

    При настройке веб-сервера часто требуется создать пользователя с ограниченными правами, но с доступом к файлам веб-приложений. В этом руководстве я покажу, как правильно добавить пользователя в группу www-data на Ubuntu 22.04.

    Зачем это нужно?

    • Безопасный доступ к файлам веб-сервера без root-прав
    • Корректная работа PHP-скриптов и WordPress
    • Возможность совместной работы над проектом
    • Правильные права для загрузки файлов через веб-интерфейс

    Шаг 1: Создаем нового пользователя

    sudo adduser devuser

    Замените «devuser» на имя вашего пользователя. Система запросит задать пароль и дополнительную информацию (можно пропустить).

    Шаг 2: Добавляем пользователя в группу www-data

    sudo usermod -aG www-data devuser
    sudo groups devuser | grep www-data

    Шаг 3: Настраиваем права на веб-папки

    sudo chown -R www-data:www-data /var/www/html
    sudo chmod -R 775 /var/www/html

    Эти команды:

    • Меняют владельца на www-data
    • Дают группе www-data права на запись
    • Сохраняют права на выполнение для всех

    Шаг 4: Настройка PHP-FPM (если используется)

    sudo nano /etc/php/8.1/fpm/pool.d/www.conf

    Добавьте или измените строки:

    user = www-data
    group = www-data
    listen.group = www-data
    listen.mode = 0660

    Перезапустите PHP-FPM:

    sudo systemctl restart php8.1-fpm

    Шаг 5: Проверка настроек

    sudo -u devuser -g www-data touch /var/www/html/test.txt

    Если файл создался — настройки верны.

    SSH доступ

    sudo rsync --archive --chown=devuser:www-data ~/.ssh /home/devuser/
    sudo chmod 700 /home/devuser/.ssh
    sudo chmod 600 /home/devuser/.ssh/authorized_keys

    Важные нюансы

    • Не давайте пользователю sudo без необходимости
    • Для системных задач используйте sudo -u www-data
    • Проверяйте логи при ошибках: sudo tail -f /var/log/nginx/error.log

    Заключение

    Теперь у вас есть безопасно настроенный пользователь для работы с веб-проектами. Это особенно полезно при:

    • Разработке в команде
    • Настройке CI/CD
    • Управлении правами на production-сервере

    Если у вас есть вопросы или дополнения — оставляйте комментарии!

  • Как ускорить поиск в больших массивах PHP: array_flip() + isset() вместо in_array()

    При работе с большими массивами в PHP стандартный in_array() может стать узким местом производительности. Разберём профессиональный метод оптимизации с использованием array_flip() и isset().

    Проблема производительности in_array()

    Метод in_array() в PHP имеет линейную сложность O(n) — он последовательно проверяет каждый элемент массива:

    $array = range(1, 100000); // Массив из 100,000 элементов
    
    // Медленно на больших массивах:
    if (in_array(99999, $array)) {
        // Поиск займёт значительное время
    }

    Оптимизированное решение

    Используем комбинацию array_flip() и isset():

    $array = range(1, 100000);
    $flipped = array_flip($array);
    
    // Мгновенный поиск:
    if (isset($flipped[99999])) {
        // Работает за константное время O(1)
    }

    Как это работает?

    ШагДействиеРезультат
    1array_flip()Преобразует массив, меняя ключи и значения местами:
    Было: [0 => 100, 1 => 200]
    Стало: [100 => 0, 200 => 1]
    2isset()Проверяет существование ключа через хеш-таблицу (O(1))

    Бенчмарк производительности

    Тест на массиве из 1,000,000 элементов:

    МетодВремя выполнения
    in_array()~15.2 ms
    isset() с array_flip()~0.002 ms

    Когда стоит применять?

    • Массивы от 1,000+ элементов
    • Многократные проверки одного массива
    • Критичные к производительности участки кода

    Ограничения метода

    • Требует уникальных значений в массиве
    • Не работает с многомерными массивами
    • Дополнительная память для flipped-версии

    Альтернативные решения

    • Для PHP 8+: array_is_list() + in_array()
    • Для сортированных массивов: array_search() с бинарным поиском
    • Для частых операций: Использование структур данных типа Set

    Практический пример

    // Оптимизация поиска ID в результатах БД
    $usersIds = array_column($users, 'id'); // [152, 734, ...]
    $flippedIds = array_flip($usersIds);
    
    // Быстрая проверка существования ID
    function isUserExists($userId, $flippedIds) {
        return isset($flippedIds[$userId]);
    }

    Вопрос к читателям

    Как вы оптимизируете работу с большими массивами в своих проектах? Делитесь опытом в комментариях!

  • Аналог JavaScript includes() в PHP

    Разбираем, как заменить популярный метод JavaScript includes() в PHP с примерами кода и подводными камнями.

    📌 JavaScript: includes()

    В JavaScript метод проверяет наличие подстроки или элемента в массиве:

    // Строки
    "Hello".includes("ell"); // true
    
    // Массивы
    [1, 2, 3].includes(2); // true

    📌 PHP: аналоги

    1. Для строк

    str_contains() (PHP 8.0+) — самый простой вариант:

    str_contains("Hello", "ell"); // true

    strpos() (для PHP < 8.0) — требует точной проверки:

    if (strpos("Hello", "ell") !== false) { // true
        echo "Найдено!";
    }

    🔹 Подводный камень:
    strpos() возвращает 0, если подстрока в начале строки, поэтому проверка должна быть строго !== false:

    strpos("Hello", "He"); // 0 (но это не false!)
    2. Для массивов

    in_array() — аналог [].includes():

    in_array(2, [1, 2, 3]); // true

    array_key_exists() — проверка ключа:

    array_key_exists("key", ["key" => 42]); // true

    🔹 Подводный камень:
    in_array() по умолчанию использует нестрогую проверку (==). Для строгой (===) укажите третий параметр:

    in_array("2", [1, 2, 3]); // true (без strict)
    in_array("2", [1, 2, 3], true); // false (со strict)
    3. Регистронезависимый поиск

    Для строк используйте stripos():

    stripos("Hello", "hello"); // 0 (проверяйте !== false!)

    Для массивов — преобразуйте элементы в нижний регистр:

    in_array(strtolower("HELLO"), array_map('strtolower', $array));

    📌 Советы по оптимизации

    • Для строк: str_contains() (PHP 8+) быстрее и читабельнее.
    • Для массивов: isset($array[$key]) работает быстрее in_array(), но проверяет только ключи.
    • Большие массивы: Используйте связку array_flip() + isset():
    isset(array_flip($array)[$value]);

    📌 Вопрос читателям

    Какой метод вы используете чаще?

    • str_contains() / strpos()
    • in_array() / isset()
    • Другие варианты? Пишите в комментарии!