Все ленты — последние статьи

Функциональное программирование на PHP

Скачать исходные файлы

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

Scala, Haskell и другие функциональные языки программирования процветают, а другие, более консервативные языки, такие как Java, начали перенимать некоторые парадигмы функционального программирования (смотрите замыкания в версии Java7 и отложенные вычисления для списков в Java8).

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

Парадигмы программирования

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

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

Каждая парадигма программирования отнимает часть нашей свободы:

  • Модульное программирование лишает нас неограниченного размера программы;
  • Структурное и процедурное программирование лишает оператора «go-to» и ограничивает программиста путем использования последовательного исполнения, ветвлений и циклов;
  • Объектно-ориентированное программирование отнимает указатели на функции;
  • Функциональное программирование лишает нас возможности использовать присвоение и изменяемые состояния.

Принципы функционального программирования

В функциональном программировании у вас нет данных, представленных с помощью переменных.

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

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

В функциональном программировании у вас нет данных, представленных с помощью переменных. Нет контейнеров данных.

Данные не присваиваются переменным. Некоторые величины могут быть определены и присвоены, однако в большинстве случаев они являются функциями, присвоенными «переменным». Я поместил «переменные» в кавычки, потому что в функциональном программировании они не изменяются.

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

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

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

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

Покажите мне код!

Хватит разговоров и философии для одного урока. Давайте программировать!

Создайте новый PHP проект в своей любимой среде разработки или редакторе кода. Создайте в нем папку «Tests». Затем создайте два файла: в папке проекта — файл FunSets.php и в папке «Tests» — FunSetsTest.php. Мы напишем приложение, с тестами, которое будет представлять концепцию множеств.

В математике множество — это совокупность отдельных объектов, рассматриваемая как единый объект.

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

Наши ограничения в программировании

Итак, давайте начнем! Хотя нет, подождите. Зачем? Для того чтобы проявить уважение к концептам функционального программирования мы должны применить следующие ограничения к нашему коду:

  • Никаких присвоений. Нам не разрешается присваивать значения переменным. Однако можно присваивать функции переменным;
  • Никаких изменяемых состояний. Нам не разрешается, в случае присвоения, изменять значение того, что присвоено. Также не разрешается изменять значение любой переменной, значение которой установлено как параметр для текущей функции. Таким образом, нельзя изменять параметры;
  • Нельзя использовать циклы while и for. Нам не разрешено пользоваться PHP командами «while» и «for». Зато мы можем определить наш собственный метод, чтобы организовать цикл по элементам множества и назвать его foreach, или for, или while.

Ограничения не применяются к тестам. Учитывая природу PHPUnit, мы будем использовать для тестирования объектно-ориентированный код на PHP. Кроме того, чтобы лучше разместить наши тесты, мы упакуем весь рабочий код в отдельный класс.

 
 

Функция, определяющая множество

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

Определяющая множество функция — это метод contains.

function contains($set, $elem) {
return $set($elem);
}

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

$set = function ($element) {return true;};
contains($set, 100);

Теперь идея немножко проясняется. Функция contains имеет два параметра:

  • $set — представляет множество, определенное как функция;
  • $elem — представляет собой элемент, определенный как значение.

В данном контексте все, что метод contains должен делать — это вызвать функцию $set с параметром $elem.

Давайте упакуем все в тест.

class FunSetsTest extends PHPUnit_Framework_TestCase {
private $funSets;
protected function setUp() {
$this->funSets = new FunSets();
}
function testContainsIsImplemented() {
//Мы описываем множество с помощью функции contains. Это базовая функция //множества. 
$set = function ($element) {return true;};
$this->assertTrue($this->funSets->contains($set, 100));
}
}

И поместим наш рабочий код в класс в файле FunSets.php:

class FunSets {
public function contains($set, $elem) {
return $set($elem);
}
}

Вы можете даже запустить этот тест и он выполнится. Множество, которое мы определили для этого теста, это просто функция, всегда возвращающая значение true. Это «true множество».

Множество с единственным элементом

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

function testSingletonSetContainsSingleElement() {
//Множество синглетон описывается при помощи функции, которая при передаче в метод //contains вернет значение true для одиночного элемента, переданного в качестве //параметра. 
//Другими словами, синглетон — это множество с единственным элементом. 
$singleton = $this->funSets->singletonSet(1);
$this->assertTrue($this->funSets->contains($singleton, 1));
}

Нам нужно определить функцию под названием singeltonSet с параметром, представляющим собой элемент множества. В тесте это число один (1).

Затем мы ожидаем, что наш метод contains, вызванный для синглетон функции, вернет значение true, если отправленный параметр равен единице. Код для тестирования выглядит следующим образом:

public function singletonSet($elem) {
return function ($otherElem) use ($elem) {
return $elem == $otherElem;
};
}

Вот это да! Просто очуметь.

Итак, функция singletonSet принимает в качестве параметра элемент $elem. Затем она возвращает другую функцию, которая имеет входной параметр $otherElem и сравнивает $elem с $otherElem.

Как это все работает? Во-первых, вот эта строка:

singleton = $this->funSets->singletonSet(1);
преобразуется в соответствии с тем, что возвращает вызов singletonSet(1):
$singleton = function ($otherElem) {
return 1 == $otherElem;
};

Затем происходит вызов функции contains($singleton, 1). Которая, в свою очередь, вызывает $singleton. Таким образом, код сводится к:

$singleton(1)

Который фактически выполняет код со значением $otherElem равным единице.

return 1 == 1

Что, конечно же, возвращает значение true, и наш тест успешно пройден.

Вы уже улыбаетесь? Вы чувствуете, как ваш мозг начинает закипать? Я точно это чувствовал, когда сначала написал этот пример на языке Scala, и почувствовал снова, когда сделал это на PHP. Я думаю, это нечто необычное.

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

У нас нет переменных, содержащих единицу или имеющих состояние единицы. Нет состояний, нет присвоений, нет изменчивости, нет циклов. Тут мы на верном пути.

Объединение множеств

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

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

function testUnionContainsAllElements() {
//Объединение описывается с помощью функции, которая принимает два множества в //качестве параметров и содержит в себе эти множества 
//На данный момент мы можем создать только множества с единственным элементом, //поэтому мы создаем два синглетона и объединяем их 
$s1 = $this->funSets->singletonSet(1);
$s2 = $this->funSets->singletonSet(2);
$union = $this->funSets->union($s1, $s2);
//Теперь проверяем, что и 1, и 2 являются частью объединения 
$this->assertTrue($this->funSets->contains($union, 1));
$this->assertTrue($this->funSets->contains($union, 2));
// …и объединение не содержит значение 3
$this->assertFalse($this->funSets->contains($union, 3));
}

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

Затем мы хотим иметь возможность проверять при помощи функции contains содержит ли объединение элемент или нет. Таким образом, наша функция union должна возвращать другую функцию, которую можно использовать для вызова contains.

 
 
public function union($s1, $s2) {
return function ($otherElem) use ($s1, $s2) {
return $this->contains($s1, $otherElem) || $this->contains($s2, $otherElem);
};
}

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

Пересечение и разница

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

public function intersect($s1, $s2) {
return function ($otherElem) use ($s1, $s2) {
return $this->contains($s1, $otherElem) && $this->contains($s2, $otherElem);
};
}
public function diff($s1, $s2) {
return function ($otherElem) use ($s1, $s2) {
return $this->contains($s1, $otherElem) && !$this->contains($s2, $otherElem);
};
}

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

Фильтр на множестве

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

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

function testFilterContainsOnlyElementsThatMatchConditionFunction() {
$u12 = $this->createUnionWithElements(1, 2);
$u123 = $this->funSets->union($u12, $this->funSets->singletonSet(3));
//Правило фильтрации, которое находит элементы больше 1 (т.е. 2 и 3 в нашем случае)
$condition = function($elem) {return $elem > 1;};
//Отфильтрованное множество 
$filteredSet = $this->funSets->filter($u123, $condition);
// Проверяем, что отфильтрованное множество не содержит 1
$this->assertFalse($this->funSets->contains($filteredSet, 1), "Should not contain 1");
// Проверяем, содержит ли оно 2 и 3
$this->assertTrue($this->funSets->contains($filteredSet, 2), "Should contain 2");
$this->assertTrue($this->funSets->contains($filteredSet, 3), "Should contain 3");
}
private function createUnionWithElements($elem1, $elem2) {
$s1 = $this->funSets->singletonSet($elem1);
$s2 = $this->funSets->singletonSet($elem2);
return $this->funSets->union($s1, $s2);
}

Мы создаем множество с тремя элементами: 1, 2, 3. И помещаем его в переменную $u123, чтобы легче распознавать его в наших тестах. Затем мы определяем функцию, к которой хотим применить тест, и помещаем ее в $condition.

Наконец, мы вызываем функцию фильтрации для нашего множества $u123 вместе с $condition и помещаем получившееся множество в $filteredSet. Затем мы запускаем проверку при помощи contains, чтобы убедиться, действительно ли множество выглядит так, как мы хотели.

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

public function filter($set, $condition) {
return function ($otherElem) use ($set, $condition) {
if ($condition($otherElem))
return $this->contains($set, $otherElem);
return false;
};
}

Пожалуйста, готово! Мы реализовали фильтрацию с помощью всего трех строк кода. Точнее, если условие выполняется для рассматриваемого элемента, то мы запускаем функцию contains на множестве для этого элемента. Если не выполняется — просто возвращаем false. Вот и все.

Цикл по элементам

Следующий шаг — это создание различных функций циклов. Самая первая — forall() — будет принимать в качестве параметров $set и $condition и возвращать значение true, если $condition применимо ко всем элементам $set. Это приводит к следующему тесту:

function testForAllCorrectlyTellsIfAllElementsSatisfyCondition() {
$u123 = $this->createUnionWith123();
$higherThanZero = function($elem) { return $elem > 0; };
$higherThanOne = function($elem) { return $elem > 1; };
$higherThanTwo = function($elem) { return $elem > 2; };
$this->assertTrue($this->funSets->forall($u123, $higherThanZero));
$this->assertFalse($this->funSets->forall($u123, $higherThanOne));
$this->assertFalse($this->funSets->forall($u123, $higherThanTwo));
}

Мы выделяем создание $u123 из прошлого теста в частный метод. Затем мы определяем три разных условия: больше, чем ноль; больше, чем один; больше чем два. Так как наше множество содержит числа один, два и три, то только условие «больше, чем ноль» должно вернуть значение true, а остальные — false. В действительности, мы можем выполнить тест при помощи другого рекурсивного метода, используемого для перебора всех элементов.

private $bound = 1000;
private function forallIterator($currentValue, $set, $condition) {
if ($currentValue > $this->bound)
return true;
elseif ($this->contains($set, $currentValue))
return $condition($currentValue) && $this->forallIterator($currentValue + 1, $set, $condition);
else
return $this->forallIterator($currentValue + 1, $set, $condition);
}
public function forall($set, $condition) {
return $this->forallIterator(-$this->bound, $set, $condition);
}

Начнем с определения некоторых ограничений для нашего множества. Значения элементов должны быть в диапазоне от -1000 до +1000. Это разумное ограничение накладывается для того, чтобы сохранить пример достаточно простым. Функция forall вызывает private-метод forallItertor с нужными параметрами, чтобы рекурсивно решить, удовлетворяют ли все элементы условию.

 
 

