Магические методы и предопределенные константы в php

Магические методы

Магические методы представляют крючки для специального функционала PHP. Их имена пишутся маленькими буквами с двумя предшествующими подчеркиваниями, например, и .

— магический метод, который PHP вызывает для создания экземпляра вашего класса. Он принимает любое количество аргументов.

<?php
class MySample
{
    public function __construct($foo) {
        echo __CLASS__ . " конструктор вызывается с аргументом $foo.";
    }
}
$obj = new MySample(42);
// MySample конструктор вызывается с аргументом 42

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

<?php
class MySample
{
    public function __destruct() {
        echo__CLASS__ . " вызвал деструктор.";
    }
}
$obj = new MySample; // MySample вызвал деструктор

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

PHP вызывает метод в получающем контексте, если свойство не определено (или недоступно). Метод принимает один аргумент — имя свойства. Он должен вернуть  значение, которое будет обрабатываться как значение свойства.

Метод вызывается для неопределенного свойства в задающем контексте. Данный метод принимает два аргумента, имя свойства и значение.

<?php
class MySample
{
    private $myArray = array();
    public function __set($prop, $value) {
        $this->myAarray = $value;
    }
    public function __get($prop) {
        return $this->myArray;
    }
    public function __isset($prop) {
        return isset($this->myArray);
    }
    public function __unset($prop) {
        unset($this->myArray);
    }
    public function __toString() {
        return __CLASS__ . ":" . $this->name;
    }
}
$obj = new MySample();
if (!isset($obj->name)) {
    $obj->name = "Alireza";
}
echo $obj->name; // Alireza
echo $obj; // MySample:Alireza

В выше приведенном коде свойство не определено в классе. В коде предпринимается попытка назначить данному свойству значение “Alireza” и  PHP вызывает магический метод . Он получает “name” в качестве аргумента и “Alireza” как , и сохраняет значение в частном массиве . Метод работает в схожей манере. При выводе вызывается метод и ему передается значение “name” как аргумент .

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

проверяет, установлено свойство или нет. Данный метод принимает один аргумент — свойство, которое надо проверить. Метод принимает один аргумент, имя свойства, которое нужно сбросить.

Во многих случаях удобно представление объекта как строки, например, для вывода пользователю или другому процессу. Обычно PHP представляет объект как идентификатор в памяти, что плохо для таких функций. Метод помогает представить объект как строку. Метод запускается во многих ситуациях, где объект используется как строка, например, . Также его можно вызвать непосредственно, как любой другой публичный метод.

__construct

The __construct() method is called when an object of a class is created. It is the first method which is called when an object of a class is created.

A Constructor is used for any initialization that the object may need before it is used.

Java

Class Student {
private $age;
//constructor
public function __construct($age) {
$this->age = $age;
}
public function getAge() {
echo $this->age;
}
}
//constructor is called and assign age 18
$Jhon = new Student(18);
$Jhon->getAge(); //18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

ClassStudent{

private$age;

//constructor

publicfunction__construct($age){

$this->age=$age;

}

publicfunctiongetAge(){

echo$this->age;

}

}
 
//constructor is called and assign age 18

$Jhon=newStudent(18);

$Jhon->getAge();//18

In above example, we are creating an instance of the Student class and passing a parameter(value of age) that will be injected into the __construct() method. We don’t have to call __construct() method it will be automatically called when an instance of a student class is created.

Инстанциирование: __new__ and __init__

После изучения основ структур данных Python, например словарей, списков и т. д., вам, скорее всего, встречались примеры определения пользовательских классов, где и происходила ваша первая встреча с магическим методом . Этот метод используется для определенияинициализации экземпляра. Точнее говоря, в вы устанавливаете начальные атрибуты создаваемого экземпляра. Приведем простой пример:  

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

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

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

В следующем коде показана как раз такая цепочка реакций: 

Сравнение

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

  •  для .
  •  для .
  •  для .
  •  для .
  •  для .
  •  для .

Давайте сначала реализуем последний вариант, который является более простым.

Мы знаем, что один экземпляр Time будет равен другому, когда часы, минуты и секунды совпадают.

