понедельник, 13 октября 2014 г.

Тестовое задание на знание фреймворка Symfony

Данное тестовое задание предлагалось в одном крупном интернет-магазине бытовой техники, искавшего к себе в команду php-программиста для удаленной работы.
Вот содержание тестового задания:

Создать небольшой проект, используя
  • Symfony 2.x (любую версию)
  • MySql
  • Doctrine
  • Twig
Проект должен состоять из двух страниц:

1. Главная: site.com

2. Страница категории: site.com/category_name
  • Все делается без дизайна (простые списки).
  • В одном блоке на странице должны выводится категории в виде ссылок.
  • Во втором товары (только названия), принадлежащие выбранной категории.
  • На главной выводить все товары.
  • Категории хранятся в таблице с 2 полями: id(primary key), name
  • Товары хранятся в таблице с полями: id(primary key), name
  • Между таблицами товаров и категорий действуют отношение Many-to-Many
  • Структура таблиц должна описываться в yml формате
  • Запросы должны выполняться с помощью ORM Doctrine
  • Можно использовать другие необходимые bundles
Код должен соответствовать стандарту PSR-2

Необязательная часть:
  • Сделать пагинацию в списке товаров
  • Используя SonataAdminBundle, добавить админ панель, в которой можно добавлять, редактировать, удалять товары и категории, назначать связи между ними.
  • Предоставить доступ для одной роли (можно использовать friendsofsymfony/user-bundle)

понедельник, 29 сентября 2014 г.

Ошибка: Warning: require() [function.require]: open_basedir restriction in effect

При деплое простенького форума на php на бесплатном хостинге hostinger.ru столкнулся с ошибкой:
Warning: require() [function.require]: open_basedir restriction in effect. File(/home/u723559755/public_html/config/config.php) is not within the allowed path(s): (/home/u395489787:/tmp:/var/tmp:/opt/php-5.3/pear) in /home/u723559755/public_html/index.php on line 9


Оказывается, причина ошибки была в том, что в коде корень сайта определялся в зависимости от $_SERVER['DOCUMENT_ROOT']:
$SITE_ROOT = $_SERVER['DOCUMENT_ROOT'];

Возможно, когда я делал запрос, другой пользователь пытался обратиться к другому сайту этого хостинга, и мой сайт получал неверное значение переменной $_SERVER['DOCUMENT_ROOT'].

Изменение определения корня сайта устранило эту проблему:
$SITE_ROOT = dirname(__DIR__);

воскресенье, 24 августа 2014 г.

Как упростить импорт файлов?

Однажды при написании программы обнаружил вот такой участок кода, импортирующий файлы в исполняемый php-скрипт index.php:

<?php include(dirname(__FILE__).'/atm_table_row.jst'); ?>
<?php include(dirname(__FILE__).'/atm_table.jst'); ?>
<?php include(dirname(__FILE__).'/atm_yandex_map.php'); ?>

Вопрос: Как упростить этот участок кода, если все указанные файлы лежат в одной директории?

четверг, 7 августа 2014 г.

Настройка Code Sniffer в PHP Storm 6.0

В среде разработки PHP Storm есть возможность активировать инструмент проверки качества кода Code Sniffer. Данный инструмент позволяет выставлять стандарт кодирования, к примеру Zend, PSR1, PSR2. Вот как выглядят подсказки, которые дает программисту Code Sniffer, если код написан с нарушением установленного стандарта кодирования:


@php_bin@ не является внутренней или внешней командой, исполняемой программой или пакетным файлом

При настройке инструмента проверки кода Code Sniffer в IDE PHP Storm возникла ошибка:
@php_bin@ не является внутренней или внешней командой, исполняемой программой или пакетным файлом.

Данная ошибка возникает, если попытаться вызвать из консоли Windows bat-файл phpcs.bat:


Если заглянуть в файл phpcs.bat, то там можно увидеть следующее:
"@php_bin@" -d auto_append_file="" -d auto_prepend_file="" -d include_path="'@php_dir@'" -f "@bin_dir@\phpcs" -- %*