В этой функции мы, прежде всего, проверяем, не вышли ли мы за границы. Если да, возвращаем true. Затем проверяем, содержит ли наше множество текущую величину, если да, то возвращаем результат применения логического «И» к проверке текущей величины на выполнение условия и рекурсивного вызова самой функции со следующим значением. В противном случае, просто вызываем из функции саму себя со следующим значением и возвращаем результат.

Все замечательно работает. Мы можем реализовать подобным образом функцию exists(). Она возвращает значение true, если какой-либо элемент множества удовлетворяет условию.

private function existsIterator($currentValue, $set, $condition) {
if ($currentValue > $this->bound)
return false;
elseif ($this->contains($set, $currentValue))
return $condition($currentValue) || $this->existsIterator($currentValue + 1, $set, $condition);
else
return $this->existsIterator($currentValue + 1, $set, $condition);
}
public function exists($set, $condition) {
return $this->existsIterator(-$this->bound, $set, $condition);
}

Единственная разница в том, что мы возвращаем значение false, если вышли за границы, и используем логическое «Или» вместо «И» во втором операторе if.

А теперь наступило время функции отображения map(), отличающейся от предыдущих, более простой и короткой.

public function map($set, $action) {
return function ($currentElem) use ($set, $action) {
return $this->exists($set, function($elem) use ($currentElem, $action) {
return $currentElem == $action($elem);
});
};
}

Отображение обозначает, что мы применяем действие ко всем элементам множества. Для отображения нам не нужна помощь итератора, мы можем повторно воспользоваться exists() и вернуть те элементы exists(), которые удовлетворяют результату $action, примененного к $element. Это, возможно, не совсем очевидно на первый взгляд, поэтому давайте посмотрим, как все происходит.

  • Мы передаем множество {1, 2} и действие $element * 2 (удвоение) в функцию map;
  • Она вернет функцию, что очевидно, которая имеет в качестве параметра элемент и использует множество и действие с уровня выше;
  • Эта функция вызовет функцию exists со множеством { 1, 2 } и условной функцией $currentElement, равной $elem * 2;
  • Функция exists() переберет все элементы от -1000 до +1000 (границы нашего множества). Когда она найдет элемент, равный удвоенному значению от того, что пришло из функции contains (значение $currentElement), то функция вернет true;
  • Другими словами, последнее сравнение вернет значение true для вызова функции contains со значением два, если текущее значение, умноженное на два, равно двум.

Т.е. для первого элемента множества, единицы, функция вернет true для значения два, а для второго элемента, двойки — для значения четыре.

Практический пример

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

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

class AuthPlugin {
private $permissions = array();
function authenticate($username, $password) {
$this->verifyUser($username, $password);
$adminModules = new AdminModules();
$this->permissions[] = $adminModules->allowRead($username);
$this->permissions[] = $adminModules->allowWrite($username);
$this->permissions[] = $adminModules->allowExecute($username);
}
private function verifyUser($username, $password) {
// … Делаем проверку пользователь/пароль
// … Загружаем детальную информацию о пользователе
}
}

Сейчас этот код может показаться вполне нормальным, но в нем есть огромная проблема. 80% метода authenticate() пользуется информацией из AdminModules. А это создает очень сильную зависимость.

Было бы намного разумнее взять эти три вызова и создать отдельный метод с использованием AdminModules.

Таким образом, переместив генерирование в AdminModules, нам удалось свести три зависимости всего к одной. Public-интерфейс AdminModules также был упрощен с трех до одного метода. Однако мы еще не у цели — плагин AuthPlugin до сих пор напрямую зависит от класса AdminModules.

Объектно-ориентированный подход

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

class AuthPlugin {
private $permissions = array();
private $appModule;
function __construct(ApplicationModule $appModule) {
$this->appModule = $appModule;
}
function authenticate($username, $password) {
$this->verifyUser($username, $password);
$this->permissions = array_merge(
$this->permissions,
$this->appModule->getPermissions($username)
);
}
private function verifyUser($username, $password) {
// … Делаем проверку пользователь/пароль
// … Загружаем детальную информацию о пользователе
}
}

AuthPlugin получил конструктор. Конструктор принимает параметр типа ApplicationModule, интерфейс, и вызывает функцию getPermissions() для этого внедренного объекта.

interface ApplicationModule {
public function getPermissions($username);
}

Интерфейс ApplicationModule определяет единственный public-метод getPermissions(), с именем пользователя в качестве параметра.

class AdminModules implements ApplicationModule {
// [ … ]
}

И наконец, в классе AdminModules необходимо реализовать интерфейс ApplicationModule.

Теперь стало намного лучше. Наш плагин AuthPlugin зависит только от интерфейса. AdminModules зависит от того же самого интерфейса, поэтому AuthPlugin стал независимым от модуля. Мы можем создавать любое количество модулей, каждый из которых реализует интерфейс ApplicationModule, и плагин AuthPlugin будет способен работать с ними со всеми.

Функциональный подход

Другой способ избавиться от зависимости и сделать возможным использование плагина AuthPlugin модулем AdminModule, или любыми другими модулями, это внедрение в эти модули зависимости от AuthPlugin. AuthPlugin будет обеспечивать возможность определять функцию аутентификации, и каждое приложение будет представлено своей собственной функцией getPermission().

class AdminModules {
private $authPlugin;
function __construct(Authentitcation $authPlugin) {
$this->authPlugin = $authPlugin;
}
private function allowRead($username) {
return "yes";
}
private function allowWrite($username) {
return "no";
}
private function allowExecute($username) {
return $username == "joe" ? "yes" : "no";
}
private function authenticate() {
$this->authPlugin->setPermissions(
function($username) {
$permissions = array();
$permissions[] = $this->allowRead($username);
$permissions[] = $this->allowWrite($username);
$permissions[] = $this->allowExecute($username);
return $permissions;
}
);
$this->authPlugin->authenticate();
}
}

