пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ Linux
Слишком длинный поисковый запрос.
По вашему запросу ничего не найдено :(
Убедитесь, что запрос написан правильно, или посмотрите другие
наши статьи:
Переменные среды (или окружения) используются для хранения общесистемных значений, которые могут использоваться любым пользователем и процессом в операционной системе. Мы уже рассказывали про то как установить переменные окружения в Linux, а теперь расскажем, как это сделать в MacOS.
Проверить текущие переменные среды
Есть два способа проверить текущие переменные среды в MacOS:
Показать список всех текущих переменных среды.
Показать конкретную переменную среды.
Список всех переменных среды
Используйте команду printenv для отображения списка текущих установленных переменных среды:
printenv
Примечание. Если вы хотите отобразить полный список переменных оболочки, используйте команду set.
Проверить конкретную переменную среды
Если вы хотите отобразить значение какой-либо конкретной переменной среды, используйте команду echo:
echo $[имя_переменной]
Например, чтобы проверить значение переменной PATH, в которой хранится список каталогов с исполняемыми файлами, используйте команду echo:
echo $PATH
Примечание. Всегда используйте префикс $ при указании имени переменной.
Установить временную переменную среды
Значение, которое вы присваиваете временной переменной среды, сохраняется только до тех пор, пока вы не закроете сеанс терминала. Это полезно для так переменных, которые нужно использовать только для текущего сеанса.
Назначить временную переменную среды с помощью команды export:
export [имя_переменной]=[значение_переменной]
Где:
[имя_переменной]: имя новой временной переменной среды, которую вы хотите установить.
[значение_переменной]: значение, которое вы хотите присвоить новой переменной.
Команда export также позволяет добавлять новые значения к существующим переменным:
export [имя_существующей_переменной]=[новое_значение_переменной]:$[имя_существующей_переменной]
Где:
[имя_существующей_переменной]: имя переменной среды, к которой вы хотите добавить новое значение.
[новое_значение_переменной]: значение, которое вы хотите добавить к существующей переменной.
Например, если вы хотите добавить собственный путь к папке в переменную PATH, используйте следующую команду:
export PATH=/Users/test/test_folder:$PATH
Установить постоянную переменную среды
В файл .bash_profile добавляются постоянные переменные среды:
Найдите путь к файлу .bash_profile, используя: ~/.bash-profile
Откройте файл .bash_profile в любом текстовом редакторе.
Прокрутите до конца файла
Используйте команду export, чтобы добавить новые переменные: export [имя_переменной]=[значение_переменной]
Сохраните все изменения, внесенные вами в файл
Запустите новый .bash_profile, перезапустив окно терминала, либо используя команду: source ~/.bash-profile
Удалить переменную среды
Используйте команду unset, чтобы удалить переменную среды:
unset [имя_переменной]
С чего начинается Linux? LPI (Linux Professional Institute) считает, что изучение необходимо начинать с темы “Обнаружение и настройка комплектующих”. Это работа с “железом”, работа с комплектующими, вся аппаратная часть, то что мы видим и настраиваем. На сайте LPI (www.lpi.org) мы можем найти, что должен знать обучающийся Linux.
Включение и отключение встроенного “железа”
Настройка системы с помощью или без помощи внешних устройств.
Разница между устройствами хранения информации
Разница между устройствами, поддерживающими “Горячую замену”
Выделение аппаратных ресурсов для устройств
Инструменты и утилиты для просмотра списка оборудования
Инструменты и утилиты для работы с USB
Разбор понятий sysfs, udev, dbus.
Далее возьмем для простоты Ubuntu 20.04.
Директория /sys – тут содержится вся информация о подключенных устройствах. В данную директорию монтируется файловая система sysfs. Sysfs — виртуальная файловая система в операционной системе Linux. Экспортирует в пространство пользователя информацию ядра Linux о присутствующих в системе устройствах и драйверах. В данной директории есть определенных набор основных папок:
devices/ - все устройства ядра
bus/ - перечень шин зарегистрированных в ядре. Шина - это общий путь, по которому информация передается от одного компонента к другому
drivers/ - каталог драйверов
block/ - каталог блочных устройств. В данном случае под устройством понимается совокупность физического устройства и драйвера. То есть, если при подключении USB-драйва некоторое новое устройство в /sys/devices/ появится всегда (можно говорить о наличии физического устройства), то появление каталога /sys/block/sda зависит ещё и от наличия в памяти необходимых драйверов (usb-storage, sd_mod и т.д. - включая все драйвера, необходимые для поддержки usb)
class/ - группировка устройств по классам
Навигацию по папкам осуществляем с помощью команды cd. Учитывая вложенность папок переход на уровень вверх, т.е в родительскую папку используем cd .., где двоеточие обозначает родительский каталог. А также переход в любую папку, например, cd /sys/bus. Чтобы посмотреть все что находится в каталоге используем команду ls
Следующий момент, если мы зайдем в папку с устройствами, то мы можем увидеть, как ОС наша видит устройства.
Не очень удобно. Чтобы удобнее было работать с устройствами, используется udev. Это менеджер устройств, который позволяет ОС предоставлять устройства в удобно используемом виде, чтобы было понятно нам.
Далее папка /proc - она находится в корне нашей ОС и содержит информацию о всех запущенных процессах. Она создается в оперативной памяти при загрузке ПК. Количество файлов зависит от конфигурации данной системы. Для работы с файлами необходимы права суперпользователя.
Внесенные изменения сохраняются только до конца сеанса. Cеанс - это каждая терминальная оболочка, запущенная процессами пользователя
В данную папку монтируется виртуальная система procfs. В ней находится информация о состоянии ядра и вообще операционной системе в целом. Термин виртуальная система - это некая абстракция, которая позволяет философии Linux говорить, что “все является файлом”, а вообще если рассматривать понятие файловая система - это иерархическое хранилище данных, которые собраны в соответствии с определенной структурой.
Вот так выглядит данная папка.
Мы можем посмотреть всю информацию, которая нам известна о процессоре. Данная информация содержится в файле cpuinfo. Для вывода информации содержащейся в файле используем команду cat [имя_файла]. Результат работы команды cat cpuinfo ниже.
Есть еще интересный файл mounts. Он показывает все смонтированные файловые системы.
Результат вывода будет примерно такой.
Можно увидеть, когда мы просматриваем содержимое каталога командой ls, то файлы подсвечиваются белым цветом, а каталоги синим.
Переходим немного глубже по дереву каталогов файловой системы cd /prox/sys в данной папке все о настройках и процессах, происходящих с нашей текущей файловой системой. В данной директории есть несколько подпапок.
И зайдем в подпапку, относящуюся к файловой системе fs.
Посмотрим например file-max в данном файле информация о том сколько файлов одновременно может открыть пользователь.
В последней версии число таких фалов увеличилось. До версии 20.04, число файлов было по умолчанию 204394. Можно изменить число или данные, например, с помощью команды echo 10000000000 > file-max
Все изменения, которые мы делаем в данной директории они сохраняются только до перезагрузки. Это надо учитывать.
Еще одна основная папка в корневой директории папка /dev – она в себе содержит интерфейсы работы с драйверами ядра.
/dev/sd [буква] - жесткий диск (в системах на ядре Linux)
/dev/sd [буква][номер] – раздел диска
/dev/sr [номер] (/dev/scd [номер]) – CD-ROM
/dev/eth [номер] – cетевой интерфейс Ethernet
/dev/wlan [номер] – cетевой интерфейс Wireless
/dev/lp [номер] – принтер
/dev/video [номер] - устройство изображений, камеры, фотоаппараты
/dev/bus/usb/001/[номер] – устройство номер на шине USB
/dev/dsp – звуковой вывод
Набор оборудования, команды вывода перечня устройств.
Lsmod – информация о модуле ядра
Lspci - информация об устройствах PCI
Lspcmcia - информация об устройствах PCMCIA
Lsusb - информация о шине USB
Lshw – детальная информация о комплектующих.
Команда Lsmod – утилита которая показывает нам модули ядра. Модуль ядра — это объект, который содержит код позволяющий расширить функционал ядра. Вот так выглядит ее вывод.
По сути, если проводить аналогию с ОС Windows это драйвера.
Вывод команды lshw
Данная команда сканирует все устройства и выводит подробную информацию по ним и достаточно детально.
Утилиты для работы с модулем ядра или утилиты управления модулями ядра.
Lsmod – информация о модулях ядра
Modinfo - информация о конкретном модуле
Rmmod - удаление модуля ядра
Insmod – установка модуля ядра
Modprobe – деликатное удаление или добавление модуля ядра
Фактически эти команды используются для добавления и удаления “драйверов” устройств в Linux системе. В большинстве случаев ОС самостоятельно подключит устройство, но бывает такое, что устройство не стандартное и требуется добавить модель, для того чтобы ядро ОС, корректно работало с данным устройством.
Rmmod и insmod - команды грубые и не умеют работать с зависимостями, поэтому необходимо использовать Modprobe с различными ключами.
Взаимодействие с CPU, основные понятия:
IRQ - механизм прерываний
IO адреса – обмен информацией между устройствами и CPU
DMA – обращение к ОЗУ минуя CPU
Выделение ресурсов. IRQ - механизм прерываний это система которая сообщает центральному процессору о наступлении какого либо события, на которое процессор должен отреагировать. Есть определенные адреса прерываний, их можно увидеть в биосе ПК. Есть стандартные номера прерываний. Ранее была необходимость при конфликте устройств назначать в ручном режиме данные прерывания, в настоящее время с появлением технологии Plug and Play, данная потребность исчезла.
IO адреса – это область памяти в которой процессор считывает информацию об устройствах и туда же ее записывает. Это выделенный диапазон. Вообще она бывает в памяти и адресация по портам.
DMA - технология появилась относительно недавно и позволяет устройствам обращаться к памяти минуя процессор. Существенно повышает быстродействие.
Все технологии настраиваются автоматически.
Устройства хранения:
PATA – параллельный интерфейс
SATA - последовательный интерфейс
SCSI - стандарт передачи данных
SAS – замена SCSI
Современные SATA, SAS нужно понимать есть устройства поддерживающие горячую замену и устройства не поддерживающие горячую замену.
Устройства, которые можно выдернуть из ПК, безболезненно, и это не обрушит систему, причем ОС не подвиснет, не перезагрузится, это устройства поддерживающие горячую замену, например, USB.
Устройство, которое не поддерживает горячую замену, например, оперативная память. Если мы ее выдернем из материнской платы, ОС однозначно обрушится.
Команда blkid показывает какие устройства у нас смонтированы.
Нужно отметить, что у каждого устройства есть уникальный UUID (универсальный уникальный идентификатор), что udev умеет читать UUID, и он монтирует в понятном виде нам.
С помощью PHP можно относительно легко создать веб-приложение, и это является одной из причин, по которой он так популярен. Однако, несмотря на простоту его использования, PHP стал довольно навороченным языком с большим количеством различных нюансов и тонкостей, которые могут усложнить процесс отладки для разработчиков так, что они волосы начнут на себе рвать. В этой статье я выделил десять самых распространенных ошибок, которых PHP-разработчикам следует избегать.
Ошибка №1. Оставлять висячие ссылки на массивы после циклов foreach
Не знаете точно, как использовать циклы foreach в PHP? Если вы хотите работать с каждым элементом итерируемого массива в отдельности, использование ссылок в циклах foreach может оказаться полезным.
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8) { // $arr – это теперь array(2, 4, 6, 8) }
Проблема в том, что, если вы не будете достаточно осторожны, это может привести к некоторым нежелательным побочным эффектам и последствиям. В частности, если мы посмотрим на пример выше, то увидим, что после того, как код выполнится, $value останется в области видимости и будет хранить ссылку на последний элемент массива. Таким образом, если вы будете использовать $value и дальше, то можете случайно изменить последний элемент массива.
Главное запомните, что у foreach нет области видимости. А значит, $value в приведенном выше примере является ссылкой на верхнюю часть сценария. На каждой итерации foreach задает ссылку, которая указывает на следующий элемент массива $array. Так что, после того, как цикл закончит свою работу, $value так и будет указывать на последний элемент $array и останется в области видимости.
Ниже приведен пример ошибок, к которым это может привести. Такие ошибки сбивают с толку и их довольно трудно обнаружить.
$array = [1, 2, 3];
echo implode(',', $array), "\n";
foreach ($array as &$value) {} // by reference { // по ссылке }
echo implode(',', $array), "\n";
foreach ($array as $value) {} // by value (i.e., copy) { // по значению (то есть путем копирования) }
echo implode(',', $array), "\n";
Приведенный выше код выдаст следующий результат:
1,2,3
1,2,3
1,2,2
Нет, это не опечатка. Последнее значение в последней строке и правда равно 2, а не 3.
Почему же?
После того, как мы прошли первый цикл foreach, $array остается таким же, но, как мы уже говорили выше, $value остается в качестве висячей ссылки на последний элемент $array (так как цикл foreach обращался к $value по ссылке).
В результате, когда мы проходим по второму циклу foreach, начинают происходить всякие «странные вещи». В частности, так как теперь мы осуществляем доступ к $value по значению (то есть путем копирования), foreach на каждом шаге цикла копирует каждый последующий элемент массива $array в $value. Таким образом, на каждом шаге второго цикла происходит следующее:
Проход 1: $array[0] (т.е. «1») копируется в $value (которое является ссылкой на $array[2]), поэтому теперь $array[2] равен 1. Так что, теперь $array содержит [1, 2, 1].
Проход 2: $array[1] (т.е. «2») копируется в $value (которое является ссылкой на $array[2]), поэтому теперь $array[2] равен 2. Так что, теперь $array содержит [1, 2, 2].
Проход 3: $array[2] (которое теперь равно «2») копируется в $value (которое является ссылкой на $array[2]), так что $array[2] так и остается равным 2. Поэтому теперь $array содержит [1, 2, 2].
Для того, чтобы быть иметь возможность использовать преимущества ссылок в циклах foreach и при этом быть уверенным в том, что подобных проблем возникать не будет, вызовите функцию unset() для переменной сразу после цикла foreach, чтобы удалить ссылку. Например,
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
unset($value); // $value no longer references $arr[3] { // $value уже не является ссылкой на $arr[3] }
Ошибка №2. Непонимание того, как ведет себя функция isset()
Несмотря на свое название, функция
isset()
возвращает значение false не только для несуществующих элементов, но и для нулевых значений (null).
Такое поведение функции может привести к куда более серьезным проблемам, чем кажется на первый взгляд. Именно оно и является довольно-таки распространенным источником проблем.
Давайте посмотрим на следующий фрагмент кода:
$data = fetchRecordFromStorage($storage, $identifier);
if (!isset($data['keyShouldBeSet']) {
// do something here if 'keyShouldBeSet' is not set
{ // если 'keyShouldBeSet' не существует, выполняются действия }
}
Тот, кто писал этот код, судя по всему, хотел проверить, существует ли keyShouldBeSet в $data. Но, как мы уже говорили ранее, функция isset($data['keyShouldBeSet']) вернет false, даже если $data['keyShouldBeSet'] существует, но имеет значение null. Так что, логика, приведенная выше, неверна.
А вот другой пример:
if ($_POST['active']) {
$postData = extractSomething($_POST);
}
// ...
if (!isset($postData)) {
echo 'post not active';
}
Код выше предполагает, что, если $_POST['active'] возвращает true, то postData обязательно существует, а, значит, isset($postData) вернет true. И наоборот, если isset($postData) возвращает false, то и $_POST['active'] возвращает false.
Но это не так.
Как мы уже говорили, isset($postData) может также вернуть false, если $postData имеет значение null. Таким образом, функция isset($postData) может вернуть false, даже если $_POST['active'] вернул true. И опять получается, что логика, приведенная выше, неверна.
К слову, если цель приведенного выше кода на самом деле заключалась в том, чтобы еще раз проверить, возвращает ли $_POST['active'] значение true, прибегнуть к помощи функции isset() было не самым хорошим решением. Вместо этого было бы гораздо лучше просто перепроверить $_POST['active'], то есть:
if ($_POST['active']) {
$postData = extractSomething($_POST);
}
// ...
if ($_POST['active']) {
echo 'post not active';
}
Правда, говоря о случаях, когда нам нужно проверить, существует ли переменная на самом деле (то есть отличить переменную, которая не существует, от переменной, значение которой равно null), гораздо более надежным решением будет использование метода array_key_exists().
Например, вы могли бы переписать первый пример следующим образом:
$data = fetchRecordFromStorage($storage, $identifier);
if (! array_key_exists('keyShouldBeSet', $data)) {
// do this if 'keyShouldBeSet' isn't set
{ // если ‘keyShouldBeSet’ не существует, то выполняются действия, указанные здесь }
}
Более того, вы можете проверить, существует ли переменная в текущей области видимости. Для этого вам нужно объединить методы array_key_exists() и get_defined_vars():
if (array_key_exists('varShouldBeSet', get_defined_vars())) {
// variable $varShouldBeSet exists in current scope
{ // переменная $varShouldBeSet существует в текущей области видимости }
}
Ошибка №3. Путаница с возвратом по ссылке и по значению
Давайте посмотрим на следующий фрагмент кода:
class Config
{
private $values = [];
public function getValues() {
return $this->values;
}
}
$config = new Config();
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];
Если вы запустите этот код, то получите следующий результат:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
Что же не так?
Проблема в том, что в коде, который мы привели выше, произошла путаница с возвратом массива по ссылке и по значению. Если вы явно не укажите PHP, чтобы он вернул массив по ссылке (то есть с помощью &), то он по умолчанию вернет массив «по значению». Это значит, что будет возвращена копия массива, и, как следствие, вызываемая функция и вызывающая сторона будут обращаться к разным экземплярам массива.
Так что, вызываемая выше функция getValues() возвращает копию массива $values, а не ссылку на него. Помня об этом, вернемся к двум ключевым строкам из примера выше:
// getValues() returns a COPY of the $values array, so this adds a 'test' element
// to a COPY of the $values array, but not to the $values array itself.
{ // функция getValues() возвращает КОПИЮ массива $values, а значит, что элемент test добавляется в КОПИЮ массива $values, в не в сам массив. }
$config->getValues()['test'] = 'test';
// getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't
// contain a 'test' element (which is why we get the "undefined index" message).
{ // функция getValues() снова возвращает ДРУГУЮ КОПИЮ массива $values, и в ЭТОЙ копии нет элемента test (собственно говоря, почему мы и получаем сообщение о неопределенном индексе) }
echo $config->getValues()['test'];
Одно из возможных решений этой проблемы заключается в следующем: вы можете сохранить первую копию массива $values, которую возвращает функция getValues(), и дальше работать с этой копией. Например,
$vals = $config->getValues();
$vals['test'] = 'test';
echo $vals['test'];
Этот код сработает отлично, то есть он выведет элемент test, не выдавая сообщения о неопределенном индексе. Однако такой подход может оказаться непригодным; это зависит от ваших целей. В частности, с помощью этого кода вы не сможете изменить исходный массив $values. Так что, если вы хотите, чтобы изменения (например, добавление элемента test) касались также исходного массива, вам нужно изменить функцию getValues() так, чтобы она возвращала ссылку на сам массив $values. Вы можете это сделать, добавив & перед именем функции, показывая таким образом, что она должна вернуть ссылку:
class Config
{
private $values = [];
// return a REFERENCE to the actual $values array
{ // возвращает ССЫЛКУ на фактический массив $values }
public function &getValues() {
return $this->values;
}
}
$config = new Config();
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];
Как мы и хотели, результатом будет test.
Но давайте запутаем ситуацию еще больше. Рассмотрим следующий код:
class Config
{
private $values;
// using ArrayObject rather than array
{ // используем ArrayObject вместо массива }
public function __construct() {
$this->values = new ArrayObject();
}
public function getValues() {
return $this->values;
}
}
$config = new Config();
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];
Если вы считаете, что здесь мы получим ту же ошибку с неопределенным индексом (что и в предыдущем примере), то вы ошибаетесь. На самом деле, этот код будет отлично работать. Это связано с тем, что PHP всегда передает объекты по ссылке (в отличие от массивов). (ArrayObject – это объект SPL, который полностью имитирует использование массивов, но при этом работает как объект.)
Как вы могли заметить, в PHP не всегда можно сразу понять, работаете вы с копией или с ссылкой. Поэтому важно знать, как ведут себя те или иные объекты по умолчанию (т.е. то, что переменные и массивы передаются по значению, а объекты – по ссылке). Кроме того, стоит хорошо изучить документацию API для функции, которую вы вызываете, чтобы понимать, что она возвращает (значение, копию массива, ссылку на массив или ссылку на объект).
При этом стоит отметить, что возвращение ссылок на массив или ArrayObject, как правило, считается не самой лучшей практикой, так как вызывающая сторона может изменить частные данные экземпляра. Это противоречит понятию инкапсуляции. Вместо этого лучше использовать старомодные геттеры и сеттеры. Например,
class Config
{
private $values = [];
public function setValue($key, $value) {
$this->values[$key] = $value;
}
public function getValue($key) {
return $this->values[$key];
}
}
$config = new Config();
$config->setValue('testKey', 'testValue');
echo $config->getValue('testKey'); // echos 'testValue'
При таком подходе вызывающая сторона может задать или получить любое значение массива, не предоставляя общий доступ к самому массиву $values, который скрыт для других.
Ошибка №4. Выполнение запросов внутри цикла
Нередко можно столкнуться с чем-то подобным, что приводит к тому, что ваш PHP-код не работает:
$models = [];
foreach ($inputValues as $inputValue) {
$models[] = $valueRepository->findByValue($inputValue);
}
По сути, здесь все верно, но если вы проследите за логикой кода, то можете обнаружить, что безвредный на первый взгляд вызов $valueRepository->findByValue() в итоге приводит к появлению некоторого запроса:
$result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);
В результате, с каждой итерацией будет выполняться запрос к базе данных. Поэтому, если вы передадите в цикл массив из 1000 элементов, то он создаст 1000 отдельных запросов к базе данных! А если этот сценарий будет вызываться в нескольких потоках, то в теории это может привести к полной остановке системы.
Именно поэтому очень важно уметь распознавать, когда ваш код создает запросы, и по возможности собирать эти значения, чтобы выполнить один запрос, который вернет все результаты.
Существуют довольно распространенные ситуации, когда запросы выполняются неэффективно (т.е. в цикле). Например, когда отправляется форма со списком значений (например, идентификаторов). После чего код будет перебирать массив в цикле и выполнять отдельный SQL-запрос для каждого идентификатора, чтобы получить полные данные записи для каждого из них. В большинстве случаев это будет выглядеть следующим образом:
$data = [];
foreach ($ids as $id) {
$result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id);
$data[] = $result->fetch_row();
}
Но то же самое можно выполнить гораздо более эффективным способом. Вам понадобиться всего один SQL-запрос:
$data = [];
if (count($ids)) {
$result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids));
while ($row = $result->fetch_row()) {
$data[] = $row;
}
}
Таким образом, очень важно уметь распознавать, выполняет ваш код запросы напрямую или нет. По возможности соберите значения, а потом, чтобы получить все результаты, выполните единый запрос. И все же даже здесь следует быть осторожным. И здесь мы плавно переходим к следующей распространенной ошибке…
Ошибка №5. Ложное и нерациональное использование памяти
Несмотря на то, что извлечение множества записей за раз куда как более эффективно, чем выполнение запросов для каждой строки, которую вы хотите извлечь, если вы используете расширение PHP mysql и библиотеку libmysqlclient, то такой подход в теории может привести к ситуации под названием «недостаточно памяти».
Чтобы понять, о чем я говорю, давайте посмотрим на пример с ограниченными ресурсами (512 Мб оперативной памяти), MySQL и php-cli.
Давайте загрузим таблицу базы данных:
// connect to mysql { // подключаемся к MySQL }
$connection = new mysqli('localhost', 'username', 'password', 'database');
// create table of 400 columns { // создаем таблицу из 400 столбцов }
$query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT';
for ($col = 0; $col < 400; $col++) {
$query .= ", `col$col` CHAR(10) NOT NULL";
}
$query .= ');';
$connection->query($query);
// write 2 million rows { // добавляем 2 миллиона строк }
for ($row = 0; $row < 2000000; $row++) {
$query = "INSERT INTO `test` VALUES ($row";
for ($col = 0; $col < 400; $col++) {
$query .= ', ' . mt_rand(1000000000, 9999999999);
}
$query .= ')';
$connection->query($query);
}
Окей, а теперь давайте проверим, как используются ресурсы:
// connect to mysql { // подключаемся к MySQL }
$connection = new mysqli('localhost', 'username', 'password', 'database');
echo "Before: " . memory_get_peak_usage() . "\n";
$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1');
echo "Limit 1: " . memory_get_peak_usage() . "\n";
$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000');
echo "Limit 10000: " . memory_get_peak_usage() . "\n";
Вот так выглядит результат:
Before: 224704
Limit 1: 224704
Limit 10000: 224704
Отлично. Выглядит так, как будто с точки зрения ресурсов запрос благополучно управляется внутри.
Но, чтобы убедиться наверняка, давайте увеличим значение LIMIT до 100 000. Опа, и вот что мы получим:
PHP Warning: mysqli::query(): (HY000/2013):
Lost connection to MySQL server during query in /root/test.php on line 11
Что же произошло?
Здесь проблема заключается в том, как работает модуль PHP mysql. По сути это просто прокси для libmysqlclient, который выполняет всю грязную работу. Когда часть данных уже выбрана, они попадаю непосредственно в память. А так как эта память никак не управляется менеджером PHP, функция memory_get_peak_usage() не сообщит нам о том, что количество использованных ресурсов возросло, так как мы увеличиваем LIMIT в нашем запросе. Это приводит к проблемам, о которых мы говорили выше. Получается, что нас обманным путем пытаются уверить, что с нашим управлением памятью все в порядке. Но по сути наше управление памятью далеко не идеально, и мы можем столкнуться с проблемами (такими как мы описывали выше).
Во всяком случае, вы можете избежать ситуаций с ложным использованием памяти, описанных выше. Для этого вам нужно использовать модуль mysqlnd. Хотя сам по себе он вам никак не поможет оптимизировать использование вашей памяти. Этот модуль скомпилирован как собственное расширение PHP, и он использует диспетчер памяти PHP.
Таким образом, если мы запустим тестовый код, приведенный выше, и вместо mysql воспользуемся mysqlnd, то получим гораздо более реалистичную картину использования нашей памяти:
Before: 232048
Limit 1: 324952
Limit 10000: 32572912
И кстати, она будет куда хуже. Если обратиться к документации PHP, то там можно найти, что mysql использует в два раза больше ресурсов для хранения данных, чем mysqlnd. Именно поэтому исходный сценарий, в котором мы использовали mysql, на самом деле использовал даже больше памяти, чем продемонстрировано здесь (примерно в два раза больше).
Дабы избежать такого рода проблем, попробуйте ограничить размер ваших запросов или использовать цикл с меньшим количеством итераций. Например,
$totalNumberToFetch = 10000;
$portionSize = 100;
for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) {
$limitFrom = $portionSize * $i;
$res = $connection->query(
"SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize");
}
Если мы рассуждаем об этой ошибке и ошибке №4, то должны понимать, что необходимо найти золотую середину между слишком детализированными и повторяющимися запросами и огромными штучными запросами. Как и почти во всем в этой жизни, необходим баланс; любая крайность может навредить корректной работе PHP.
Ошибка №6. Игнорирование проблем с Unicode/UTF-8
В некотором смысле это скорее проблема самого PHP, нежели что-то, с чем вы столкнетесь при отладке кода. Однако эту проблему так толком и не решили. Ядро PHP 6 должно было поддерживать Unicode, но, так как разработка PHP 6 была приостановлена в 2010 году, с этим пришлось повременить.
Но это вовсе не значит, что разработчики могут игнорировать правильную обработку UTF-8 и думать, что все строки обязательно будут «старым добрым ASCII». Код, который не может правильно обработать не-ASCII строки, печально известен внедрением опасных гейзенбагов. Даже простые вызовы strlen($_POST['name']) могут вызвать проблемы, если некто с фамилией Schr?dinger захочет зарегистрироваться в вашей системе.
Вашему внимаю предоставляю небольшой чек-лист, который поможет вам избежать таких проблем в вашем коде:
Если вы мало что знаете о Unicode и UTF-8, то изучите, по крайней мере, основы.
Вместо старых строковых функций используйте функции mb_* (убедитесь, что в вашей сборке PHP есть расширение multibyte).
Убедитесь, что ваша база данных и таблицы настроены таким образом, что могут использовать Unicode (многие сборки MySQL все еще используют по умолчанию latin1).
Помните, что json_encode() преобразует не-ASCII символы (например, Schr?dinger преобразуется в Schr\u00f6dinger), а serialize() - нет.
Убедитесь, что ваши файлы с PHP-кодом имеют кодировку UTF-8. Это необходимо, чтобы избежать конфликтов при объединении строк с жестко запрограммированными или сконфигурированными строковыми постоянными.
Ошибка №7. Предполагать, что $_POST всегда будет содержать ваши данные POST-запроса
Несмотря на свое название, массив $_POST не всегда будет содержать ваши данные POST-запроса. Он и вовсе может оказаться пустым. Чтобы разобраться в этом, давайте рассмотрим пример. Допустим, что мы делаем запрос к серверу, вызвав jQuery.ajax():
// js
$.ajax({
url: 'http://my.site/some/path',
method: 'post',
data: JSON.stringify({a: 'a', b: 'b'}),
contentType: 'application/json'
});
(К слову, обратите внимание, что здесь есть contentType: 'application/json'. Мы отправляем данные в формате JSON, что довольно популярно для API. Это стандартная практика, например, при добавлении службы $http в AngularJS.)
Мы просто распечатываем массив $_POST на серверной стороне:
// php
var_dump($_POST);
Сюрприз! А вот и результат:
array(0) { }
Но почему? Что случилось со строкой JSON {a: 'a', b: 'b'}?
Ответ таков: PHP анализирует полезную нагрузку POST-запроса автоматически только в том случае, если она имеет тип содержимого application/x-www-form-urlencoded или multipart/form-data. Причины такого поведения исторически сложившиеся – много лег назад, когда $_POST был только реализован в PHP, это были единственные типы содержимого. Таким образом, PHP не загружает полезную нагрузку POST-запроса автоматически ни для каких других типов содержимого (даже для тех, которые считаются довольно популярными на сегодняшний день, например, application/json).
Так как $_POST - это суперглобальная переменная, то переопределив ее один раз (желательно в самом начале нашего сценария), мы сможем пользоваться этим измененным значением (т.е. в том числе и полезной нагрузкой POST-запроса) во всем нашем коде. Это очень важно, так как фреймворки PHP и почти все пользовательские сценарии используют $_POST для того, чтобы извлекать и преобразовывать данные запроса.
Поэтому, если мы, например, обрабатываем полезную нагрузку POST-запроса с типом содержимого application/json, то мы должны вручную проанализировать содержимое запроса (т.е. декодировать данные JSON) и переопределить переменную $_POST:
// php
$_POST = json_decode(file_get_contents('php://input'), true);
После чего, когда мы распечатаем массив, мы увидим, что он должным образом содержит полезную нагрузку POST-запроса:
array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }
Ошибка №8. Думать о том, что PHP поддерживает символьный тип данных
Посмотрите на следующий фрагмент кода и попробуйте догадаться, что он выведет:
for ($c = 'a'; $c <= 'z'; $c++) {
echo $c . "\n";
}
Если вы ответили, что он выведет буквы от «a» до «z», то, вы будете удивлены – это не так.
Да, он выведет буквы от «а» до «z», но потом он также выведет сочетания букв от «аа» до «yz». Давайте разберемся, почему так происходит.
В PHP нет такого типа данных как char; есть только тип string. Принимая это в расчет, мы должны понимать, что, увеличив строку z, PHP выдаст aa:
php> $c = 'z'; echo ++$c . "\n";
aa
Впрочем, запутаю вас еще больше: с лексикографической точки зрения aa меньше, чем z:
php> var_export((boolean)('aa' < 'z')) . "\n";
true
Именно поэтому пример кода, который мы привели в начале, сначала напечатает буквы от a до z, а потом также напечатает сочетания букв от aa до yz. Он остановится, когда достигнет значения za – первого значения, которое «больше», чем z:
php> var_export((boolean)('za' < 'z')) . "\n";
false
А раз так, то вот один из способов, как можно правильно перебрать в PHP значения от «а» до «z»:
for ($i = ord('a'); $i <= ord('z'); $i++) {
echo chr($i) . "\n";
}
Или вот так:
$letters = range('a', 'z');
for ($i = 0; $i < count($letters); $i++) {
echo $letters[$i] . "\n";
}
Ошибка №9. Игнорирование стандартов написания кода
И хотя игнорирование стандартов написания кода не приводит сразу к необходимости отлаживать PHP-код, это все же, пожалуй, одна из самых важных тем, которую стоит здесь обсудить.
Если вы будете игнорировать эти стандарты, то можете столкнуться с целым рядом проблем в своем проекте. В самом лучшем случае это приведет к тому, что ваш код будет непоследовательным (так как каждый разработчик «занимается своим делом»). А в худшем случае вы получите PHP-код, который не будет работать или будет слишком сложен для перемещения по нему (в некоторых случаях это может быть практически невозможно), что, соответственно, приводит к тому, что его будет крайне сложно отлаживать, совершенствовать и сопровождать. А это значит, что продуктивность вашей команды снизится, а все из-за того, что вы будете тратить свои силы впустую.
К счастью для PHP-разработчиков, существуют Рекомендации по стандартам PHP (PSR - PHP Standards Recommendation). В ней есть пять стандартов:
PSR-0: Стандарт автозагрузки
PSR-1: Базовый стандарт оформления кода
PSR-2: Рекомендации по оформлению кода
PSR-3: Интерфейс протоколирования
PSR-4: Улучшенная автозагрузка
Изначально PSR был создан на базе материалов, полученных от лиц, сопровождающих наиболее узнаваемые на рынке платформы. Свой вклад внесли Zend, Drupal, Symfony, Joomla и прочие, и теперь они придерживаются этих стандартов. И даже PEAR, который много лет назад делал попытку стать стандартом, теперь является частью PSR.
В некоторым смысле практически неважно, какой стандарт вы используете, если вы принимаете его «условия игры» и придерживаетесь его. Однако, лучше всего придерживаться именно PSR, если у вас нет каких-то веских причин этого не делать. Все больше и больше команд и проектов соответствуют требованиям PSR. На данный момент большая часть разработчиков PHP абсолютно точно признала его Стандартом, поэтому, используя его, вы можете быть уверены, что новым разработчикам, которые будут присоединяться к вашей команде, этот стандарт будет знаком, и они будут чувствовать себя комфортно.
Ошибка №10. Неправильное использование метода empty()
Некоторые разработчики PHP очень любят использовать метод empty() для логических проверок практически всего. Однако бывают моменты, когда это может привести к путанице.
Для начала давайте вернемся к массивам и экземплярам ArrayObject (которые имитируют массивы). Учитывая их сходство, можно предположить, что массивы и экземпляры ArrayObject будут вести себя одинаково. Но это далеко не так. Например, вот что происходит в PHP 5.0:
// PHP 5.0 or later: { // PHP 5.0 и более поздние версии: }
$array = [];
var_dump(empty($array)); // outputs bool(true) { // выводит bool(true) }
$array = new ArrayObject();
var_dump(empty($array)); // outputs bool(false) { // выводит bool(false) }
// why don't these both produce the same output?
{ // почему мы получаем разный результат? }
А знаете, что еще хуже? Для более ранних версий PHP (до 5.0) результаты были бы другими:
// Prior to PHP 5.0: { // версии, предшествующие PHP 5.0: }
$array = [];
var_dump(empty($array)); // outputs bool(false) { // выводит bool(false) }
$array = new ArrayObject();
var_dump(empty($array)); // outputs bool(false) { // выводит bool(false) }
Как ни печально, но такой подход довольно популярен. Например, как предполагает документация, Zend\Db\TableGateway из Zend Framework 2 именно таким образом возвращает данные при вызове current() для результата TableGateway::select(). Разработчик может легко допустить ошибку, используя такие данные.
Для того, чтобы избежать таких проблем, лучше всего для проверки пустых массивов использовать функцию count():
// Note that this work in ALL versions of PHP (both pre and post 5.0):
{ // Обратите внимание, что это работает во ВСЕХ версиях PHP (и тех, что были до 5.0, и тех, что были после 5.0) }
$array = [];
var_dump(count($array)); // outputs int(0) { // выводит int(0) }
$array = new ArrayObject();
var_dump(count($array)); // outputs int(0) { // выводит int(0) }
И к слову, так как PHP преобразует 0 в false, функцию count() можно использовать для проверки пустых массивов в условной конструкции if (). Также стоит отметить, что эта функция имеет постоянную сложность (то есть O(1)), что еще больше доказывает тот факт, что это правильный выбор.
Есть еще один пример, когда метод empty() может быть опасной. Речь идет о ее совместном использовании с функцией класса Magic – функцией __get(). Давайте определим два класса и свойство test в каждом из них.
Для начала давайте определим класс Regular, у которого свойство test - это обычное свойство:
class Regular
{
public $test = 'value';
}
А теперь давайте определим класс Magic, который для доступа к свойству test использует оператор __get():
class Magic
{
private $values = ['test' => 'value'];
public function __get($key)
{
if (isset($this->values[$key])) {
return $this->values[$key];
}
}
}
Окей, а теперь давайте посмотрим, что будет происходить, когда мы попытаемся получить доступ к свойству test каждого из этих классов:
$regular = new Regular();
var_dump($regular->test); // outputs string(4) "value" { // выводит string(4) “value” }
$magic = new Magic();
var_dump($magic->test); // outputs string(4) "value" { // выводит string(4) “value” }
Пока все хорошо.
А теперь давайте посмотрим, что будет происходить, когда вы вызовем метод empty() для каждого из эти классов:
var_dump(empty($regular->test)); // outputs bool(false) { // выводит bool(false) }
var_dump(empty($magic->test)); // outputs bool(true) { // выводит bool(true) }
Ах ты! Так что, если мы прибегаем к помощи метода empty(), то мы можем прийти в неверным выводам, полагая, что свойство test ($magic) пусто, хотя на самом деле у него есть значение - 'value'.
Увы, но если класс для получения значения свойства использует функцию __get(), то нет какого-то беспроигрышного способа проверить, является ли свойство пустым или нет. Если вы находитесь вне области видимости класса, то вы сможете лишь проверить, вернется ли нулевое значение (null), и это не обязательно значит, что соответствующее свойство не существует, ему просто могли присвоить значение null.
Для сравнения, если мы попытаемся обратиться к несуществующему свойству экземпляра класса Regular, то вы получим вот такое уведомление:
Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10
Call Stack:
0.0012 234704 1. {main}() /path/to/test.php:0
Так что, главное, что нужно запомнить, что метод empty() стоит использовать аккуратно, так как в противном случае вы можете получить довольно запутанные (или даже неправильные) результаты.
Заключение
Простота использования PHP может усыпить бдительность разработчиков, внушив им ложное чувство комфорта. Однако из-за некоторых нюансов и особенностей языка разработчики могут попасть в ловушку под названием «утомительная отладка». В результате, PHP-код может просто не заработать или могут возникнуть проблемы, о которых мы говорили в этой статье.
PHP неплохо эволюционировал за свою 20-летнюю историю. Так что, вам определенно стоит ознакомиться с его тонкостями. Вы сможете создавать более масштабируемое, более надежное программное обеспечение, которое будет удобно в сопровождении.
