среда, 18 марта 2015 г.

Strict Standards: Only variables should be passed by reference

Данная ошибка в php может возникать в ряде случаев. Мы рассмотрим один из них.
При попытке в php получить последний элемент массива таким образом:
$ip = '192.168.0.2';
$lastNum = end(explode('.', $ip));
var_dump($lastNum); // string(1) "2" 

возникнет ошибка: Strict Standards: Only variables should be passed by reference.
Для устранения этой ошибки нужно просто обернуть вызов explode в скобки:
$ip = '192.168.0.2';
$lastNum = end( (explode('.', $ip)) );
var_dump($lastNum);

Это связано с тем, что функция end() принимает массив по ссылке и сдвигает его внутренний указатель на последний элемент. А по ссылке могут передаваться только объявленные переменные.

Обратите внимание, что так работать не будет:
$lastNum = end( array(1,2,3) );
var_dump($lastNum);

Придется поместить массив в переменную, а только после этого передать в end():
$nums = array(1,2,3);
$lastNum = end( $nums );
var_dump($lastNum); // int(3)



вторник, 17 марта 2015 г.

Автозагрузка классов в PHP

1. Функция __autoload()

В php для автозагрузки классов можно использовать функцию __autoload().

1.1 Файлы лежат в одной директории

Создадим файл user.php:
<?php
class User {
  public function __construct() {
    echo 'Created User';
  }  
}

Допустим, что данный класс мы хотим использовать в файле autoload_func.php:
<?php
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

function __autoload($class) {
  require_once strtolower($class) . '.php';
}

new User();

При этом эти два файла расположены в одной директории.
При запуске скрипта autoload_func.php класс User будет загружен автоматически. Нам не пришлось в файле autoload_func.php писать вручную включение файла: require_once 'user.php';. Также, если мы в этом файле обратимся к друкому классу, к примеру new Company(), то благодаря __autoload() интерпретатор php постарается включить файл company.php, в котором должен располагаться класс Company.

1.2 Файлы лежат во вложенных директориях

Теперь допустим, что у нас добавилась папка bank с файлом account.php:
<?php
class Account {
  public function __construct(){
    echo 'Account created.';
  }
}

Структура файлов выглядит так:
.
├── autoload_func.php
├── bank
│   └── account.php
└── user.php

Получается, что нужно php сообщить, что поиск файлов нужно осуществлять еще и в директории bank:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

set_include_path('.');
set_include_path(get_include_path() . PATH_SEPARATOR . './bank' );

function __autoload($class) {
  //Перечисляем пути поиска классов относительно 
  //текущего файла, где расположена эта ф-ция
  $path_arr = array(
    '.',
    'bank'
  );
  foreach ($path_arr as $path) {
    $file_path = $path.DIRECTORY_SEPARATOR.strtolower($class).'.php';
    if (file_exists($file_path)) {
      include_once $file_path;
    }
  }
}

new User();
new Account();

В этом случае при попытке создать класс Account php приводит имя файла к нижнему регистру - account.php и проверяет его наличие сначала в текущей директории, затем в директории bank. Если файл существует, то он будет включен. Однако, это неверно, т.к. в папке bank может тоже быть файл с именем user.php, характеризующий пользователя банка. Из-за этого мы получим ошибку:
Fatal error: Cannot redeclare class User

Этой ошибки не возникнет, если в вашем проекте используются классы с уникальными названиями.

2. Функция spl_autoload_register()


Она предназначена для автозагрузки файлов используемых классов. Для примера 1.2. код с ее использованием выглядит так:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$path_arr = array(
  '.',
  'bank'
);

foreach ($path_arr as $path) {
  set_include_path(get_include_path(). PATH_SEPARATOR . $path);  
}

spl_autoload_register(function($class) {
  include_once strtolower($class) . '.php';
});

new User();
new Account();

И снова, если имена классов не будут уникальны, возникнет ошибка: Fatal error: Cannot redeclare class ***.
Однако плюсом использования этой функции является отсутствие необходимости проверять наличие файла перед его загрузкой. Здесь мы просто посредством set_include_path() добавляем к include_path новые пути, а функция автозагрузки уже сама позаботится о включении нужных файлов.

Удобочитаемые имена загружаемых файлов

До сих пор мы обсуждали функции автозагрузки, которые поддерживали загрузку только таких файлов: bankuser.php, bankusercomment.php. Такие имена сложно читать, поэтому функцию автозагрузки нужно изменить, чтобы она стала работать с такими файлами: bank_user.php, bank_user_comment.php.
Чтобы это сделать, нужно придумать, как преобразовать строку имени класса "BankUserComment" в "bank_user_comment".
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$path_arr = array(
  '.',
  'bank'
);

foreach ($path_arr as $path) {
  set_include_path(get_include_path(). PATH_SEPARATOR . $path);  
}

spl_autoload_register(function($class) {
  include_once strtolower(
    ltrim(
      preg_replace('/[A-Z]/', '_$0', $class),
      '_'
    )
  ) . '.php';
});

new User();
new Account();
new BankUser();

В этом случае вы можете файл называть bank_user_comment.php, а класс в нем так: BankUserComment.
Разумеется, если вас устраивает, что файлы будут называться так: BankUserComment.php, то в функции автозагрузки нужно просто удалить преобразование имени к нижнему регистру - strtolower().



понедельник, 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.