Причина ошибки в том, что система не может найти путь к интерпретатору php у вас на компьютере. Для устранения данной ошибки можно решить задачу "в лоб" - просто указать прямые пути к интерпретатору php и самому файлу phpcs.
Напишем в файле phpcs.bat:
"C:\wamp\bin\php\php5.5.12\php.exe" -d auto_append_file="" -d auto_prepend_file="" -d include_path="'C:\wamp\bin\php\php5.5.12\php.exe'" -f "C:\tools\PHP_CodeSniffer-master\scripts\phpcs" -- %*

Как видно, здесь мы указали:
- путь к интерпретатору php: C:\wamp\bin\php\php5.5.12\php.exe
- путь к файлу Code Sniffer - phpcs: C:\tools\PHP_CodeSniffer-master\scripts\phpcs

После этого запуск bat-файла из консоли не будет вызывать ошибок. Это позволит нам настроить Code Sniffer в PHP Storm.

вторник, 29 июля 2014 г.

Bitrix. Добавить пользователя в группу социальной сети

При использовании битрикса и системы "Портал Битрикс24" может потребоваться добавить пользователя в группу социальной сети. Сделать это можно при помощи CSocNetUserToGroup::Add():
function addUserToGroup ($userId, $groupId)
{
    global $APPLICATION;

    //1 - это id пользователя, от имени которого будет добавлен пользователь
    //в нашей системе, 1 - это id пользователя "Администратор"
    $initiatedByUserId = CUser::GetID() ? CUser::GetID() : 1;

    $arFields = array(
        "USER_ID" => $userId,
        "GROUP_ID" => $groupId,
        "ROLE" => SONET_ROLES_USER,
        "=DATE_CREATE" => $GLOBALS["DB"]->CurrentTimeFunction(),
        "=DATE_UPDATE" => $GLOBALS["DB"]->CurrentTimeFunction(),
        "INITIATED_BY_TYPE" => SONET_INITIATED_BY_USER,
        "INITIATED_BY_USER_ID" => $initiatedByUserId,
        "MESSAGE" => false,
    );
    
    $rs = CSocNetUserToGroup::Add($arFields);
    if ($rs === false) {
        echo 'Пользователь #' . $userId . ' не добавлен в группу #' . $groupId;
        if ($e = $APPLICATION->GetException()) {
            echo 'Error code: ' . $e->id . '; error message: ' . $e->msg;
        }
    } else {
        echo 'Пользователь #' . $userId . ' добавлен в группу #' . $groupId;
    }
}

Bitrix. Удалить пользователя из группы социальной сети

В битриксе может потребоваться удалить пользователя из группы социальной сети. Сделать это можно при помощи CSocNetUserToGroup::DeleteRelation($userId, $groupId):
$deleteUserId = 2149; // ID пользователя
$deleteGroupId = 114;  // ID группы

if (CSocNetUserToGroup::DeleteRelation($deleteUserId, $deleteGroupId)) {
    echo 'Пользователь #' . $deleteUserId . ' удален из группы';
} else {
    echo 'Не удалось удалить пользователя #' . $deleteUserId . ' из группы';

    //Также желательно выводить сообщения об ошибке, 
    //чтобы понять, что помешало удалить пользователя
    if ($e = $APPLICATION->GetException()) {
        echo 'Error code: ' . $e->id . '; error message: ' . $e->msg;
    }
}

В битриксе сообщения об ошибке можно получить при помощи $e = $APPLICATION->GetException(). Это выражение запишет в $e объект, который содержит поля id и msg. Эта информация может оказаться полезной, чтобы понять, что пошло не так.
Вот, к примеру, при удалении пользователей из группы возникла ошибка:
Error code: ERROR_NO_MEMBER_REQUEST; error message: Record was not found

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

Bitrix. Получить список пользователей группы социальной сети

Для получения списка пользователей группы в социальной сети битрикса можно использовать CSocNetUserToGroup::GetList():
$rsUsers = CSocNetUserToGroup::GetList(
    array("ID" => "ASC"),
    array("GROUP_ID" => 100) //Указать ID группы в социальной сети
);

while($arGroupUser = $rsUsers->Fetch()) {
    var_dump($arGroupUser);
}

