среда, 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().