Конструктор запросов mysql php

Обновлено: 06.05.2024

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

  • Поддержка sleep/wakeup
  • Изменены названия методов в классе Statement (старые методы назывались не в CamelCase стиле по историческим причинам)
  • Изменено пространство имён
  • Больше не поддерживается, советую переходить на релиз v2.0

Что такое Database?

Database — библиотека классов на PHP >= 5.3 для простой, удобной, быстрой и безопасной работы с базой данных MySql, использующая расширение PHP mysqli.

Зачем нужен самописный класс для MySql, если в PHP есть абстракция PDO и расширение mysqli?

Основные недостатки всех библиотек для работы с базой в PHP это:

  • Многословность
    • Что бы предотвратить SQL-инъекции, у разработчиков есть два пути:
      • Использовать подготавливаемые запросы (prepared statements).
      • Вручную экранировать параметры идущие в тело SQL-запроса. Строковые параметры прогонять через mysqli_real_escape_string(), а ожидаемые числовые параметры приводить к соответствующим типам — int и float .
      • Подготавливаемые запросы ужасно многословны. Пользоваться "из коробки" абстракцией PDO или расширением mysqli, без агрегирования всех методов для получения данных из СУБД просто невозможно — что бы получить значение из таблицы необходимо написать минимум 5 строк кода! И так на каждый запрос!
      • Экранирование вручную параметров, идущих в тело SQL-запроса — даже не обсуждается. Хороший программист — ленивый программист. Всё должно быть максимально автоматизировано.
      • Что бы понять, почему в программе не работает SQL-запрос, его нужно отладить — найти либо логическую, либо синтаксическую ошибку. Что бы найти ошибку, необходимо "видеть" сам SQL-запрос, на который "ругнулась" база, с подставленными в его тело параметрами. Т.е. иметь сформированный полноценный SQL. Если разработчик использует PDO, с подготавливаемыми запросами, то это сделать. НЕВОЗМОЖНО! Никаких максимально удобных механизмов для этого в родных библиотеках НЕ ПРЕДУСМОТРЕНО. Остается либо извращаться, либо лезть в лог базы данных.

      Решение: Database — класс для работы с MySql

      1. Избавляет от многословности — вместо 3 и более строк кода для исполнения одного запроса при использовании "родной" библиотеки, вы пишите всего 1.
      2. Экранирует все параметры, идущие в тело запроса, согласно указанному типу заполнителей — надежная защита от SQL-инъекций.
      3. Не замещает функциональность "родного" mysqli адаптера, а просто дополняет его.

      Чем НЕ является библиотека Database?

      Большинство оберток под различные драйверы баз данных являются нагромождением бесполезного кода с отвратительной архитектурой. Их авторы, сами не понимая практической цели своих оберток, превращают их в подобие построителей запросов (sql builder), ActiveRecord библиотек и прочих ORM-решений.

      Библиотека Database не является ничем из перечисленных. Это лишь удобный инструмент для работы с обычным SQL в рамках СУБД MySQL — и не более!

      Что такое placeholders (заполнители)?

      Placeholders (англ. — заполнители) — специальные типизированные маркеры, которые пишутся в строке SQL запроса вместо явных значений (параметров запроса). А сами значения передаются "позже", в качестве последующих аргументов основного метода, выполняющего SQL-запрос:

      Параметры SQL-запроса, прошедшие через систему placeholders, обрабатываются специальными механизмами экранирования, в зависимости от типа заполнителей. Т.е. вам теперь нет необходимости заключать переменные в функции экранирования типа mysqli_real_escape_string() или приводить их к числовому типу, как это было раньше:

      Теперь запросы стало писать легко, быстро, а главное библиотека Database полностью предотвращает любые возможные SQL-инъекции.

      Типы заполнителей и типы параметров SQL-запроса

      Типы заполнителей и их предназначение описываются ниже. Прежде чем знакомиться с типами заполнителей, необходимо понять как работает механизм библиотеки Database.

      SQL-запрос после преобразования шаблона:

      В процессе исполнения этой команды библиотека проверяет, является ли аргумент 123 целочисленным значением. Заполнитель ?i представляет собой символ ? (знак вопроса) и первую букву слова integer . Если аргумент действительно представляет собой целочисленный тип данных, то в шаблоне SQL-запроса заполнитель ?i заменяется на значение 123 и SQL передается на исполнение.

      Поскольку PHP слаботипизированный язык, то вышеописанное выражение эквивалентно нижеописанному:

      SQL-запрос после преобразования шаблона:

      т.е. числа (целые и с плавающей точкой) представленные как в своем типе, так и в виде string — равнозначны с точки зрения библиотеки.

      Приведение к типу заполнителя

      SQL-запрос после преобразования шаблона:

      В данном примере заполнитель целочисленного типа данных ожидает значение типа integer , а передается double . По-умолчанию библиотека работает в режиме приведения типов, что дало в итоге приведение типа double к int .

      Режимы работы библиотеки и принудительное приведение типов

      Существует два режима работы библиотеки:

      • Mysql::MODE_STRICT — строгий режим соответствия типа заполнителя и типа аргумента. В режиме MODE_STRICT аргументы должны соответствовать типу заполнителя. Например, попытка передать в качестве аргумента значение 55.5 или '55.5' для заполнителя целочисленного типа ?i приведет к выбросу исключения:
      • Mysql::MODE_TRANSFORM — режим преобразования аргумента к типу заполнителя при несовпадении типа заполнителя и типа аргумента. Режим MODE_TRANSFORM установлен по-умолчанию и является "толерантным" режимом — при несоответствии типа заполнителя и типа аргумента не генерирует исключение, а пытается преобразовать аргумент к нужному типу заполнителя посредством самого языка PHP. К слову сказать, я, как автор библиотеки, всегда использую именно этот режим, строгий режим (Mysql::MODE_STRICT) я сделал чисто "по фану" и в реальной работе никогда не использовал.

      Допускаются следующие преобразования в режиме Mysql::MODE_TRANSFORM:

      • К типу int (заполнитель ?i ) приводятся
        • числа с плавающей точкой, представленные как string или тип double
        • bool TRUE преобразуется в int(1) , FALSE преобразуется в int(0)
        • null преобразуется в int(0)
        • целые числа, представленные как string или тип int
        • bool TRUE преобразуется в float(1) , FALSE преобразуется в float(0)
        • null преобразуется в float(0)
        • bool TRUE преобразуется в string(1) "1" , FALSE преобразуется в string(1) "0" . Это поведение отличается от приведения типа bool к int в PHP, т.к. зачастую, на практике, булев тип записывается в MySql именно как число.
        • значение типа numeric преобразуется в строку согласно правилам преобразования PHP
        • NULL преобразуется в string(0) ""
        • любые аргументы.

        Какие типы заполнителей представлены в библиотеке Database?

        ?i — заполнитель целого числа

        ВНИМАНИЕ! Если вы оперируете числами, выходящими за пределы PHP_INT_MAX, то:

        ?d — заполнитель числа с плавающей точкой

        ВНИМАНИЕ! Если вы используете библиотеку для работы с типом данных double , установите соответствующую локаль, что бы разделитель целой и дробной части был одинаков как на уровне PHP, так и на уровне СУБД.

        ?s — заполнитель строкового типа

        Значение аргументов экранируются с помощью функции PHP mysqli_real_escape_string() :

        SQL-запрос после преобразования шаблона:

        ?S — заполнитель строкового типа для подстановки в SQL-оператор LIKE

        Значение аргументов экранируются с помощью функции PHP mysqli_real_escape_string() + экранирование спецсимволов, используемых в операторе LIKE ( % и _ ):

        SQL-запрос после преобразования шаблона:

        ?n — заполнитель NULL типа

        Значение любых аргументов игнорируются, заполнители заменяются на строку NULL в SQL запросе:

        SQL-запрос после преобразования шаблона:

        ?A* — заполнитель ассоциативного множества из ассоциативного массива, генерирующий последовательность пар ключ = значение

        Пример: "key_1" = "val_1", "key_2" = "val_2", . "key_N" = "val_N"

        где * после заполнителя — один из типов:

        • i (заполнитель целого числа)
        • d (заполнитель числа с плавающей точкой)
        • s (заполнитель строкового типа)

        правила преобразования и экранирования такие же, как и для одиночных скалярных типов, описанных выше. Пример:

        SQL-запрос после преобразования шаблона:

        ?a* — заполнитель множества из простого (или также ассоциативного) массива, генерирующий последовательность значений

        Пример: "val_1", "val_2", . "val_N"

        где * после заполнителя — один из типов:

        • i (заполнитель целого числа)
        • d (заполнитель числа с плавающей точкой)
        • s (заполнитель строкового типа)

        правила преобразования и экранирования такие же, как и для одиночных скалярных типов, описанных выше. Пример:

        SQL-запрос после преобразования шаблона:

        ?A[?n, ?s, ?i, . ] — заполнитель ассоциативного множества с явным указанием типа и количества аргументов, генерирующий последовательность пар ключ = значение

        SQL-запрос после преобразования шаблона:

        ?a[?n, ?s, ?i] — заполнитель множества с явным указанием типа и количества аргументов, генерирующий последовательность значений

        SQL-запрос после преобразования шаблона:

        ?f — заполнитель имени таблицы или поля

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

        SQL-запрос после преобразования шаблона:

        Библиотека требует от программиста соблюдения синтаксиса SQL. Это значит, что следующий запрос работать не будет:

        — заполнитель ?s необходимо взять в одинарные или двойные кавычки:

        SQL-запрос после преобразования шаблона:

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

        Примеры работы с библиотекой

        Примеры для понимания сути заполнителей

        Различные варианты INSERT:

        Простая вставка данных через заполнители разных типов:

        SQL-запрос после преобразования шаблона:

        Вставка значений через заполнитель ассоциативного множества типа string:

        SQL-запрос после преобразования шаблона:

        Вставка значений через заполнитель ассоциативного множества с явным указанием типа и количества аргументов:

        SQL-запрос после преобразования шаблона:

        Различные варианты SELECT

        Укажем некорректный числовой параметр - значение типа double:

        SQL-запрос после преобразования шаблона:

        SQL-запрос после преобразования шаблона:

        SQL-запрос после преобразования шаблона:

        Некоторые возможности API

        Применение метода queryArguments() - аргументы передаются в виде массива. Это второй, после метода query(), метод запросов в базу:

        Вставить запись, получить последнее значение автоинкрементного поля и количество задействованных рядов:

        Где-то полтора года назад я начал заниматься web разработкой. Начинал с функционального программирования. Примерно пол года назад я перешел на ООП и стал использовать MVC архитектуру проектирования. Недавно появилась задача оптимизировать работу с базой данных, т. к. вся связь и работа с базой осуществлялась через один класс. Это было неудобно потому, что все время приходилось вручную писать SQL — запросы. Задача была разбита на 2 этапа:

        1. Написать класс для подключения к базе данных
        2. Написать класс модели для работы с данными


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

        Метод getRecords
          $what — передает массив полей, которые нужно выбрать из таблицы $where — передает ассоциативный массив ключей в виде array('Поле'=>array('знак','значение')). Это дает нам возможность более гибко использовать предикат WHERE
        • $limit — передает ассоциативный массив ключей в виде array('начальная запись', 'количество записей'). Такая структура дает нам возможность реализовать постраничный вывод или вывод ограниченного количества записей
        • $order — ассоциативный массив array('поле'=>'вид сортировки'). Дает возможность сортировки по любому количеству столбцов. $join — ассоциативный массив array('Тип связи', array('Таблица1', 'Таблица2'), array('Алиас1', 'Алиас2'), array('поле1','поле2')). Тип связи: LEFT, INNER, RIGHT, OUTER; Таблица1 и Таблица2: таблицы между которыми устанавливается связь; Аллиас1 и Аллиас2 — псевдонимы для таблицы; Поле1 и Поле2 — это соотв. PK и FK таблиц
        • $degub — этот параметр нужен для сохранения в свойства класса уже созданного sql запроса, а также параметров, которые нужно если мы используем prepare statement в PDO
        Метод addRecord
        • $data — ассоциативный массив параметров в виде: 'поле'=>'значение', которые будут вставляться в таблицу
        Метод deleteRecords
        • $table — название таблицы из которой будут удаляться данные
        Метод setRecords
        • $what — В этом случает этот параметр передает массив в виде: 'поле'=>'значение', которые будут использоваться с оператором SET
        Метод query

        Рассмотрим эти методы подробнее.

        Метод checkWhat

        Единственным входным параметром этого метода является ассоциативный массив значений, которые либо нужно выбрать с помощью SELECT или нужно обновить с помощью UPDATE.
        Параметр $what описан тут

        Думаю тут все довольно ясно. Проверка на массив, обход всех элементов массива и построение части строки запроса.

        Метод checkJoin

        Параметр $join описан тут. Также предусмотрена ситуация, когда нужно объединить больше двух таблиц, тогда параметр $join может быть представлен в виде array($join1, $join2. $joinN)

        Метод checkWhere

        Метод проверяет наличие параметров для WHERE составляющей запроса. Параметр $where описан тут

        Метод checkLimit

        Генерация предикаты LIMIT запроса. Тут все довольно просто.

        Метод checkOrder

        Генерация предикаты ORDER.

        Вот и все методы, которые нужны для генерации основных частей запроса. Но т.к. мы используем prepare statement в таких частях запроса как WHERE и WHAT, то нам нужно объединить параметры эти частей, чтобы передать в PDO. Для этой задачи я написал еще один метод

        Метод checkParams


        Следующим этапом построения SQL запросов является итоговая генерация sql кода. Для этого я создал 4 метода: prepareSelectSQL, prepareInsertSQL, prepareDeleteSQL, prepareUpdateSQL
        Рассмотрим эти методы подробнее.

        Метод prepareSelectSQL

        Параметры этого метода совпадают с параметрами метода getRecords. Это $what, $where, $limit, $order, $join, $debug. Эти параметры описаны тут

        Метод prepareInsertSQL

        Этот метод проще, т.к. используется ограниченный набор предикат и параметров

        Метод prepareDeleteSQL

        Запрос для удаления записей. Используем название таблицы и набор параметров для предикаты WHERE.

        Метод prepareUpdateSQL

        Генерируем sql запрос для обновления записей в таблице.


        Выше был описан класс DataBase, который отвечает за подключение к базе данных и генерацию DML sql запросов. Ниже приведен полный код этого класса.

        Теперь настало время описать класс модели News. Этот класс реализует все абстрактные методы класса-родителя DataBase и статический метод getObject. Этот метод возвращает экземпляр объекта этого класс. Этот метод был создан для того, чтобы отпала необходимость в создании объекта класса News путем использования ключевого слова new. Вот как это выглядит:

        Каждый метод этого класса вызывает нужный ему генератор sql запроса и передает итоговый запрос и параметры в PDO для выполнения запроса. Ниже приведен полный код класса модели News.


        В принципе на этом можно завершать. Конечно же, можно было еще добавить генерацию предикаты GROUP BY и HAVING, но я решил этого не делать. Думаю, что принцип построения запросов я изложил ясно и проблем с использованием не возникнет. В итоге мы получили механизм построения sql запросов, который не завязан на конкретной структуре таблицы в БД и может применяться к разным типам DML SQL запросов. Если нужно будет, могу создать репозиторий на github.
        Буду рад услышать критику и предложения по улучшению метода.

        Где-то полтора года назад я начал заниматься web разработкой. Начинал с функционального программирования. Примерно пол года назад я перешел на ООП и стал использовать MVC архитектуру проектирования. Недавно появилась задача оптимизировать работу с базой данных, т. к. вся связь и работа с базой осуществлялась через один класс. Это было неудобно потому, что все время приходилось вручную писать SQL — запросы. Задача была разбита на 2 этапа:

        1. Написать класс для подключения к базе данных
        2. Написать класс модели для работы с данными


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

        Метод getRecords
          $what — передает массив полей, которые нужно выбрать из таблицы $where — передает ассоциативный массив ключей в виде array('Поле'=>array('знак','значение')). Это дает нам возможность более гибко использовать предикат WHERE
        • $limit — передает ассоциативный массив ключей в виде array('начальная запись', 'количество записей'). Такая структура дает нам возможность реализовать постраничный вывод или вывод ограниченного количества записей
        • $order — ассоциативный массив array('поле'=>'вид сортировки'). Дает возможность сортировки по любому количеству столбцов. $join — ассоциативный массив array('Тип связи', array('Таблица1', 'Таблица2'), array('Алиас1', 'Алиас2'), array('поле1','поле2')). Тип связи: LEFT, INNER, RIGHT, OUTER; Таблица1 и Таблица2: таблицы между которыми устанавливается связь; Аллиас1 и Аллиас2 — псевдонимы для таблицы; Поле1 и Поле2 — это соотв. PK и FK таблиц
        • $degub — этот параметр нужен для сохранения в свойства класса уже созданного sql запроса, а также параметров, которые нужно если мы используем prepare statement в PDO
        Метод addRecord
        • $data — ассоциативный массив параметров в виде: 'поле'=>'значение', которые будут вставляться в таблицу
        Метод deleteRecords
        • $table — название таблицы из которой будут удаляться данные
        Метод setRecords
        • $what — В этом случает этот параметр передает массив в виде: 'поле'=>'значение', которые будут использоваться с оператором SET
        Метод query

        Рассмотрим эти методы подробнее.

        Метод checkWhat

        Единственным входным параметром этого метода является ассоциативный массив значений, которые либо нужно выбрать с помощью SELECT или нужно обновить с помощью UPDATE.
        Параметр $what описан тут

        Думаю тут все довольно ясно. Проверка на массив, обход всех элементов массива и построение части строки запроса.

        Метод checkJoin

        Параметр $join описан тут. Также предусмотрена ситуация, когда нужно объединить больше двух таблиц, тогда параметр $join может быть представлен в виде array($join1, $join2. $joinN)

        Метод checkWhere

        Метод проверяет наличие параметров для WHERE составляющей запроса. Параметр $where описан тут

        Метод checkLimit

        Генерация предикаты LIMIT запроса. Тут все довольно просто.

        Метод checkOrder

        Генерация предикаты ORDER.

        Вот и все методы, которые нужны для генерации основных частей запроса. Но т.к. мы используем prepare statement в таких частях запроса как WHERE и WHAT, то нам нужно объединить параметры эти частей, чтобы передать в PDO. Для этой задачи я написал еще один метод

        Метод checkParams


        Следующим этапом построения SQL запросов является итоговая генерация sql кода. Для этого я создал 4 метода: prepareSelectSQL, prepareInsertSQL, prepareDeleteSQL, prepareUpdateSQL
        Рассмотрим эти методы подробнее.

        Метод prepareSelectSQL

        Параметры этого метода совпадают с параметрами метода getRecords. Это $what, $where, $limit, $order, $join, $debug. Эти параметры описаны тут

        Метод prepareInsertSQL

        Этот метод проще, т.к. используется ограниченный набор предикат и параметров

        Метод prepareDeleteSQL

        Запрос для удаления записей. Используем название таблицы и набор параметров для предикаты WHERE.

        Метод prepareUpdateSQL

        Генерируем sql запрос для обновления записей в таблице.


        Выше был описан класс DataBase, который отвечает за подключение к базе данных и генерацию DML sql запросов. Ниже приведен полный код этого класса.

        Теперь настало время описать класс модели News. Этот класс реализует все абстрактные методы класса-родителя DataBase и статический метод getObject. Этот метод возвращает экземпляр объекта этого класс. Этот метод был создан для того, чтобы отпала необходимость в создании объекта класса News путем использования ключевого слова new. Вот как это выглядит:

        Каждый метод этого класса вызывает нужный ему генератор sql запроса и передает итоговый запрос и параметры в PDO для выполнения запроса. Ниже приведен полный код класса модели News.


        В принципе на этом можно завершать. Конечно же, можно было еще добавить генерацию предикаты GROUP BY и HAVING, но я решил этого не делать. Думаю, что принцип построения запросов я изложил ясно и проблем с использованием не возникнет. В итоге мы получили механизм построения sql запросов, который не завязан на конкретной структуре таблицы в БД и может применяться к разным типам DML SQL запросов. Если нужно будет, могу создать репозиторий на github.
        Буду рад услышать критику и предложения по улучшению метода.

        В большинстве веб-проектов разработка структуры базы данных (таблицы, связи между ними) а также написание необходимых запросов является одним из важнейших этапов. Для удобства используются различные системы проектирования баз данных. В этой статье будет рассмотрено 3-и системы, описаны их возможности, выпилено сравнение и произведен анализ использования для различных типов задач.
        Сравниваться будут:

        MySQL Workbench

        Официальная система, как для проектирования, так и для администрирования баз данных MySQL. Удобный графический интерфейс позволяет легко создавать структуру базы любой сложности, а наглядность позволит легко ориентироваться.

        image

        Проектирование баз данных
        • Наглядность
        • Простота редактирования
        • Разработка не требует подключения к серверу
        • Недостаточно развит буфер обмена
        • Запросы необходимо писать самому, отсутствует конструктор запросов
        Администрирование баз данных

        Ar-wik Builder

        Онлайн сервис для разработки структуры баз данных и для работы по созданию запросов с использованием конструкторов. Как и в случае с предыдущим инструментом, графический интерфейс позволяет легко манипулировать таблицами, их полями и связями между ними. Также этот сервис позволяет внедрять PHP код непосредственно в запросы, не заботясь о необходимости проверки данных. Все проверки выполняются автоматически.

        image

        Проектирование баз данных
        • Наглядность
        • Простота редактирования
        • Разработка не требует подключения к серверу
        • Удобный конструктор запросов, с возможностью внедрения PHP функций
        • Удобный импорт/экспорт проекта и его частей
        • Возможность обмена проектами с другими пользователями сервиса
        • Хранимые процедуры и триггеры необходимо писать разработчику, для них конструкторы не предусмотрены
        Администрирование баз данных

        phpMyAdmin

        Бесплатный инструмент, как по мне, создан для непосредственного администрирования. Позволяет выполнять не сложные запросы без их написания. Удобно использовать когда удаленный доступ к базе запрещен например хостером, ведь он запускается на сервере.

        image

        Проектирование баз данных

        Использовать этот сервис для проектирования базы очень неудобно, он с самого начала создавался не для этого.

        Администрирование баз данных
        • Возможность запускать непосредственно на сервере, что удобно при хостинге и когда хостер запрещает удаленный доступ к базе

        Следует отметить, что использование подобных сервисов позволяет значительно увеличить скорость разработки, а выбор уже личное дело каждого. Главное есть с чего.

        This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

        Latest commit

        Git stats

        Files

        Failed to load latest commit information.

        README.md

        SQL Query Builder

        Scrutinizer Code Quality
        SensioLabsInsight

        An elegant lightweight and efficient SQL Query Builder with fluid interface SQL syntax supporting bindings and complicated query generation. Works without establishing a connection to the database.

        The recommended way to install the SQL Query Builder is through Composer. Run the following command to install it:

        The SQL Query Builder allows to generate complex SQL queries standard using the SQL-2003 dialect (default) and the MySQL dialect, that extends the SQL-2003 dialect.

        2.1. Generic Builder ↑

        The Generic Query Builder is the default builder for this class and writes standard SQL-2003.

        All column aliases are escaped using the ' sign by default.

        2.2. MySQL Builder ↑

        The MySQL Query Builder has its own class, that inherits from the SQL-2003 builder. All columns will be wrapped with the tilde ` sign.

        All table and column aliases are escaped using the tilde sign by default.

        2.3. Human Readable Output ↑

        Both Generic and MySQL Query Builder can write complex SQL queries.

        Every developer out there needs at some point revising the output of a complicated query, the SQL Query Builder includes a human-friendly output method, and therefore the writeFormatted method is there to aid the developer when need.

        Keep in mind writeFormatted is to be avoided at all cost in production mode as it adds unneeded overhead due to parsing and re-formatting of the generated statement.

        Читайте также: