Автозагрузка и пространства имен в ООП PHP

Автозагрузка и пространства имен
Ключевое слово use



Автоматическая загрузка классов: php.net


Функция __autoload

Функция spl_autoload_register

Подключение нескольких функций автозагрузки

Пространства имен

Ключевое слово use

Групповые объявления use

Замена обратного слэша в пути на прямой



Содержание папки 12 ("Автозагрузка и пространства имен"):


12-1




Функция __autoload




__autoload (php.net) - функция автозагрузки (данная функция является устаревшей).

Она вызывается в тот момент, когда PHP встречает необходимость подключения какого нибудь класса и этот класс не подключен. Допустим, мы где-то создаем экземпляр(объект) какого нибудь класса и при этом данный класс не был подключен (с помощью require или include) то, соответственно, PHP ищет функцию автозагрузки __autoload, если ее он находит, он передает ей параметром имя класса, который необходимо подключить. В этой функции __autoload мы можем определить что делать с этим классом (где его искать и как его подключать):


-- индексный файл --

<?php
//...
// В функцию __autoload передан параметр - имя класса($classname)
function __autoload($classname)
{
// путь, где искать этот класс + ".php"
$filename= "./" . $classname . ".php";
// как подключать этот класс
include_once($filename);
}
//...
?>




- эта функция является устаревшей и, в качестве альтернативы, рекомендуется использовать функцию spl_autoload_register.



Функция spl_autoload_register




spl_autoload_register (php.net) - регистрирует заданную функцию в качестве реализации метода __autoload.

Эта функция выполняет тоже самое, что и функция __autoload, но она удобнее.

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

Функция автозагрузки используется для того, чтобы избежать множественных подключений (с помощью require или include и др.).

Опишем функцию автозагрузки:


function autoloder($class)
{
exit($class); // выведем то, что у нас приходит(для проверки)
}
// - в переменную $class будет поступать имя класса.



Нашу функцию автозагрузки необходимо зарегистрировать с помощью функции spl_autoload_register. В качестве первого обязательного параметра в нее передается имя функции, которую необходимо зарегистрировать (которую мы написали):


spl_autoload_register('autoloder');



- когда этот класс не подключен, PHP ищет зарегистрированные функции автозагрузки и вызывает их одну за другой в поисках подключения нужного класса.


Файл index.php


-- index.php --

<?php
error_reporting(-1);
function autoloder($class)
{
exit($class); // выведем то, что у нас приходит(для проверки)
}
spl_autoload_register('autoloder');
//...
//...
//...
?>
Выведет:
BookProduct





Все классы лежат у нас в папке classes, напишем путь к этому классу: от текущего каталога (с помощью константы __DIR__), обращамся к папке classes, далее - имя класса ($class) и расширение .php:


- $file = __DIR__ . "/classes/{$class}.php" ; - путь к классу от текущего каталога

- exit($file); - выведет на странице путь к файлу: W:\domains\PHPoop1\12/classes/BookProduct.php


Если теперь распечатать наш файл то, вместо BookProduct мы увидим полный путь к итересующему нас классу (W:\domains\PHPoop1\12/classes/BookProduct.php ).

Остается его подключить. Для этого сначала проверяем, а если там этот класс:


-- индексный файл --

<?php
// Если класс существует (file_exists) по указаному пути ($file), то
// мы его подключим (данная функция вызывается только в том случае,
// если данный класс еще не был подключен, то есть поочередно подключаем все классы )
// проверим, как это работает
if(file_exists($file))
{
require_once $file;
//var_dump($file);
}
?>




Файл index.php


-- файл index.php --

<?php

error_reporting(-1);

// Закомментируем подключения классов
require_once 'classes/Product.php';
require_once 'classes/I3D.php'; // подключаем интерфейсный файл interface I3D
require_once 'classes/IGadget.php'; // подключаем интерфейс IGadget.php
require_once 'classes/NotebookProduct.php'; // подключаем класс NotebookProduct
require_once 'classes/BookProduct.php'; // подключаем класс BookProduct

// Опишем функцию автозагрузки
function autoloder($class)
{
//exit($class);
// - после обновления страницы получаем - BookProduct - это тот класс, который был не найден

// путь к классу от текущего каталога
$file = __DIR__ . "/classes/{$class}.php" ;

//exit($file);
// - получаем путь к файлу W:\domains\PHPoop1\12/classes/BookProduct.php

// подключаем класс BookProduct.php
// Если класс существует (file_exists) по указаному пути ($file), то
// мы его подключим (данная функция вызывается только в том случае,
// если данный класс еще не был подключен,
// то есть поочередно подключаем все классы )
if(file_exists($file))
{
require_once $file;
//var_dump($file); // проверим, как это работает
}
}
spl_autoload_register('autoloder');

// ------------------------------------------------------

function debug($data)
{
echo '<pre>' . print_r($data, 1 ) . '</pre>';
}

function offerCase($product)
{
echo "<p>Предлагаем чехол для гаджета {$product -> getName()} </p>";
}

$book = new BookProduct('Три мушкетера', 20, 1000);

$notebook = new NotebookProduct('Dell', 1000, 'Intel');

// предлагаем чехол для гаджета Dell
offerCase($notebook);

// предлагаем чехол для гаджета Три мушкетёра
offerCase($book);

debug($book);

echo $book -> getProduct();

?>
Получим:
Предлагаем чехол для гаджета Dell

BookProduct Object
(
[numPages] => 1000
[name:Product:private] => Три мушкетера
[price:protected] => 20
[discount:Product:private] => 5
)

О товаре:
Наименование: Три мушкетера
Цена со скидкой: 19
Цена без скидки: 20
Кол-во страниц: 1000
Скидка: 5%




- все работает, при этом никаких подключений классов у нас больше нет.



Подключение нескольких функций автозагрузки




Так как нерационально держать все классы (их может быть очень много) в одной папке classes, то для интерфейсов создадим отдельную папку interfaces, которая будет лежать в папке classes.


12-2


Если сейчас обновить страницу - будет ошибка.

Преимущество функции spl_autoload_register заключается в том, что мы можем зарегистрировать несколько функций автозагрузки:


- function autoloder1($class)

- function autoloder2($class)


В функции autoloder2 поменяется логика, здесь мы будем искать не просто в корне папки classes, а в ней есть еще папка interfaces:


- $file = __DIR__ . "/classes/interfaces/{$class}.php".


- и наш интерфейс мы будем искать именно там.

Остается зарегистрировать функцию autoloder2.

У нас получилось две функции автозагрузки и подключаем их одну за другой. PHP их будет вызывать в порядке, как мы их регистрируем: сначала вызовет autoloder1, затем - autoloder2.

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


Интерфейс I3D.php:


-- интерфейс I3D .php --

<?php
interface I3D
{
// константа интерфейса
const TEST2 = 'test interface';
// метод интерфейса (данный метод не имеет реализацию)
public function test();
}
?>




Интерфейс IGadget:


-- интерфейс IGadget.php --

<?php
// создадим интерфейс IGadget
interface IGadget
{
// публичный метод getCase() без реализации
public function getCase();
}
?>




Файл index.php:


-- index.php --

<?php
error_reporting(-1);

// первая функция автозагрузки
function autoloder1($class)
{
echo 1; // для проверки порядка вызова
// путь к классу от текущего каталога
$file = __DIR__ . "/classes/{$class}.php";

// Если класс существует (file_exists) по указаному пути ($file), то
// мы его подключим (данная функция вызывается только в том случае,
// если данный класс еще не был подключен,
// то есть поочередно подключаем все классы )
if(file_exists($file))
{
require_once $file;
}
}

// вторая функция автозагрузки
function autoloder2($class)
{
echo 2; // для проверки порядка вызова
$file = __DIR__ . "/classes/interfaces/{$class}.php";

if(file_exists($file))
{
require_once $file;
}
}

// наши функции автозагрузки необходимо зарегестрировать с помощью
// функции spl_autoload_register();
// в качестве первого обязательного параметра передаются имена функций,
// которые мы написали 'autoloder...'
// 3-й параметр меняет очередность загрузки:

spl_autoload_register('autoloder1');
spl_autoload_register('autoloder2', true, true);

// ------------------------------------------------------

function debug($data)
{
echo '<pre>' . print_r($data, 1 ) . '</pre>';
}

function offerCase($product)
{
echo "<p>Предлагаем чехол для гаджета {$product -> getName()} </p>";
}

$book = new BookProduct('Три мушкетера', 20, 1000);

$notebook = new NotebookProduct('Dell', 1000, 'Intel');

// предлагаем чехол для гаджета Dell
offerCase($notebook);

// предлагаем чехол для гаджета Три мушкетёра
offerCase($book);

debug($book);

echo $book -> getProduct();

?>
Выведет:
21212212
- 3-й параметр меняет очередность загрузки (сначала будет вызываться функция 'autoloder2')
...
...
...




- после обновления страницы все у нас работает.


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

В зтом случае нам помогут "пространства имен"



Пространства имен




Пространства имен (php.net) нужны для того, чтобы избежать, в первую очередь, конфликта имен.

Иногда разные классы могут иметь одинаковые имена (например, контроллер может называться news и модель может называться news). Кроме того, у нас может быть свой код, могут быть стороние библиотеки и там может быть совпадение имен классов.

Эту проблему решает пространство имен.

Простаннство имен указывается с помощью ключевого слова namespace, которое записывается в самом начале, перед основным кодом (перед объявлением класса).

В качестве namespace принято указавыть путь к данному классу, начиная от глобального пространства имен, то есть - от корня нашего приложения (в данном случае - это папка 12).

Добавим пространства имен для наших классов Product, NotebookProduct, BookProduct (namespace classes) и итерфейсов I3D, IGadget (namespace classes\interfaces).


Теперь имена классов - составные - включаются пространства имен (например, класс 'BookProduct' - стал 'classes\BookProduct'). Теперь, если мы создадим два класса BookProduct, которые лежат в разных пространствах имен, то все будет работать и мы увидим, что у нас два разных класса.


Чтобы теперь все у нас заработало, необходимо правильно прописать имена классов:

например, не BookProduct, а \classes\BookProduct и т. д.:


- $file = __DIR__ . "/classes/{$class}.php" - в автозагрузчике,

- $book = new \classes\BookProduct ('Три мушкетера', 20, 1000); -

- создание объекта класса classes\BookProduct.



Ключевое слово use




Также это можно сделать с помощью ключевого слова use.

С помощью ключевого слова use мы указываем какие классы мы будем использовать:


- use classes\BookProduct - (в файле index.php).


В автозагрузчике никакой папки дописывать не нужно (в файле index.php).


- $file = __DIR__ . "/{$class}.php" - (вместо $file = __DIR__ . "/classes/interfaces/{$class}.php")


- теперь здесь мы пишем только имя класса, а весь остальной путь берется из namespace

Убираем второй автозагрузчик.


Интерфейс I3D .php


-- интерфейс I3D .php --

<?php

// В качестве namespace - указывается путь к данному классу, начиная от глобального
// пространства имен, то есть от корня нашего приложения (папки 12)

// добавляем пространство имен (обратный слэш !!!)
namespace classes\interfaces;

// теперь наш интерфейс лежит в пространстве имен: classes\interfaces
// и называется I3D (classes\interfaces I3D)
interface I3D
{
// константа интерфейса
const TEST2 = 'test interface';
// метод интерфейса (данный метод не имеет реализацию)
public function test();
}
?>




Интерфейс IGadget .php


-- интерфейс IGadget --

<?php
namespace classes\interfaces;

// теперь наш интерфейс лежит в пространстве имен: classes\interfaces
// и называется IGadget (classes\interfaces IGadget)
interface IGadget
{
// публичный метод getCase() без реализации
public function getCase();
}
?>




Класс NotebookProduct.php


-- файл NotebookProduct.php --

<?php

// добавляем пространство имен
namespace classes;

// импортируем IGadget
use classes\interfaces\IGadget;

// теперь наш интерфейс лежит в пространстве имен: classes и
// называется NotebookProduct (classes NotebookProduct)
class NotebookProduct extends Product implements IGadget
{
public $cpu;

public function __construct($name, $price, $cpu)
{
parent :: __construct($name, $price);
$this -> cpu = $cpu;
}

// в нашем классе мы обязательно должны реализовать интерфейсный метод getCase()
// если интерфейс будет пустым, то ничего реализовывать мы не будем.
public function getCase()
{
}

public function getProduct()
{
$out = parent :: getProduct();
$out .= "Процессор: {$this -> cpu}<br>";
return $out;
}

public function getCpu()
{
return $this -> cpu;
}

public function addProduct($name, $price, $cpu = '')
{
// --------------------------
var_dump($name);
var_dump($price);
var_dump($cpu);
}
}
?>





Файл index.php


-- файл index.php --

<?php

// с помощью ключевого слова use мы указываем какие классы мы будем использовать
use classes\BookProduct;
use classes\interfaces\IGadget;
use classes\interfaces\I3D;
use classes\NotebookProduct;

error_reporting(-1);

// ПРОСТРАНСТВО ИМЕН начало -----------------------------------------------

// Опишем функцию автозагрузки
function autoloder1($class)
{
//var_dump($class); exit;

// теперь здесь мы пишем только имя класса,
// а весь остальной путь берется из namespace
$file = __DIR__ . "/{$class}.php";
if(file_exists($file))
{
require_once $file;
}
}

// Убираем второй автозагрузчик

// регистрируем автозагрузчик
spl_autoload_register('autoloder1');


// ПРОСТРАНСТВО ИМЕН конец ---------------------------------------------------

function debug($data)
{
echo '<pre>' . print_r($data, 1 ) . '</pre>';
}

function offerCase($product)
{
echo "<p>Предлагаем чехол для гаджета {$product -> getName()} </p>";
}

$book = new BookProduct('Три мушкетера', 20, 1000);

$notebook = new NotebookProduct('Dell', 1000, 'Intel');

// предлагаем чехол для гаджета Dell
offerCase($notebook);

// предлагаем чехол для гаджета Три мушкетёра
offerCase($book);

debug($book);

echo $book -> getProduct();

?>
Выведет:
Предлагаем чехол для гаджета Dell

classes\BookProduct Object
(
[numPages] => 1000
[name:classes\Product:private] => Три мушкетера
[price:protected] => 20
[discount:classes\Product:private] => 5
)

О товаре:
Наименование: Три мушкетера
Цена со скидкой: 19
Цена без скидки: 20
Кол-во страниц: 1000
Скидка: 5%




- все работает.

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

В этом и заключается преимущество использования namespace:

- во первых, мы избегаем возможного конфликта имен,

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



Групповые объявления use




В PHP7 мы можем группировать одинаковые пространста имен (Групповые объявления use).

Например, если классы ClassA , ClassB , ClassC - лежат в одном пространстве имен classes\namespace\. Соответственно мы можем сгруппировать названия классов в фигурных скобках в одной строке кода. Аналогично можно сгруппировать подключаемые функции и константы.


use classes\namespace\{ClassA, ClassB, ClassC};

// в нашем случае:
use classes\{BookProduct, NotebookProduct};
use classes\interfaces\{IGadget, I3D};





Замена обратного слэша в пути на прямой




В автозагрузчике выведем наш класс: echo $file = __DIR__ ."/{$class}.php" и завершим работу (exit).

Выведем на экран - увидим, что в пути есть обратные слэши: C:\OSPanel\domains\PHPoop1\12/classes\BookProduct.php, - что недопустимо для ЛИНУКС,

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

С помощью функции str_replace мы ищем обратные слэши (\) и заменяем их на прямые (/) и ищем это в переменной $class, которая передается параметром.

После обновления получим: C:\OSPanel\domains\PHPoop1\12/classes/BookProduct.php

- обратный слэш в classes\BookProduct поменялся на прямой: classes/BookProduct


Файл index.php


-- index.php --

<?php
// с помощью ключевого слова use мы указываем какие классы мы будем использовать
//use classes\BookProduct;
//use classes\interfaces\IGadget;
//use classes\interfaces\I3D;
//use classes\NotebookProduct;

// группируем одинаковые пространства имен
use classes\{BookProduct, NotebookProduct};
use classes\interfaces\{IGadget, I3D};

error_reporting(-1);

// Опишем функцию автозагрузки
function autoloder1($class)
{
$class = str_replace("\\", "/", $class);
// - ищем обратный слэш (здесь (\\) - один экранирующий) и заменяем их
// на прямые, и ищем их в переменной $class, которая передается
// параметром (после запуска увидим, что обратный слэш поменялся на прямой)

$file = __DIR__ . "/{$class}.php";

exit; // завершаем работу

if(file_exists($file))
{
require_once $file;
}
}

// Убираем второй автозагрузчик

// регистрируем автозагрузчик
spl_autoload_register('autoloder1');


function debug($data)
{
echo '<pre>' . print_r($data, 1 ) . '</pre>';
}

function offerCase($product)
{
echo "<p>Предлагаем чехол для гаджета {$product -> getName()} </p>" ;
}

$book = new BookProduct('Три мушкетера', 20, 1000);

$notebook = new NotebookProduct('Dell', 1000, 'Intel');

offerCase($notebook);

// предлагаем чехол для гаджета Три мушкетёра
offerCase($book);

debug($book);

echo $book -> getProduct();

?>
Выведет:
C:\OSPanel\domains\PHPoop1\12/classes/BookProduct.php







Наверх Наверх