Примеры.

Просто! Теперь продолжим с операцией a < b.

В этом случае a будет меньше b, если a.h < b.h.

Если часы равны, нужно проверить минуты. А если они равны, то секунды.

Примеры.

Мы могли бы определить остальные методы сравнения так же, как мы это сделали с этими.

Но поскольку другие операции можно вывести из этих двух, которые мы определили (например, a != b равно не a == b), нам не нужно делать это вручную.

Об этом позаботится декоратор functools.total_ordering().

Вот и все! Таким образом, мы получили определение всех операторов сравнения.

Улучшенный контроль доступа к атрибутам: __getattr__ and __setattr__

Если у вас есть опыт программирования на других языках, то, возможно, вы привыкли создавать явные геттеры и сеттеры для атрибутов экземпляра. В Python нам не нужно использовать эти методы контроля доступа для каждого конкретного атрибута. Однако у нас есть возможность получить контроль благодаря реализации методов и . Метод вызывается при обращении к атрибутам экземпляра, а метод  —  при их установке:

Метод вызывается каждый раз при попытке установить атрибут объекта. Для правильного его применения вам придется использовать метод суперкласса  —  super(). В противном случае это приведет к бесконечной рекурсии. 

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

Отмечу, что есть и другой магический метод, тесно связанный с контролем доступа  — это . Он похож на , но вызывается при каждом обращении к атрибуту. Кроме того, он схож и с , в связи с чем также подразумевает использование в своей реализации во избежание ошибки бесконечной рекурсии.

Свойства

Свойства позволяют нам инкапсулировать атрибуты внутри класса. Для чего они нужны?

Давайте рассмотрим следующее.

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

Для решения этой проблемы мы воспользуемся встроенным декоратором property(), который позволяет нам контролировать получение и изменение значения атрибута.

Сделаем это для трех атрибутов.

Давайте начнем с времени.

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

Таким образом, print(a.h) выведет значение, возвращаемое методом h().

Но мы еще не определили атрибут _h, то есть значение, которое мы хотим инкапсулировать, чтобы предотвратить присвоение ему любого типа данных, кроме целого.

Итак, следующее, что нужно сделать, это определить другую функцию, которая будет вызываться, когда атрибуту h будет присвоено новое значение.

Таким образом, выполнение a.h = 50 будет эквивалентно вызову вышеуказанной функции.

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

Давайте проверим, что следующий код выдает ошибку.

То же самое произойдет, если аргументы будут неправильного типа, потому что метод __init()__ позаботится о присвоении переданных значений соответствующим атрибутам.

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

Отлично! Мы успешно решили проблему типа данных.

Теперь давайте разберемся с другим вопросом.

Мы знаем, что 60 секунд равны одной минуте, а 60 минут равны одному часу.

Необходимо, чтобы наш класс позаботился о балансировке данных автоматически: например, преобразовал 80 секунд в 1 минуту и 20 секунд, присвоив соответствующие атрибуты.

Для этого добавим баланс в методы, вызываемые при присвоении атрибутов m и s (сеттеры).

Функция _balance() создается перед определением класса.

Я не объясняю, как работает функция, потому что это не главная тема статьи.

Давайте убедимся, что теперь балансировка происходит автоматически.

Процедурный подход

Представьте, что вы пишите веб-сервис и перед вами встала следующая задача: «Как представить пользователя системы в программе?». Для начала нам необходимо выделить ряд значимых характеристик нашего пользователя:

  • имя (username)
  • адрес электронной почты (email)
  • пароль (password)

Можно представить пользователя в виде нескольких переменных:

Как показать, что существует логическая связь между этими переменными? Мы можем использовать любую подходящую структуру, например, словарь:

А так мы теперь могли бы представить список пользователей:

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

Итак, каждый контейнер (словарь) хранит состояние (характеристики, атрибуты) конкретного пользователя. Для управляемого доступа к состоянию объекта используют специальные функции, так называемые «геттеры» и «сеттеры», например:

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

Info

Инкапсуляция – это свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали реализации от пользователя.

Черная практическая магия: заклинание и проклятие

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