Обязательно обратите внимание на структуру данных, помещаемых в переменную $arGroupUser:
array(9) {
  ["ID"]=>
  string(3) "133"
  ["USER_ID"]=>
  string(4) "1770"
  ["GROUP_ID"]=>
  string(3) "114"
  ["ROLE"]=>
  string(1) "A"
  ["DATE_CREATE"]=>
  string(19) "15.04.2014 17:55:23"
  ["DATE_UPDATE"]=>
  string(19) "15.04.2014 17:55:23"
  ["INITIATED_BY_TYPE"]=>
  string(1) "U"
  ["INITIATED_BY_USER_ID"]=>
  string(4) "1770"
  ["MESSAGE"]=>
  NULL
}

Для получения идентификатора пользователя нам здесь нужно использовать не $arGroupUser["ID"], а такое выражение: $arGroupUser["USER_ID"].

четверг, 24 июля 2014 г.

Fatal error: Maximum execution time of 30 seconds exceeded

Ошибка Fatal error: Maximum execution time of 30 seconds exceeded возникает из-за того, что ваш php-скрипт выполняется по времени больше установленного ограничения.
Вот пример такого скрипта:
//Данный скрипт будет потреблять много памяти, 
//поэтому уберем ограничение на объем потребляемой памяти 
ini_set('memory_limit', -1);

$str = '';
for ($i = 0; $i < 10000000; $i++) {
    $str .= 'Hello my wonderful world!';
}

Данный скрипт вызовет ошибку Fatal error: Maximum execution time of 30 seconds exceeded. Устранить эту ошибку можно несколькими путями:
1. Увеличить скорость работы скрипта, изменив его логику работы.
2. Увеличить ограничение на время работы скрипта:
//Устанавливаем ограничение времени выполнения скрипта в 300 секунд
ini_set('max_execution_time', 300); 
$str = '';
for ($i = 0; $i < 10000000; $i++) {
    $str .= 'Hello my wonderful world!';
}
3. Убрать ограничение на время работы скрипта (не рекомендуется):
//Позволяем скрипту выполняться бесконечно долго
set_time_limit(0);

Fatal error: Allowed memory size of 134217728 bytes exhausted

Ошибка Fatal error: Allowed memory size of 134217728 bytes exhausted возникает из-за того, что ваш php-скрипт стал потреблять слишком много памяти.
Поскольку, 134217728 байт / 1024 /1024 = 128 Мб, то ваш скрипт требует памяти больше, чем 128 Мб.

Вот пример такого скрипта:
$str = '';
for ($i = 0; $i < 10000000; $i++) {
    $str .= 'Hello my world';
}

Если ваш скрипт стал таким прожорливым не по причине утечек памяти, то вы можете увеличить этот лимит:
//Увеличиваем лимит памяти, которую может использовать скрипт
ini_set('memory_limit', '256M');

$str = '';
for ($i = 0; $i < 10000000; $i++) {
    $str .= 'Hello my world';
}

Также можно вообще убрать это ограничение и позволить вашему скрипту использовать всю доступную память сервера. Однако это не рекомендуется делать по соображениям производительности.
//Отключаем ограничение на количество памяти, используемой скриптом 
ini_set('memory_limit', -1);

Передать массив в функцию по ссылке

В PHP можно передавать массив в функцию по ссылке. Но что это значит? Лучше посмотреть на примере.
//Функция переводит фамилию в верхний регистр
function uppercase_surname ($person) {
    $person['surname'] = strtoupper($person['surname']);
}

$singer = array(
    'name' => 'Hilary',
    'surname' => 'Duff'
);

uppercase_surname($singer);
echo $singer['surname']; //"Duff" (т.е. регистр не изменился)

Здесь внутри функции происходит создание копии переданного массива. Разумеется, на это расходуется память и системные ресурсы сервера. Следовательно, при больших размерах передаваемого в функцию массива это может быть "тонким местом" производительности вашего скрипта.

Если функция предназначена для модификации данных, то разумно передавать в нее массив по ссылке. Это сэкономит ресурсы сервера на создание копии массива:
//Знак '&' просит php не копировать передаваемый массив, 
//а передать в функцию ссылку на него
function uppercase_surname (&$person) {
    $person['surname'] = strtoupper($person['surname']);
}

$singer = array(
    'name' => 'Hilary',
    'surname' => 'Duff'
);

uppercase_surname($singer);
echo $singer['surname']; //"DUFF" (регистр изменился)

Используйте тернарный оператор

В любом языке, в котором имеется тернарный оператор, хорошо его применять. Есть такой оператор и в php. Его синтаксис прост:
$age = 111;
echo $age > 65 ? 'Мудрец' : 'Юнец'; //Вернет 'Мудрец'

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

Плохо
function confidential_name ($confidential) {
    if ($confidential)
        return 'Ограниченный доступ';
    else
        return 'Без ограничений';
}

Хорошо
function confidential_name ($confidential) {
    return $confidential ? 'Ограниченный доступ' : 'Без ограничений';
}

вторник, 22 июля 2014 г.

Как грамотно настроить среду на стендах разработки

Как правило при разработки используются стенды:
1. develop - У каждого разработчика свой стенд. На нем он разрабатывает фичи для проекта.
2. test - Тестовый стенд, куда переносят разработанные фичи для тестирования.
3. production - Рабочий сайт.

Как в php можно сконфигурировать сервера, чтобы сервер автоматически подключал файлы конфигурации, специфичные для дева, теста и продакшена?

Вот один из возможных методов.

1. Создаем в корне сервера файл .htaccess и указываем в нем переменную для данного стенда:
 SetEnv APPLICATION_ENV develop

Это в том случае, если мы находимся на девелоперском стенде.
Для тестового стенда эта переменная задавалась бы так:
 SetEnv APPLICATION_ENV test

2. В файле конфигурации вашего приложения (к примеру, config.php) теперь можно использовать созданную переменную APPLICATION_ENV:
<?php
switch (getenv('APPLICATION_ENV')) {
    case 'production':
        require_once 'prod.php';
        break;
    case 'testing':
        require_once 'test.php';
        break;
    default:
        require_once 'dev.php';
        break;
}


Как видно, в зависимости от среды происходит подключение файла конфигурации, специфичного для данного стенда.

Другой способ задать переменную окружения - сделать это в php-скрипте, который будет отличаться на каждом из стендов:
//Определяем среду разработки
putenv("APPLICATION_ENV=production"); 

Здесь также в зависимости от среды можно производить подключение нужного файла конфигурации.

четверг, 3 июля 2014 г.

Не делайте лишний вызов функции

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

Плохо

foreach($rules as $rule) {
    //1-й вызов функции
    if ($this->userMatchRule($user, $rule) === 'match') {
        return true;
    }
    //2-й вызов функции
    if ($this->userMatchRule($user, $rule) === 'notMatch') {
        return false;
    }
}

Хорошо

foreach($rules as $rule) {
    //Сохраняем результат работы ф-ции в переменную
    $matchResult = $this->userMatchRule($user, $rule);
    
    //Далее работаем с переменной
    if ($matchResult === 'match') {
        return true;
    }
    if ($matchResult === 'notMatch') {
        return false;
    }
}

Есть ли подстрока в строке

Иногда нужно проверить, есть ли какое-то слово в фразе. Например, есть ли слово "начальник" в названии должности сотрудника.

strpos() - регистрозависимый поиск:

Функция не найдет слово "начальник" во фразе "Начальник отдела продаж":

$text = 'Начальник отдела продаж';
$word = 'начальник';

//Регистрозависимый поиск
if (strpos($text, $word) !== false) {
    echo "Слово есть в тексте";
}

Данный скрипт ничего не выведет.

stripos() - регистроНЕзависимый поиск:

Здесь функция сможет найти слово "начальник" во фразе "Начальник отдела продаж":

$text = 'Начальник отдела продаж';
$word = 'начальник';

//РегистроНЕзависимый поиск
if (stripos($text, $word) !== false) {
    echo "Слово есть в тексте";
}

Этот скрипт найдет вхождение слова во фразе и выведет "Слово есть в тексте".

вторник, 1 июля 2014 г.

Это не проблема с кодировкой

Столкнулся однажды с такими результатами вывода на экран переменной php-скрипта:


Вот часть того скрипта, использующего класс системы Bitrix:
$res = CIBlockElement::GetList(
    Array("SORT"=>"ASC"),
    Array("IBLOCK_ID" => IBLOCK_AUTOFILL_GROUP_ID),
    false,
    false,
    Array("ID", "IBLOCK_ID", "NAME", "DATE_ACTIVE_FROM","PROPERTY_*")
);

while($ob = $res->GetNextElement()){
    $arFields = $ob->GetFields();
    //Вываодим переменную на экран
    echo $arFields['NAME'];
}

Чтобы браузер отобразил корректно русский текст, нужно ему помочь. В Firefox это делается так:


 После этого данные нашего echo отобразятся правильно:


В чем причина произошедшего?
Браузер не определил корректно кодировку по следующим причинам:
- не были переданы заголовки о кодировке;
- данные выводились на пустой странице без тега <meta charset="utf-8">.
Я попробовал добавить в вывод страницы указанный тег:
//Указываем тег кодировки
echo '<html><head><meta charset="utf-8"></head><body>';

while($ob = $res->GetNextElement()){

    echo '<pre>';
    $arFields = $ob->GetFields();
    echo $arFields['NAME'];
    echo '</pre>';
}
    
//Завершаем html-документ
echo '</body></html>';

Это привело к правильному отображению русских букв, поскольку браузер смог корректно определить кодировку благодаря наличию тега <meta charset="utf-8">

понедельник, 23 июня 2014 г.

Использование trigger_error()

Иногда в коде нужно сгенерировать пользовательское исключение.
Для этого отлично подходит встроенная в php функция trigger_error():

$jiraConn = getJiraConnection();
if(!$jiraConn) {
    trigger_error('No connection to Jira', E_USER_WARNING);
} 

При ошибке на страницу будет выведено сообщение:
Warning: No connection to Jira in /var/www/html/core/init.php on line 125

Благодаря такому написанию, можно быстро будет определить, почему ваше приложение не работает.

вторник, 22 апреля 2014 г.

Ошибка в php: Segmentation fault

Однажды под конец рабочего дня я столкнулся с ошибкой:
-bash: line 10: 29379 Segmentation fault  php export_groups.php
Возникала она при запуске скрипта export_groups.php
include ("export_groups.php");
$fileNameGroups = 'site_groups.csv';
$siteExport = new SiteExport($fileNameGroups);
$siteExport->generateGroupsCsv();

Оказалось, что Segmentation fault (Ошибка сегментации (англ. Segmentation fault или сокращённо segfault) — ошибка программного обеспечения, возникающая при попытке обращения к недоступным для записи участкам памяти либо при попытке изменения памяти запрещённым способом.

Причина ошибки состояла в том, что скрипт пытался импортировать сам себя:
include ("export_groups.php");

пятница, 4 апреля 2014 г.

Извлечь табельный номер из строки

Задание. Из строк 'Таб. № 101', 'Таб.  №  102', 'Таб. 103' посредством функции preg_match() извлечь табельные номера и поместить их в переменную $tabel_num_arr.

Решение:
$strings = array(
'Таб. № 101',
'Таб.  №  102',
'Таб. 103',
);
$matches = array();
$tabel_num_arr = array();
foreach($strings as $str) {
    $is_tabel_num = preg_match('/Таб\. *(?:№)? *(\d+)/', $str, $matches);
    if ($is_tabel_num === 1 && $matches[1]) {
        $tabel_num_arr[] = $matches[1];
    }
}
var_dump($tabel_num_arr);
/*
Результат:
array
  0 => string '101' (length=3)
  1 => string '102' (length=3)
  2 => string '103' (length=3)
*/

Примечательным здесь является регулярное выражение 
/Таб\. *(?:)? *(\d+)/
В этом регулярном выражении важным моментом является незахватываемая группа, обрамляемая символами (?:   ) 
Выражение, обрамленное этими символами, не попадает в массив $matches.

Ох уж этот PHP

Добрый день, дорогие друзья!

Что же это за язык такой - PHP? Читается как "пи эйч пи", а не "пэ хэ пэ" или "пых". Это один из наиболее популярных языков для веб-разработки. Отличительной его чертой является простота освоения и интуитивная понятность работы при построении веб-страницы.
PHP освоить гораздо проще, чем C# или Java.