Начнем с класса AdminModule. Он больше не реализует ничего. Однако, в нем используется внедренный объект, который должен реализовывать интерфейс Authentication. В классе AdminModule будет метод authenticate(), который вызывает setPermissions() для AuthPlugin и передает в качестве параметра функцию, которую необходимо задействовать.

interface Authentication {
function setPermissions($permissionGrantingFunction);
function authenticate();
}

Интерфейс Authentication просто определяет два метода.

class AuthPlugin implements Authentication {
private $permissions = array();
private $appModule;
private $permissionsFunction;
function __construct(ApplicationModule $appModule) {
$this->appModule = $appModule;
}
function authenticate($username, $password) {
$this->verifyUser($username, $password);
$this->permissions = $this->permissionsFunction($username);
}
private function verifyUser($username, $password) {
// … Делаем проверку пользователь/пароль
// … Загружаем детальную информацию о пользователе
}
public function setPermissions($permissionGrantingFunction) {
$this->permissionsFunction = $permissionGrantingFunction;
}
}

И наконец, AuthPlugin реализует Authentication и определяет входную функцию в private атрибуте класса. Теперь функция authentication() стала туповатой. Она просто вызывает функцию и затем устанавливает возвращаемое значение. Она полностью изолирована от входных объектов.

Если мы посмотрим на схему, то увидим два важных изменения:

  • Вместо AdminModule, AuthPlugin реализует интерфейс;
  • AuthPlugin передает с помощью обратного вызова модуль AdminModule или любой другой модуль, переданный в функцию permissionsFunction.

Какой подход использовать?

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

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

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

Перевод статьи «Functional Programming in PHP» был подготовлен дружной командой проекта Сайтостроение от А до Я.

Дата и время — метод OOP

Расширение PHP Date/Time представляет собой набор классов, которые позволяют решать практически все задачи, связанные с датой и временем.

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

  • Дата или время представляются объектом DateTime;
  • Часовой пояс представляется объектом DateTimeZone;
  • Объекты DateInterval представляют временной интервал. Например, когда мы говорим, что с этого момента прошло два дня, то в этом случае «два дня» как раз является временным интервалом. Объект DateInterval не указывает на конкретную дату или время;
  • Объекты DatePeriod задают период между двумя конкретными датами.

А теперь не позволите ли мне дать два вам два небольших совета: оба, я уверен, помогут вам в решении практических задач.

От date() к DateTime

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

Функция DateTime дает нам намного больше, чем просто возможность выводить в нужном формате дату и время. Однако прежде, чем мы пойдем дальше, вам нужно будет создать новый объект DateTime, который представляет дату и / или время.

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

$date = new DateTime();

Конструктор по умолчанию принимает для него значение «сейчас» — текущие время и дата. Чтобы создать объект для определенной даты, вы должны присвоить ему значение этих даты и времени.

Определение форматов для объекта, как правило очень интуитивно. Ниже приводятся несколько примеров создания объектов DateTime:

new DateTime('2013, March 24') //DateTime, представляющий 24 марта 2013 года.
new DateTime('2013-03-24') //DateTime, представляющий 24 марта 2013 года.
new DateTime('+2 days') //DateTime, представляющий 2 дня, считая с сегодняшней даты.
new DateTime('tomorrow')

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

 
 

Если в ней отсутствует нужный вам формат, вы всегда можете задать свой собственный с помощью функции DateTime :: createFromFormat:

DateTime :: createFromFormat ( ' JM- Y' , '24 -Mar- 2013 ');

Теперь, когда у нас есть объект DateTime, мы довольно легко можем сделать с ним кучу интересных вещей.

Формат времени для Unix:

$date->getTimestamp(); //возвращает формат времени для unix

Изменение даты/времени:

$date->setDate(2013, 12, 30); //задает конкретную дату в формате гггг, мм, дд
$date->setTime(12, 3, 20); //чч, мм, сс (опционально) изменяет время
$date->modify('tomorrow'); //изменение текущего времени
$date->setTimestamp(1364798550); //изменение используемого формата времени для unix

Обратите внимание, что, если задается значение, выходящее за границы диапазона, PHP соответственно модифицирует дату. Например, запись $date->setDate(2013, 12, 35); будет преобразована в дату 2014-01-04. То же самое касается и времени.

Работа с несколькими датами

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

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

Скажем, вам нужно сравнить два дня рождения:

$sheldon = new DateTime('May 20th, 1980');
$neo = new DateTime('March 11th, 1962');
if ($sheldon > $neo)
echo 'Sheldon is younger than neo';

Еще один сценарий сравнения двух дат. Мы можем сравнить их вот таким образом:

$diff = $neo->diff($sheldon);

Функция diff задает объект DateInterval . Если мы создали для него все соответствующие значения:

DateInterval Object
(
[y] => 18
[m] => 2
[d] => 9
[h] => 0
[i] => 0
[s] => 0
[invert] => 0
[days] => 6645
)

Это публичные свойства. Вы можете создать и собственный привлекательный формат вывода объекта DateInterval:

$diff->format('Neo is older by %Y years and %m months older'); //Нео старше на 18 лет и 2 месяца

Но самое главное, что интервал, заданный в объекте DateInterval, можно применить к другому объекту DateTime :

 
 
$neo->add($diff); // дата рождения Нео заменена на дату Шелдона

Примечание: Изменения в объекте DateTime, такие как, например, добавление данных, не создает новые объекты DateTime — все изменения производятся с исходным объектом. Всегда помните об этом, когда ваши приложения используют объекты DateTime.

В PHP 5.5 введен новый класс, который задает новые объекты, не поддерживавшиеся предыдущими версиями.
Функция diff не единственный способ, которым можно генерировать объект DateInterval. Так как это отдельный класс, новые объекты можно инициировать обычным способом:

$new_diff = new DateInterval('P2Y');

Значения года / месяца / дня и т.д., передаются в конструктор в виде строки. Более подробную информацию можно найти в справочной документации.

Работа с часовыми поясами

При создании новых объектов DateTime, второй аргумент конструктора предназначен для задания часового пояса. Если мы пропускаем его, тогда часовой пояс присваивается по умолчанию, на основании значения date.timezone файла php.ini. Вы можете изменить его в любое время через функцию date_default_timezone_set:

date_default_timezone_set('America/New_York');
new DateTime('today'); //объект datetime, принадлежит часовому поясу Нью-Йорка

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

$timezone = new DateTimeZone('America/New_York');
$sheldon->setTimezone($timezone); //день рождения Шелдона теперь исчисляется по Нью-Йоркскому времени

Также вы можете задать часовой пояс при создании нового объекта DateTime :

$sheldon = new DateTime('May 20th, 1980', $timezone);

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

Список часовых поясов доступен в онлайн-документации.

DatePeriods

Я думаю, что в официальном руководстве дано наиболее подходящее определение:
«DatePeriods позволяет перечислить множества дат и времен, повторяющихся через определенные интервалы времени в течение заданного периода».

 
 

DatePeriod позволяет создать набор объектов DateTimes, используя два дня, ограничивающие временной интервал. Основными элементами набора являются: дата начала периода, интервал и дата окончания. Для каждого временного интервала создается новый объект DateTime.

Предположим, мы хотим получить все дни рождения Шелдона с момента его рождения:

//с момента его рождения это событие происходит каждый год с интервалом в один год
$interval = new DateInterval('P1Y');
//в качестве третьего аргумента задается дата окончания периода, новый объект DateTime()
//== прямо сейчас
$period = new DatePeriod($sheldon, $interval, new DateTime());
foreach($period as $dt) {
//DateTime objects
echo $dt->format('Y-m-d — D'), "\n";
}

Результат обработки будет выглядеть таким образом:

1981-05-20 — Wed
1982-05-20 — Thu
1983-05-20 — Fri
1984-05-20 — Sun
1985-05-20 — Mon
1986-05-20 — Tue

Теперь по умолчанию объект DatePeriod включает дату начала временного интервала. Однако с помощью четвертого аргумента можно пропустить начальную дату:

$period = new DatePeriod($sheldon, $interval, new DateTime(), DatePeriod::EXCLUDE_START_DATE);

Теперь давайте посмотрим, сколько дней рождения успел отпраздновать Нео, до того, как родился Шелдон:

$bdays = new DatePeriod($neo, $interval, $sheldon, DatePeriod::EXCLUDE_START_DATE);
echo iterator_count($bdays);

Расширения

Все классы, которые мы сегодня рассмотрели, могут быть расширены под использование в сочетании с собственными методами. Одно из самых популярных расширений — это использование объекта DateTime с методом __ ToString, которое позволяет выводить корректно DateTime без обращения к формату.

Несколько примеров использования

Лично я в своей практике часто использую объекты DateTime, когда имею дело со столбцами базы данных, в которых записываются значения даты / времени. Все даты хранятся в таблице в виде даты по Гринвичскому времени.

Код приложения работает только с объектами DateTime, однако пока в ходе обработки запроса не будут сгенерированы даты в соответствующих форматах, все они исчисляются в часовом поясе по Гринвичу. Данный подход позволил мне довольно просто работать с датами, которые принадлежат разным часовым поясам.

Я могу работать с временным объектом, исчисляемым по Нью-Йоркскому времени, и не заботиться о его формате, когда данные записываются в базу данных. Я могу легко переключаться между форматом Unix и регулярным форматом даты-времени в любой момент, мои приложения обрабатывают только объекты DateTime.

Я также использовал объект DateInterval, чтобы упростить логику скрипта, обрабатывающего поступающие платежи за подписку. Использовав объекты DateInterval для определения интервалов между двумя регулярными платежами, я сделал его намного проще.

Мне всего лишь нужно применить заданный интервал к дате последней оплаты.
У вас есть свои собственные примеры работы с датой / временем? Поделитесь ими в комментариях.

На будущее

Расширение DateTime может иметь столь большое количество применений. Если вы следите за последними событиями в этой области, то знаете, что появились новые классы и интерфейсы, принятые уже после PHP 5.5.

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

Перевод статьи «Dates and Time — The OOP Way» был подготовлен дружной командой проекта Сайтостроение от А до Я.

23 замечательных PHP-библиотеки, о которых вам стоит знать


23 замечательных PHP-библиотеки, о которых вам стоит знать

 

Сейчас очень хорошее время для того, чтобы быть PHP-разработчиком. Есть множество библиотек, которые выходят чуть ли не каждый день, а такие ресурсы, как Composer и Github помогают нам легко их найти и начать использовать.

Ниже вы найдете 23 классных библиотеки, о которых я хочу вам рассказать.

1. Dispatch — Микро-фреймворк

Dispatch — это минималистичный PHP-фреймворк. Он не дает вам полноценной MVC-структуры, но вы можете назначить правила и методы обработки URL-адресов чтобы лучше организовать ваше приложение. Это удобно для API, простых сайтов и прототипов:

// Подключаем библиотеку
include 'dispatch.php';
// назначаем свои правила маршрутизации
get('/greet', function () {
// отрисовка вида
render('greet-form');
});
// post-обработчик
post('/greet', function () {
$name = from($_POST, 'name');
// отрисовка вида с передачей переменных
render('greet-show', array('name' => $name));
});
dispatch();