Заклинания

Такой «устный» формат часто содержит обращение к каким-либо конкретным силам, которые должны помочь выполнить пожелание заклинателя. Это не всегда темные силы вроде Дьявола или Сатаны.

При должном энергетическом посыле донести негатив «до адресата» можно с помощью ветра, воды, прочих природных стихий. Но, конечно же, это требует определённых навыков и опыта.

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

Заклинание – это, как правило, часть какого-либо магического действа. Хотя иногда бывает, что некое действие требует просто произнесения текста, без дополнительных обрядовых инсинуаций.

Проклятия

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

Простейший пример – это «мимолётные» проклятия, предполагающие нашёптывание текстовки, глядя в спину уходящему недругу. Тут взгляд играет роль действия, а «заклинательное» нашёптывание выполняет функцию донесения нужной информации до потусторонних сил.

Более сложные примеры – проклятия с пожеланием болезни и/или смерти. Часто это сложные многодневные и многоходовые обрядовые действия, в которых нельзя пропустить ни одного хода и нужно правильно выполнить все рекомендации.

Вплоть до рекомендации не брать сдачу при покупке иголки для подклада или читать заклинание указанное количество раз – не больше и не меньше.

Заклинание Проклятие
Текст как часть ритуала Полный обряд с текстом и действием
Текст бывает самодостаточным действом Ритуал проклятия без текста невозможен

__clone() Magic Method

In PHP, Objects are passed by reference. It means if we copy an object, then it will be copied by reference, not by value.

To understand this let’s take an example –

Java

Class Student {
public $age;
public $name;
public function __construct($age, $name) {
$this->age = $age;
$this->name = $name;
}
public function getAge() {
return $this->age;
}
}
$student = new Student(18, «John»);
$student1 = $student;
//print student object
var_dump($student);
//print student1 object
var_dump($student1);
/*
*Change the value of age property in a student object
*student1 object is also changed
*/
$student->age = 20;
//print student object
var_dump($student);
//print student1 object
var_dump($student1);
/****** Output ********/
object(Student)#1 (2) {
=>
int(18)
=>
string(4) «John»
}
object(Student)#1 (2) {
=>
int(18)
=>
string(4) «John»
}
object(Student)#1 (2) {
=>
int(20)
=>
string(4) «John»
}
object(Student)#1 (2) {
=>
int(20)
=>
string(4) «John»
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

ClassStudent{

public$age;

public$name;

publicfunction__construct($age,$name){

$this->age=$age;

$this->name=$name;

}

publicfunctiongetAge(){

return$this->age;

}

}
 

$student=newStudent(18,»John»);

$student1=$student;

 
//print student object

var_dump($student);

 
//print student1 object

var_dump($student1);

 
/*
*Change the value of age property in a student object
*student1 object is also changed
*/

$student->age=20;

 
//print student object

var_dump($student);

 
//print student1 object

var_dump($student1);

 
/****** Output ********/
 

object(Student)#1(2){

«age»=>

int(18)

«name»=>

string(4)»John»

}

object(Student)#1(2){

«age»=>

int(18)

«name»=>

string(4)»John»

}
 
 

object(Student)#1(2){

«age»=>

int(20)

«name»=>

string(4)»John»

}

object(Student)#1(2){

«age»=>

int(20)

«name»=>

string(4)»John»

}

In order to create a copy of an object, we need to use clone keyword.

Java

Class Student {
public $age;
public $name;
public function __construct($age, $name) {
$this->age = $age;
$this->name = $name;
}
public function getAge() {
return $this->age;
}
}
$student = new Student(18, «John»);
//create a copy of student object
$student1 = clone $student;
//Change the value of age property in student object
$student->age = 20;
//print student object
var_dump($student);
//print student1 object
var_dump($student1);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

ClassStudent{

public$age;

public$name;

publicfunction__construct($age,$name){

$this->age=$age;

$this->name=$name;

}

publicfunctiongetAge(){

return$this->age;

}

}
 

$student=newStudent(18,»John»);

 
//create a copy of student object

$student1=clone$student;

 
//Change the value of age property in student object

