Рубрика: Разработка

  • Vue 3 Slots передача параметров

    Slots (слоты) в Vue 3 предоставляют мощный механизм для создания гибких и переиспользуемых компонентов. В этой статье мы разберём все способы передачи параметров в слоты с примерами из реальной практики.

    Основные типы слотов в Vue 3

    • Слоты по умолчанию — базовый способ передачи контента
    • Именованные слоты — для точного позиционирования контента
    • Scoped slots — с передачей параметров из дочернего компонента

    1. Scoped Slots (Основной способ передачи параметров)

    <!-- Дочерний компонент -->
    <template>
      <div>
        <slot :item="item" :index="index"></slot>
      </div>
    </template>
    
    <!-- Родительский компонент -->
    <ChildComponent>
      <template v-slot:default="slotProps">
        {{ slotProps.item }} - {{ slotProps.index }}
      </template>
    </ChildComponent>

    2. Именованные scoped slots

    <!-- Дочерний компонент -->
    <template>
      <div>
        <slot name="header" :title="title"></slot>
        <slot name="content" :data="contentData"></slot>
      </div>
    </template>
    
    <!-- Родительский компонент -->
    <DataContainer>
      <template #header="{ title }">
        <h2>{{ title }}</h2>
      </template>
      
      <template #content="{ data }">
        <p>{{ data.description }}</p>
      </template>
    </DataContainer>

    3. Динамические параметры слотов

    <!-- Дочерний компонент -->
    <script setup>
    const slotProps = computed(() => ({
      user: currentUser.value,
      timestamp: new Date()
    }))
    </script>
    
    <template>
      <slot v-bind="slotProps"></slot>
    </template>

    4. Деструктуризация параметров

    <template #item="{ id, name }">
      <div>{{ id }}: {{ name }}</div>
    </template>

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

    Пример 1: Гибкий список

    <SmartList :items="users">
      <template #item="{ user }">
        <UserCard :user="user" />
      </template>
    </SmartList>

    Пример 2: Модальное окно с параметрами

    <ModalDialog>
      <template #header="{ close }">
        <button @click="close">×</button>
      </template>
      
      <template #default="{ data }">
        {{ data.message }}
      </template>
    </ModalDialog>

    Лучшие практики

    • Используйте осмысленные имена для параметров слотов
    • Для сложных компонентов документируйте структуру слотов
    • Избегайте глубокой вложенности scoped slots
    • Используйте TypeScript для типизации параметров

    Заключение

    Scoped slots в Vue 3 предоставляют мощный инструмент для создания гибких компонентов. Освоив передачу параметров в слоты, вы сможете создавать по-настоящему переиспользуемые UI-компоненты.

    Для более сложных сценариев рассмотрите использование Composition API вместе со слотами.

    Vue 3 Composition API + Slots: Сложные сценарии использования

    В сочетании с Composition API слоты Vue 3 раскрывают свою настоящую мощь. Рассмотрим продвинутые паттерны для сложных UI-компонентов.

    1. Динамические слоты с реактивными параметрами

    <!-- DynamicTable.vue -->
    <script setup>
    import { ref, computed } from 'vue'
    
    const props = defineProps(['data'])
    const sortDirection = ref('asc')
    
    const sortedData = computed(() => {
      return [...props.data].sort((a, b) => {
        return sortDirection.value === 'asc' 
          ? a.value - b.value 
          : b.value - a.value
      })
    })
    </script>
    
    <template>
      <table>
        <slot name="header" :toggleSort="() => sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'"></slot>
        <tr v-for="(item, index) in sortedData" :key="index">
          <slot :item="item" :index="index"/>
        </tr>
      </table>
    </template>

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

    2. Состояние модальных окон

    <!-- useModal.js -->
    import { ref } from 'vue'
    
    export function useModal() {
      const isOpen = ref(false)
      
      const open = () => isOpen.value = true
      const close = () => isOpen.value = false
      
      return { isOpen, open, close }
    }
    
    <!-- ModalComponent.vue -->
    <script setup>
    import { useModal } from './useModal'
    const modal = useModal()
    </script>
    
    <template>
      <slot :open="modal.open" :close="modal.close" :isOpen="modal.isOpen"/>
    </template>

    Использование:

    <ModalComponent v-slot="{ open, close, isOpen }">
      <button @click="open">Открыть</button>
      
      <div v-if="isOpen" class="modal">
        <slot name="content"/>
        <button @click="close">Закрыть</button>
      </div>
    </ModalComponent>

    3. Композиционные слоты для форм

    <!-- useFormField.js -->
    import { ref, computed } from 'vue'
    
    export function useFormField(initialValue) {
      const value = ref(initialValue)
      const isValid = computed(() => value.value.length > 0)
      
      return { value, isValid }
    }
    
    <!-- FormField.vue -->
    <script setup>
    import { useFormField } from './useFormField'
    
    const props = defineProps(['initialValue'])
    const field = useFormField(props.initialValue)
    </script>
    
    <template>
      <div class="form-field" :class="{ invalid: !field.isValid }">
        <slot :value="field.value" :isValid="field.isValid"/>
      </div>
    </template>

    Использование с кастомным input:

    <FormField initialValue="" v-slot="{ value, isValid }">
      <input 
        v-model="value"
        :class="{ error: !isValid }"
        placeholder="Введите текст"
      >
      <span v-if="!isValid">Поле обязательно</span>
    </FormField>

    4. Сложные компоненты данных

    <!-- DataFetcher.vue -->
    <script setup>
    import { ref, onMounted } from 'vue'
    
    const props = defineProps(['url'])
    const data = ref(null)
    const error = ref(null)
    const isLoading = ref(false)
    
    onMounted(async () => {
      isLoading.value = true
      try {
        const response = await fetch(props.url)
        data.value = await response.json()
      } catch (err) {
        error.value = err
      } finally {
        isLoading.value = false
      }
    })
    </script>
    
    <template>
      <slot 
        :data="data" 
        :error="error" 
        :isLoading="isLoading"
        :reload="onMounted"
      />
    </template>

    Использование:

    <DataFetcher url="/api/users" v-slot="{ data, isLoading }">
      <div v-if="isLoading">Загрузка...</div>
      <UserList v-else :users="data" />
    </DataFetcher>

    5. Продвинутый пример: Компонент вкладок

    <!-- TabsContainer.vue -->
    <script setup>
    import { ref } from 'vue'
    
    const activeTab = ref(0)
    const tabs = ref([])
    
    const registerTab = (title) => {
      const id = tabs.value.length
      tabs.value.push({ id, title })
      return id
    }
    
    const setActiveTab = (id) => {
      activeTab.value = id
    }
    </script>
    
    <template>
      <div class="tabs-container">
        <div class="tabs-header">
          <button 
            v-for="tab in tabs" 
            @click="setActiveTab(tab.id)"
            :class="{ active: activeTab === tab.id }"
          >
            {{ tab.title }}
          </button>
        </div>
        
        <div class="tabs-content">
          <slot :activeTab="activeTab"/>
        </div>
      </div>
    </template>

    Использование:

    <TabsContainer v-slot="{ activeTab }">
      <template #default>
        <Tab :register="registerTab" title="Профиль">
          <div v-if="activeTab === 0">...</div>
        </Tab>
        
        <Tab :register="registerTab" title="Настройки">
          <div v-if="activeTab === 1">...</div>
        </Tab>
      </template>
    </TabsContainer>

    Заключение

    Сочетание Composition API и слотов позволяет создавать:

    • Полностью инкапсулированную логику компонентов
    • Максимально гибкие API для переиспользуемых компонентов
    • Сложные stateful-компоненты с простым интерфейсом
    • Легко тестируемые решения (бизнес-логика отделена от рендеринга)

  • Как получить 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’) будет наиболее оптимальным решением.

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

  • Как очистить таблицу MySQL с внешними ключами — 4 способа

    При администрировании баз данных MySQL разработчики часто сталкиваются с ошибкой «Cannot truncate a table referenced in a foreign key constraint». Эта статья подробно объяснит причины ошибки и предложит несколько проверенных способов решения с детальным разбором каждого метода.

    Почему MySQL запрещает TRUNCATE для таблиц с внешними ключами?

    Ошибка возникает из-за фундаментального различия между операторами DELETE и TRUNCATE:

    • TRUNCATE — операция DDL (Data Definition Language), которая:
      • Удаляет и воссоздаёт таблицу
      • Не регистрирует отдельные удаления в логе транзакций
      • Сбрасывает автоинкремент
      • Не проверяет ограничения FOREIGN KEY
    • DELETE — операция DML (Data Manipulation Language), которая:
      • Удаляет строки по одной
      • Проверяет все ограничения
      • Может быть частью транзакции

    Детальный разбор методов решения

    1. Временное отключение проверки внешних ключей

    SET FOREIGN_KEY_CHECKS = 0;
    TRUNCATE TABLE products;
    SET FOREIGN_KEY_CHECKS = 1;

    Что происходит:

    • FOREIGN_KEY_CHECKS = 0 отключает проверку ограничений на уровне сессии
    • TRUNCATE выполняется без проверки связанных таблиц
    • FOREIGN_KEY_CHECKS = 1 восстанавливает проверку

    Когда использовать: В разработке и тестовых средах, когда вам нужно быстро очистить таблицу.

    Внимание: Этот метод может нарушить целостность данных, если в связанных таблицах останутся ссылки на удалённые записи.

    2. Использование DELETE с последующим сбросом автоинкремента

    DELETE FROM products;
    ALTER TABLE products AUTO_INCREMENT = 1;

    Что происходит:

    • DELETE удаляет все строки, проверяя ограничения
    • ALTER TABLE сбрасывает счётчик автоинкремента
    • Операция записывается в лог транзакций

    Когда использовать: В production-среде, когда важна целостность данных.

    Недостатки: Для больших таблиц выполняется медленнее, чем TRUNCATE.

    3. Полное удаление и воссоздание таблицы

    DROP TABLE products;
    CREATE TABLE products (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        ...
    );

    Что происходит:

    • DROP TABLE полностью удаляет таблицу и все её зависимости
    • CREATE TABLE создаёт новую чистую таблицу
    • Все индексы и ограничения нужно определить заново

    Когда использовать: При изменении структуры таблицы или в тестовых средах.

    4. Удаление и восстановление ограничений

    -- 1. Узнаём имя ограничения
    SELECT TABLE_NAME, CONSTRAINT_NAME 
    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
    WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' 
    AND TABLE_NAME = 'order_items';
    
    -- 2. Удаляем ограничение
    ALTER TABLE order_items DROP FOREIGN KEY fk_order_items_products;
    
    -- 3. Очищаем таблицу
    TRUNCATE TABLE products;
    
    -- 4. Восстанавливаем ограничение
    ALTER TABLE order_items ADD CONSTRAINT fk_order_items_products 
    FOREIGN KEY (product_id) REFERENCES products(id) 
    ON DELETE RESTRICT ON UPDATE CASCADE;

    Что происходит:

    • Сначала находим точное имя ограничения
    • Временно удаляем FOREIGN KEY
    • Выполняем TRUNCATE
    • Восстанавливаем ограничение с теми же параметрами

    Когда использовать: Когда нужно сохранить все другие настройки таблицы.

    Рекомендации для разных сценариев

    СценарийРекомендуемый методПримечания
    Разработка/тестированиеОтключение FOREIGN_KEY_CHECKSБыстро, но требует осторожности
    Production-средаDELETE + ALTER TABLEБезопасно, но медленно для больших таблиц
    Изменение структурыDROP + CREATE TABLEПолная пересоздание структуры
    Миграции данныхУдаление/восстановление ограниченийТочный контроль над процессом

    Дополнительные советы

    • Всегда делайте бэкап перед массовыми операциями
    • Используйте транзакции для безопасности:
      START TRANSACTION;
      SET FOREIGN_KEY_CHECKS = 0;
      TRUNCATE TABLE products;
      SET FOREIGN_KEY_CHECKS = 1;
      COMMIT;

    • Для Doctrine в Symfony:
      $this->addSql('SET FOREIGN_KEY_CHECKS = 0');
      $this->addSql('TRUNCATE TABLE products');
      $this->addSql('SET FOREIGN_KEY_CHECKS = 1');

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

  • Методы массивов в JavaScript для поиска и проверки элементов в массиве: some, every, includes, find, findIndex.

    В JavaScript у массивов есть несколько методов для проверки элементов. В этой статье разберём:

    • some() — проверяет, удовлетворяет ли хотя бы один элемент условию.
    • Похожие методы: every(), includes(), find(), findIndex().
    • Примеры и сравнение с аналогами.

    1. Array.some(): Хотя бы один элемент проходит проверку

    Синтаксис

    arr.some(callback(element, index, array));

    — Возвращает true, если хотя бы один элемент соответствует условию.
    — Иначе — false.

    Пример

    const numbers = [1, 2, 3, 4, 5];
    
    // Есть ли хотя бы одно чётное число?
    const hasEven = numbers.some(num => num % 2 === 0);
    console.log(hasEven); // true (2 и 4 подходят)

    Когда использовать?

    Проверка, что в массиве есть хотя бы один подходящий элемент.


    2. Array.every(): Все элементы проходят проверку

    Синтаксис

    arr.every(callback(element, index, array));

    — Возвращает true, если все элементы удовлетворяют условию.

    Пример

    const ages = [18, 22, 25, 30];
    
    // Все ли совершеннолетние?
    const allAdults = ages.every(age => age >= 18);
    console.log(allAdults); // true

    Сравнение some() и every()

    МетодВозвращает true, если…Аналог в логике
    some()Хотя бы один элемент подходит|| (ИЛИ)
    every()Все элементы подходят&& (И)

    3. Array.includes(): Проверка наличия конкретного значения

    Синтаксис

    arr.includes(value, fromIndex);

    — Проверяет, есть ли конкретное значение в массиве.

    Пример

    const fruits = ['apple', 'banana', 'orange'];
    
    console.log(fruits.includes('banana')); // true
    console.log(fruits.includes('grape'));  // false

    includes() vs some()

    includes() — ищет конкретное значение.
    some() — проверяет условие (например, через функцию).

    // Эквивалентные проверки:
    const numbers = [1, 2, 3];
    
    // Через includes()
    numbers.includes(2); // true
    
    // Через some()
    numbers.some(num => num === 2); // true

    4. Array.find() и findIndex(): Поиск первого подходящего элемента

    find()

    Возвращает первый элемент, удовлетворяющий условию:

    const users = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ];
    
    const bob = users.find(user => user.name === 'Bob');
    console.log(bob); // { id: 2, name: 'Bob' }

    findIndex()

    Возвращает индекс первого подходящего элемента (или -1):

    const index = users.findIndex(user => user.name === 'Bob');
    console.log(index); // 1

    Сравнение с some()

    МетодВозвращаетПодходит для…
    some()true/falseПроверка наличия
    find()Элемент или undefinedПолучение объекта
    findIndex()Индекс или -1Удаление/замена элемента

    5. Итог: Какой метод выбрать?

    ЗадачаМетод
    Есть ли хотя бы один подходящий?some()
    Все элементы подходят?every()
    Есть ли конкретное значение?includes()
    Найти первый подходящий элементfind()
    Найти индекс элементаfindIndex()

    Примеры использования

    ❶ Проверка прав доступа

    const permissions = ['read', 'write', 'delete'];
    
    // Есть ли право на запись?
    const canWrite = permissions.some(perm => perm === 'write');

    ❷ Валидация формы

    const inputs = ['', 'test@example.com', '123'];
    
    // Все ли поля заполнены?
    const isValid = inputs.every(input => input.trim() !== '');

    ❸ Поиск в массиве объектов

    const products = [
      { id: 1, name: 'Laptop', inStock: true },
      { id: 2, name: 'Phone', inStock: false },
    ];
    
    // Есть ли хотя бы один товар в наличии?
    const hasStock = products.some(product => product.inStock);

    Вывод

    some() — лучший выбор для проверки хотя бы одного элемента.
    every() — если нужно убедиться, что все элементы подходят.
    includes() — для простой проверки значений.
    find()/findIndex() — если нужен сам элемент или его индекс.

    Используйте эти методы, чтобы писать чистый и эффективный код! 🚀