Вы сможете настроить определенные типы HTTP-запросов и пути, отрисовывать виды и др. Если вы объедините Dispatch с другим фреймвороком, то получите действительно мощный и легкий инструмент.

2. Klein — молниеносный роутер для PHP

Klein — еще одна легковесная библиотека маршрутизации для PHP 5.3+. У нее чуть более многословный синтаксис по сравнению с Dispatch, однако она также очень быстра. Вот пример:

respond('/[:name]', function ($request) {
echo 'Hello ' . $request->name;
});

Вы также можете настраивать различные HTTP-методы и использовать регулярные выражения при формировании путей:

respond('GET', '/posts', $callback);
respond('POST', '/posts/create', $callback);
respond('PUT', '/posts/[i:id]', $callback);
respond('DELETE', '/posts/[i:id]', $callback);
// Обработка нескольких HTTP-методов:
respond(array('POST','GET'), $route, $callback);
// Или вы можете обрабатывать запросы так:
respond('/posts/[create|edit:action]?/[i:id]?', function ($request, $response) {
switch ($request->action) {
// делаем что-нибудь
}
});

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

В этом случае лучше будет посмотреть в сторону полноценных MVC-фреймворков, вроде Laravel или CodeIgniter.

3. Ham — библиотека маршрутизации с кэшированием

Ham — это тоже легкая библиотека маршрутизации, а для еще большего выигрыша в скорости она использует кэширование. Пример:

require '../ham/ham.php';
$app = new Ham('example');
$app->config_from_file('settings.php');
$app->route('/pork', function($app) {
return "Delicious pork.";
});
$hello = function($app, $name='world') {
return $app->render('hello.html', array(
'name' => $name
));
};
$app->route('/hello/<string>', $hello);
$app->route('/', $hello);
$app->run();

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

4. Assetic — управление ресурсами

Assetic — это фреймворк для управления ресурсам под PHP. Он позволяет удобно управляться с вашими CSS и JS-файлами. Вот как он используется:

use Assetic\Asset\AssetCollection;
use Assetic\Asset\FileAsset;
use Assetic\Asset\GlobAsset;
$js = new AssetCollection(array(
new GlobAsset('/path/to/js/*'),
new FileAsset('/path/to/another.js'),
));
echo $js->dump();

Группировка ресурсов в такой манере — хорошая идея, т.к. это позволяет ускорить работу вашего сайта. Помимо того, что уменьшается объем скачиваемых данных, убирается еще и множество ненужных HTTP-запросов (это две вещи, которые больше всего влияют на общее время загрузки страницы).

5. ImageWorkshop — манипуляция изображениями со слоями

ImageWorkshop — это библиотека с открытым исходным кодом, которая позволяет вам манипулировать изображениями со слоями.

С ее помощью вы сможете изменять размер изображений, обрезать их, делать миниатюры, наносить водяные знаки и многое другое. Пример:

// Инициализируем слой norway в изображении norway.jpg
$norwayLayer = ImageWorkshop::initFromPath('/path/to/images/norway.jpg');
// Инициализируем слой watermark в изображении watermark.png
$watermarkLayer = ImageWorkshop::initFromPath('/path/to/images/watermark.png');
$image = $norwayLayer->getResult(); // Это сгенерированное изображение!
header('Content-type: image/jpeg');
imagejpeg($image, null, 95); // Показываем jpg-изображение с 95% качеством
exit;

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

6. Snappy — PDF-библиотека

Snappy — это библиотека, которая умеет делать "снимки" или PDF-документы из URL-сдресов или HTML-документов. Она зависит от wkhtmltopdf, которая доступна под Linux, Windows и OSX. Применяется она примерно так:

require_once '/path/to/snappy/src/autoload.php';
use Knp\Snappy\Pdf;
// Инициализируем библиотеку
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
// Отображаем готовый PDF в браузере
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="file.pdf"');
echo $snappy->getOutput('http://www.github.com');

Помните о том, что вызов внешних бинарных кодов может быть запрещен вашим хостинг-провайдером.

7. Idiorm — Легкая ORM-библиотека

Idiorm — это легкая ORM-библиотека и средство быстрого построения PDO-запросов. C этой библиотекой вы забудете про утомительное написание SQL-запросов:

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();
$user->first_name = 'Jamie';
$user->save();
$tweets = ORM::for_table('tweet')
->select('tweet.*')
->join('user', array(
'user.id', '=', 'tweet.user_id'
))
->where_equal('user.username', 'j4mie')
->find_many();
foreach ($tweets as $tweet) {
echo $tweet->text;
}

У Idiorm есть "сестринская" библиотека Paris, являющаяся реализацией подхода Active Record и построенная на основе Idiorm.

8. Requests — Простые HTTP-запросы

Requests — это библиотека, которая упрощает работу с HTTP-запросами. Если вы похожи на меня и тоже не можете запомнить все те параметры, которые можно пердавать в Curl, то это для вас:

$headers = array('Accept' => 'application/json');
$options = array('auth' => array('user', 'pass'));
$request = Requests::get('https://api.github.com/gists', $headers, $options);
var_dump($request->status_code);
// int(200)
var_dump($request->headers['content-type']);
// string(31) "application/json; charset=utf-8"
var_dump($request->body);
// string(26891) "[…]"

Используя эту библиотеку, вы сможете отправлять запросы HEAD, GET, POST, PUT, DELETE и PATCH HTTP, добавлять файлы и параметры с помощью массивов и получать доступ ко всем даннным, возвращенным в ответе сервера.

9. Buzz — простая библиотека для работы с HTTP-запросам

Buzz — еще одна библиотека для удобной работы с HTTP-запросами. Вот пример:

$request = new Buzz\Message\Request('HEAD', '/', 'http://google.com');
$response = new Buzz\Message\Response();
$client = new Buzz\Client\FileGetContents();
$client->send($request, $response);
echo $request;
echo $response;

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