$student->age=20;

 
//print student object

var_dump($student);

 
//print student1 object

var_dump($student1);

The __clone() magic method is used to change the behaviour of a clone object.

Java

Class Student {
public $age;
public $name;
public function __construct($age, $name) {
$this->age = $age;
$this->name = $name;
}
public function getAge() {
return $this->age;
}
public function __clone() {
$this->name = «Another student»;
}
}
$student = new Student(18, «John»);
$student1 = clone $student;
//Change the value of age property in student object
$student->age = 20;
//print student object
var_dump($student);
//print student1 object
var_dump($student1);
/*** Output ***/
object(Student)#1 (2) {
=>
int(20)
=>
string(4) «John»
}
object(Student)#2 (2) {
=>
int(18)
=>
string(15) «Another student»
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

ClassStudent{

public$age;

public$name;

publicfunction__construct($age,$name){

$this->age=$age;

$this->name=$name;

}

publicfunctiongetAge(){

return$this->age;

}

publicfunction__clone(){

$this->name=»Another student»;

}

}
 

$student=newStudent(18,»John»);

$student1=clone$student;

 
//Change the value of age property in student object

$student->age=20;

 
//print student object

var_dump($student);

 
//print student1 object

var_dump($student1);

 
/*** Output ***/
 

object(Student)#1(2){

«age»=>

int(20)

«name»=>

string(4)»John»

}

object(Student)#2(2){

«age»=>

int(18)

«name»=>

string(15)»Another student»

}

Работа с массивами

Создать пустой массив

Узнать длину массива (количество элементов) (count)

Вывести (напечатать) массив

Инвертировать массив (array_reverse)

Проверить входит ли значение в массив (in_array)

Узнать существует ли ключ массива (array_key_exists)

Вставить элемент в начало ассоциативного массива

Вставить элемент в определённую позицию массива

Создание и доступ к 2-мерному массиву (массиву хранящему массив) (array)

Массив с русским и английским алфавитом (array)

Поменять кодировку у всех элементов массива (array_map, mb_convert_encoding)

Применить функцию ко всем элементам массива(array_map)

Удалить повторяющиеся элементы массива (array_unique)

Удалить из массива $b все элементы, которые встречаются в массиве $bd (array_diff)

Удалить из массива все пустые элементы (array_diff)

Удалить элемент из массива по ключу (array_slice, unset)

Объединить нескольких массивов (array_merge)

Объединить элементы массива в строку

Цикл с неизвестным количеством итераций (while)

Перебрать все элементы массива $arr (foreach)

Цикл с известным количеством итераций (for)

Прерывание выполнения цикла (break)

Подсчёт количества повторов в массиве (array_count_values)

Получить повторяющиеся элементы из массива (array_unique, array_diff_assoc)

Сортировка массива по значениям, ключам, возрастанию и убыванию (sort, rsort, asort, arsort, ksort, krsort)

Сортировка значений массива на кириллице по алфавиту (sort)

Объединить элементы массива в строку с заданным разделителем (implode)

Набор символов

Предположим, мы хотим найти в тексте все междометия, обозначающие смех. Просто нам не подойдёт — ведь под него не попадут «Хехе», «Хохо» и «Хихи». Да и проблему с регистром первой буквы нужно как-то решить.

Здесь нам на помощь придут наборы — вместо указания конкретного символа, мы можем записать целый список, и если в исследуемой строке на указанном месте будет стоять любой из перечисленных символов, строка будет считаться подходящей. Наборы записываются в квадратных скобках — паттерну будет соответствовать любой из символов «a», «b», «c» или «d».

Внутри набора большая часть спецсимволов не нуждается в экранировании, однако использование перед ними не будет считаться ошибкой. По прежнему необходимо экранировать символы «\» и «^», и, желательно, «]» (так, обозначает любой из символов «]» или «»). Необычное на первый взгляд поведение регулярок с символом «]» на самом деле определяется известными правилами, но гораздо легче просто экранировать этот символ, чем их запоминать. Кроме этого, экранировать нужно символ «-», он используется для задания диапазонов (см. ниже).

