Метка: Doctrine

  • Как получить ID сущности Doctrine без загрузки всей сущности в Symfony

    При работе с Doctrine ORM в Symfony часто возникает необходимость получить только идентификатор сущности, не загружая все её данные из базы данных. Это особенно важно для оптимизации производительности при работе с большими объемами данных.

    Почему это важно?

    • Уменьшение количества запросов к БД
    • Снижение потребления памяти
    • Повышение скорости выполнения операций
    • Оптимизация работы с ассоциациями

    Способы получения ID без загрузки сущности

    1. Использование getReference()

    // Получаем ссылку на сущность без её загрузки
    $entityReference = $entityManager->getReference(Product::class, $id);
    $entityId = $entityReference->getId();

    2. DQL с выборкой только ID

    $query = $entityManager->createQuery(
        'SELECT p.id FROM App\Entity\Product p WHERE p.price > :price'
    )->setParameter('price', 100);
    
    $ids = $query->getResult(); // Возвращает массив ID

    3. QueryBuilder с выборкой ID

    $ids = $entityManager->getRepository(Product::class)
        ->createQueryBuilder('p')
        ->select('p.id')
        ->where('p.stock > 0')
        ->getQuery()
        ->getResult();

    4. Работа с ассоциациями

    // Получаем ID связанной сущности без её полной загрузки
    $categoryId = $product->getCategory()->getId();
    // Doctrine использует прокси-объекты для ленивой загрузки

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

    Рассмотрим реальный сценарий — нам нужно получить список ID товаров, которые нужно обновить:

    public function getProductIdsToUpdate(EntityManagerInterface $em): array
    {
        return $em->getRepository(Product::class)
            ->createQueryBuilder('p')
            ->select('p.id')
            ->where('p.updatedAt < :date')
            ->setParameter('date', new \DateTime('-1 week'))
            ->getQuery()
            ->getSingleColumnResult();
    }

    Заключение

    Использование этих методов позволяет значительно оптимизировать работу с базой данных в Symfony-приложениях. Выбор конкретного способа зависит от вашего сценария использования, но в большинстве случаев QueryBuilder с явным указанием select(‘id’) будет наиболее оптимальным решением.

    Помните, что преждевременная оптимизация может быть вредна — используйте эти подходы там, где действительно есть проблемы с производительностью.

  • Как создать уникальные индексы в 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.