10. Goutte — Библиотека парсинга

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

require_once '/path/to/goutte.phar';
use Goutte\Client;
$client = new Client();
$crawler = $client->request('GET', 'http://www.symfony-project.org/');
$link = $crawler->selectLink('Plugins')->link();
$crawler = $client->click($link);
// Извлекаем данные, используя CSS-подобный синтаксис:
$t = $crawler->filter('#data')->text();
echo "Here is the text: $t";

11. Carbon — Библиотека для работы с датой и временем

Carbon — это простое API-расширение для DateTime. Оно расширеят класс некоторыми полезными методами для работы с датой и временем, например:

printf("Right now is %s", Carbon::now()->toDateTimeString());
printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver'));
$tomorrow = Carbon::now()->addDay();
$lastWeek = Carbon::now()->subWeek();
$nextSummerOlympics = Carbon::createFromDate(2012)->addYears(4);
$officialDate = Carbon::now()->toRFC2822String();
$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age;
$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London');
$endOfWorld = Carbon::createFromDate(2012, 12, 21, 'GMT');
// сравнения всегда производятся в UTC
if (Carbon::now()->gte($endOfWorld)) {
die();
}
if (Carbon::now()->isWeekend()) {
echo 'Party!';
}
echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago'

12. Ubench — Микро-библиотека для проведения тестирования приложения

Ubench — это маленькая библиотека для тестирования вашего PHP-кода. Она мониторит время выполнения скрипта и использование памяти. Пример:

use Ubench\Ubench;
$bench = new Ubench;
$bench->start();
// Выполняем некоторый код
$bench->end();
// Получаем затраченное время и память
echo $bench->getTime(); // 156ms or 1.123s
echo $bench->getTime(true); // затраченное время (тип float)
echo $bench->getTime(false, '%d%s'); // 156ms or 1s
echo $bench->getMemoryPeak(); // 152B or 90.00Kb or 15.23Mb
echo $bench->getMemoryPeak(true); // пик использования памяти в байтах
echo $bench->getMemoryPeak(false, '%.3f%s'); // 152B or 90.152Kb or 15.234Mb
// Возвращает данные об использвании памяти на финальной отметке теста
echo $bench->getMemoryUsage(); // 152B or 90.00Kb or 15.23Mb

Хорошая идея — запускать такие проверки только во время разработки приложения.

13. Validation — движок для валидации входящих данных

Validation претендует на звание самой удобной библиотеки валидации когда-либо созданной для PHP. Посмотрите сами:

use Respect\Validation\Validator as v;
// Простая валидация
$number = 123;
v::numeric()->validate($number); //true
// Валидация "цепочкой"
$usernameValidator = v::alnum()->noWhitespace()->length(1,15);
$usernameValidator->validate('alganet'); //true
// Валидация атрибутов объекта
$user = new stdClass;
$user->name = 'Alexandre';
$user->birthdate = '1987-07-01';
// Валидация его атрибутов в одной цепочке:
$userValidator = v::attribute('name', v::string()->length(1,32))
->attribute('birthdate', v::date()->minimumAge(18));
$userValidator->validate($user); //true

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

14. Filterus — библиотека фильтрации

Filterus — еще одна библиотека подобного плана, но она умеет не только производить валидацию, но и фильтровать данные, чтобы они соответствовали определенному шаблону. Например:

$f = Filter::factory('string,max:5');
$str = 'This is a test string';
$f->validate($str); // false
$f->filter($str); // 'This '

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

15. Faker — генератор случайных данных

Faker — это PHP-библиотека, которая генерирует для вас случайные данные. Это может быть полезно, когда вам нужно заполнить базу данных тестовой информацией или сгенерировать бессмысленный контент при отладке веб-приложения. Используется очень просто:

// подключаем автозагузчик
require_once '/path/to/Faker/src/autoload.php';
// Создаем экземпляр объекта Faker
$faker = Faker\Factory::create();
// генерируем данные, обращаясь к свойствам
echo $faker->name; // 'Lucy Cechtelar';
echo $faker->address;
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"
echo $faker->text;
// Sint velit eveniet. Rerum atque repellat voluptatem quia …

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

16. Mustache.php — элегантная библиотека работы с шаблонами

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

$m = new Mustache_Engine;
echo $m->render('Hello {{planet}}', array('planet' => 'World!')); // "Hello World!"

Продвинутые приемы работы можно посмотреть в официальной документации Mustache docs.

17. Gaufrette — абстрактный уровень работы с файловой системой

Gaufrette — это библиотека, которая предоставляет абстрактный уровень для работы с файловой системой. Благодаря этому вы можете работать с локальными файлами, FTP-сервером, Amazon S3 и другими файлами одинаково.

Такой подход позволяет вам не беспокоиться о том, чтобы заранее знать, каким именно образом вам потребуется получать доступ к файлам в будущем. По сути — принцип инкапсуляции в действии.

use Gaufrette\Filesystem;
use Gaufrette\Adapter\Ftp as FtpAdapter;
use Gaufrette\Adapter\Local as LocalAdapter;
// Локальные файлы:
$adapter = new LocalAdapter('/var/media');
// Опциональное использование FTP-адаптера
// $ftp = new FtpAdapter($path, $host, $username, $password, $port);
// Инициализация файловой системы
$filesystem = new Filesystem($adapter);
// Используем ее
$content = $filesystem->read('myFile');
$content = 'Hello I am the new content';
$filesystem->write('myFile', $content);

Также в данной библиотеке доступна опция кэширования.

18. Omnipay — библиотека обработки платежей

Omnipay — это библиотека обработки платежей для PHP. У нее понятный и последовательный API и с ее помощью, изучив лишь один API, вы сможете работать с большим числом различных платежных процессоров. Например:

use Omnipay\CreditCard;
use Omnipay\GatewayFactory;
$gateway = GatewayFactory::create('Stripe');
$gateway->setApiKey('abc123');
$formData = ['number' => '4111111111111111', 'expiryMonth' => 6, 'expiryYear' => 2016];
$response = $gateway->purchase(['amount' => 1000, 'card' => $formData]);
if ($response->isSuccessful()) {
// успешная оплата: обновляем базу данных
print_r($response);
} elseif ($response->isRedirect()) {
// перенаправляем на сайт платежного процессора
$response->redirect();
} else {
// оплата не прошла: показываем сообщение покупателю
exit($response->getMessage());
}

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

19. Upload — для обработки загружаемых файлов

Upload — библиотека, которая облегчает загрузку и валидацию файлов. Когда форма отправлена, библиотека может проверить тип файла и его размер:

$storage = new \Upload\Storage\FileSystem('/path/to/directory');
$file = new \Upload\File('foo', $storage);
// Валидация загружаемого файла
$file->addValidations(array(
// Убедимся, что файл имеет тип "image/png"
new \Upload\Validation\Mimetype('image/png'),
// Убедимся, что файл не больше 5 Мб. (use "B", "K", M", or "G")
new \Upload\Validation\Size('5M')
));
// Пытаемся загрузить файл
try {
// Успешно!
$file->upload();
} catch (\Exception $e) {
// Ошибка при загрузке!
$errors = $file->getErrors();

Она позволит вам сэкономить массу времени на написание утомительного кода.

20. HTMLPurifier — XSS-защита

HTMLPurifier (на github) — это библиотека для фильтрации HTML, которая защищает ваш код от XSS-атак благодаря использванию надежных "белых листов" и агрессивному парсингу URL-адресов. Кроме того, она позволяет вам отследить факт того, что разметка соответствует стандартам.

require_once '/path/to/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean_html = $purifier->purify($dirty_html);

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

21. ColorJizz-PHP — библиотека работы с цветами

ColorJizz — это маленькая библиотека, которая умеет конвертировать между собой разные форматы представления цвета и знакома с простой "цветовой арифметикой":

use MischiefCollective\ColorJizz\Formats\Hex;
$red_hex = new Hex(0xFF0000);
$red_cmyk = $hex->toCMYK();
echo $red_cmyk; // 0,1,1,0
echo Hex::fromString('red')->hue(-20)->greyscale(); // 555555

22. PHP Geo — библиотека геолокации

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

use Location\Coordinate;
use Location\Distance\Vincenty;
$coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
$coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit
$calculator = new Vincenty();
$distance = $calculator->getDistance($coordinate1, $coordinate2); // returns 128130.850 (meters; ≈128 kilometers)

Это отличный инструмент для приложений, где используются данные о местоположении чего-либо. Для получения координат вы можете использовать HTML5 Location API, Yahoo’s API (или оба, как это сделано в примере с данным приложением)

23. ShellWrap — оболочка для командной строки

ShellWrap — это библиотека, которая позволяет вам использовать мощные средства командой строки Linux/Unix в PHP с использованием приятного синтаксиса:

require 'ShellWrap.php';
use \MrRio\ShellWrap as sh;
// Выводим списком все файлы директории
echo sh::ls();
// Проверяем ветвь в git (распределенная система для управления версиями файлов)
sh::git('checkout', 'master');
// Вы также можете направить вывод одной команды в другую
echo sh::grep('html', sh::curl('http://example.com', array(
'location' => true
)));
// Создаем файл
sh::touch('file.html');
// Удаляем файл
sh::rm('file.html');
// Удаляем файл снова (не получится, и мы увидим исключение потому что файл не существует)
try {
sh::rm('file.html');
} catch (Exception $e) {
echo 'Caught failing sh::rm() call';
}

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

По материалам http://tutorialzine.com
Перевод: Дмитрий Науменко


Воспламенитель Кода: Сайт-Визитка на CodeIgniter

Хотите освоить PHP-фреймворк CodeIgniter?

Рекомендую Вам ознакомиться с курсом "Воспламенитель Кода: Сайт-Визитка на CodeIgniter".

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

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

Подробнее ->

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!

 

 
 

Смотрите также:

Позднее статическое связывание: ключевое слово static

Позднее статическое связывание: ключевое слово static

Как продлить доменное имя?

Как продлить доменное имя?

Как выбрать и зарегистрировать домен?

Как выбрать и зарегистрировать домен?

Абстрактные классы и интерфейсы в PHP

Абстрактные классы и интерфейсы в PHP

Статические методы и свойства в PHP

Статические методы и свойства в PHP

Шаблон проектирования «Декоратор»

Шаблон проектирования «Декоратор»

Шаблон проектирования «Адаптер»

Шаблон проектирования «Адаптер»

Шаблон проектирования «Фасад»

Шаблон проектирования «Фасад»

Шаблон проектирования «Наблюдатель»

Шаблон проектирования «Наблюдатель»

Управление доступом к классам: спецификаторы доступа public, private и protected

Управление доступом к классам: спецификаторы доступа public, private и protected

Работа с наследованием в PHP

Работа с наследованием в PHP

Наверх

Ошибка Call to undefined function ibase_connect() в Linux

 С такой Ошибка Call to undefined function ibase_connect() в Linux нашел решение после установки pt-get install firebird2.5-super  во всех папках PHP5 открывал подпапку conf.d , клонировал один файл любой в 20-interbase
и перепрописывал внутри вручную intebase.so

2. Пакеты стояли php-pear php-gd php-odbc php-json php-common php-cli php-cgi php-interbase php-db

3. Бывают случаи *.so найти не может переновать /usr/lib/php5/20121212+lfs/ в php_interbase.so или interbase.so

4. apt-get install php5-interbase