Если сразу после записать символ , то набор приобретёт обратный смысл — подходящим будет считаться любой символ кроме указанных. Так, паттерну соответствует любой символ, кроме, собственно, «x», «y» или «z».

Итак, применяя данный инструмент к нашему случаю, если мы напишем , то каждая из строк «Хаха», «хехе», «хихи» и даже «Хохо» будут соответствовать шаблону.

Предопределённые классы символов

Для некоторых наборов, которые используются достаточно часто, существуют специальные шаблоны. Так, для описания любого пробельного символа (пробел, табуляция, перенос строки) используется , для цифр — , для символов латиницы, цифр и подчёркивания «_» — .

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

Также с помощью регулярных выражений есть возможность проверить положение строки относительно остального текста. Выражение обозначает границу слова, — не границу слова, — начало текста, а — конец. Так, по паттерну в строке «Java and JavaScript» найдутся первые 4 символа, а по паттерну — символы c 10-го по 13-й (в составе слова «JavaScript»).

Комикс про регулярные выражения с xkcd.ru

Диапазоны

У вас может возникнуть необходимость обозначить набор, в который входят буквы, например, от «б» до «ф». Вместо того, чтобы писать можно воспользоваться механизмом диапазонов и написать . Так, паттерну соответствует строка «xA6», но не соответствует «xb9» (во-первых, из-за того, что в диапазоне указаны только заглавные буквы, во-вторых, из-за того, что 9 не входит в промежуток 0-8).

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

Обратите внимание, что буква «ё» не включается в общий диапазон букв, и её нужно указывать отдельно

Модификаторы (флаги)

Модифицируют поведение регулярного выражения, стоящий перед модификатором инвертирует его поведение (не распространяется на ). Флаги указываются после «строки-шаблона» в произвольном порядке ().

  • — ищет все совпадения со «строкой-шаблоном» (по умолчанию поиск останавливается после первого совпадения).
  • — регистронезависимость («a» и «A» считаются эквивалентными).
  • — мультистроковость (по умолчанию целевая строка, в котором производится поиск, считается одной строкой).
  • — однострочность (контент считается одной строкой в отличие от режима по умолчанию, метасимвол включает в себя пробельные символы).
  • — поддержка юникода («строка-шаблон» и целевая строка будут обрабатываться в кодировке UTF-8).
  • — инверсия жадности квантификаторов (по умолчанию квантификаторы становятся «ленивыми», вернуть им «жадность» можно, поставив после квантификатора ).
  • — все неэкранированные пробельные символы, которые находятся вне символьного класса, будут проигнорированы.

Скобочные группы ― ()

a(bc)       создаём группу со значением bc -> тестa(?:bc)*    оперетор ?: отключает группу -> тестa(?<foo>bc) так, мы можем присвоить имя группе -> тест

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

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

Опережающие и ретроспективные проверки — (?=) and (?

d(?=r) соответствует d, только если после этого следует r, но r не будет входить в соответствие выражения -> тест(?<=r)d соответствует d, только если перед этим есть r, но r не будет входить в соответствие выражения -> тест

Вы можете использовать оператор отрицания !

d(?!r) соответствует d, только если после этого нет r, но r не будет входить в соответствие выражения -> тест(?<!r)d соответствует d, только если перед этим нет r, но r не будет входить в соответствие выражения -> тест

Заключение

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

  • Валидация данных (например, правильно ли заполнена строка time)
  • Сбор данных (особенно веб-скрапинг, поиск страниц, содержащих определённый набор слов в определённом порядке)
  • Обработка данных (преобразование сырых данных в нужный формат)
  • Парсинг (например, достать все GET параметры из URL или текст внутри скобок)
  • Замена строк (даже во время написания кода в IDE, можно, например преобразовать Java или C# класс в соответствующий JSON объект, заменить “;” на “,”, изменить размер букв, избегать объявление типа и т.д.)
  • Подсветка синтаксиса, переименование файла, анализ пакетов и многие другие задачи, где нужно работать со строками (где данные не должны быть текстовыми).

Перевод статьи Jonny Fox: Regex tutorial — A quick cheatsheet by examples

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: