Pull to refresh

Руководство по собеседованию на вакансию PHP-программиста

Reading time 22 min
Views 254K
Вездесущий… это определённо то слово, которым можно описать язык PHP по отношению к вебу. Он действительно вездесущ. В настоящее время, среди всех серверных языков программирования, PHP используется наиболее широко. На нём написаны более 80% сайтов, при этом следующий по популярности ASP.NET со своими 17% остаётся далеко позади.



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

Именно в этом и заключается основная проблема поиска высококвалифицированных PHP-разработчиков. Относительно низкий порог вхождения и 20 лет развития языка привели к тому, что PHP-программисты стали такими же вездесущими, как и сам PHP. Многие из них могут на законных основания утверждать, что «знают» язык. Однако разработчики, которые действительно являются экспертами по PHP, способны создавать куда более функциональное, надёжное, масштабируемое и простое в сопровождении программное обеспечение.

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

От переводчика: данный текст представляет собой перевод и адаптацию с небольшими дополнениями статьи The Insider's Guide to PHP Interviewing, ссылка на которую была опубликована в последнем июньском дайджесте по PHP. Я постарался максимально передать смысл оригинала, добавил несколько пояснений, значительно увеличил количество ссылок на документацию, а также добавил ссылки на статьи с хабра и на другие источники. Важное отличие от оригинала: добавлена навигация по вопросам, часть кода и текста скрыта под спойлеры.

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

Обратите внимание, что мы сфокусировали внимание на современных версиях PHP (5.3 и старше), но также есть отсылки к концепциям и функциям, существующим уже достаточно давно, а значит знакомым всем квалифицированным PHP-разработчикам.

Список вопросов


  1. Расскажите о замыканиях в PHP. Приведите примеры, когда, почему и как они могут быть использованы?
  2. Объясните, с какой целью и как используется ключевое слово global. Приведите пример, когда его применение целесообразно и когда нет.
  3. Расскажите о пространствах имён в PHP и о том, почему они полезны.
  4. Что такое типажи (traits, трейты)? Опишите их основные характеристики и расскажите, чем они полезны. Приведите пример объявления типажа и класса, использующего несколько типажей.
  5. Расскажите, как связаны между собой php://input и $_POST и как получить доступ к потоку php://input?
  6. Назовите не менее пяти суперглобальных переменных, имена которых начинаются с $_, и дайте им определение. Расскажите об их связи с переменной $GLOBALS.
  7. Объясните назначение и применение магических методов __get, __set, __isset, __unset, __call, и __callStatic. Когда, как и почему их стоит использовать (или не использовать)?
  8. Опишите несколько структур данных из стандартной библиотеки PHP (SPL). Приведите примеры использования.
  9. Чему будет равен $x после выполнения выражения $x = 3 + "15%" + "$25"?
  10. Объясните назначение ключевого слова static при вызове метода или обращении к свойству. Расскажите, когда и зачем его нужно использовать, а также чем оно отличается от ключевого слова self. Приведите пример.
  11. Расскажите о внутреннем устройстве массивов в PHP?
  12. В чем различие между ArrayAccess и ArrayObject?
  13. Что такое генераторы? Когда их можно использовать вместо итераторов и обычных массивов? Для чего нужны ключевые слова yield и send?
  14. Перечислите ключевые отличия между версиями PHP 5.3, 5.4, 5.5.


Ключевые концепции и парадигмы


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

Вопрос: Расскажите о замыканиях в PHP. Приведите примеры, когда, почему и как они могут быть использованы?

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

Первый строительный блок для замыкания – это анонимные (лямбда) функции, т.е. такие функции, у которых нет ассоциированных с ними имён. Например:

// 2-ой аргумент для array_walk - анонимная функция
array_walk($array, function($dog) {
    echo $dog->bark();
}); 

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

// объявляем анонимную функции и ассоциируем её
// с переменной $dogs_bark
$dogs_bark = function($dog) {  
    echo $dog->bark();
}
array_walk($array, $dogs_bark);

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

Пример кода
class Dog {
    public function bark() { echo 'woof'; }
}

$dogs_bark = function($dog) use (&$collar) { // связываем по ссылке
    if ($collar == 'fitsWell'){
         echo $dog->bark(); // 'woof'
    } else {
         echo 'no bark';    // ошейник ($collar) слишком туго затянут
    }
};

$dog = new Dog;

$collar = 'fitsWell'; // внешняя переменная
$dogs_bark($dog); // 'woof'

$collar = 'tight';
$dogs_bark($dog); // 'no bark'


Способность получить доступ ко внешним переменным в пределах замыкания особенно полезна при использовании функций высшего порядка. Возьмём, например, функцию array_walk($array, $calback), которая, как и другие подобные функции, позволяет обойти переданный ей набор переменных и обработать их специфическим способом. Эта функция обходит массив $array и на каждой итерации вызывает анонимную функцию ($callback), передавая ей только значение текущего элемента и его ключ. Поэтому использовать переменную $collar без замыкания и конструкции use не получится. Конечно, мы можем воспользоваться ключевым словом global, но это приведёт к бессмысленному засорению глобального пространства имён переменной, которая нужна только в данном конкретном контексте.
Замыкания обладают дополнительными объектно-ориентированными возможностями. Начиная с версии PHP 5.4, в интерфейсе класса Closure появились новые методы: bind() и bindTo(), которые можно использоваться для привязки новых объектов к замыканию. Например:

Closure::bindTo($newthis, $newscope); 

Этот метод дублирует замыкание и связывает его область видимости с новым объектом таким образом, что внутри замыкания переменная $this станет ссылкой на $newthis в объектном контексте. Давайте изменим функцию $dogs_bark так, чтобы она использовала переменную $this, а затем привяжем её к объекту $dog.

// объявляем замыкание, но не связываем его с объектом
$dogs_bark = function() {
    echo $this->sound;  // где sound - свойство объекта $this
};
$new_dog = new Dog();
// создаем новое замыкание и привязываем его к объекту $new_dog
$new_closure = $dogs_bark->bindTo($new_dog);
$new_closure();   // выводит значение свойства $sound

Привязка замыкания к переменной и получение доступа к $this – достаточно мощная возможность. В частности, мы можем присвоить замыкание свойству объекта, по существу, превратив его в метод этого объекта.

$dog = new Dog();
$dog->closure = $dogs_bark;
$dog->closure();

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

Вопрос: Объясните, с какой целью и как используется ключевое слово global. Приведите пример, когда его применение целесообразно и когда нет.

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

Рассмотрим простой пример:
class Dog {
    function bark() {
        global $sounds;
        return $sounds->bark();
    }
} 


Код демонстрирует появление скрытой зависимости класса Dog от глобальной переменной $sounds. Конечно, бывают случаи, когда это оправданно (допустим, в системе существует единственный набор звуков, который вообще никогда не изменяется), но гораздо лучше явно передать $sounds объекту класса Dog в конструкторе, и хранить и использовать его в пределах этого экземпляра:
Пример кода
class Dog {
    protected $sounds; 
    function __construct($sounds) {
        $this->sounds = $sounds;
    }

    public function bark() {
        return $this->getSounds()->bark();
    }

    public function getSounds() {
        return $this->sounds;
    }
}


Но, как часто бывает в программировании, никогда не говори никогда. Существует ряд надёжных и стабильных продуктов, написанных на PHP, которые активно пользуются глобальными переменными: Wordpress, на котором работает около 20% всех сайтов (и это число растёт из года в год), Joomla! 1.5 (да, она до сих пор еще широко распространена) и iLance Enterprise Auction system – это все примеры успешных проектов, в течение многих лет использующих глобальные переменные. Точно так же и у вас могут возникнуть ситуации, в которых глобальные переменные будут уместны, однако к их применению нужно подходить осторожно.
К списку вопросов

Вопрос: Расскажите о пространствах имён в PHP и о том, почему они полезны.

Одно из объектно-ориентированных нововведений в PHP 5.3 – пространства имён. Как следует из названия, пространство имён определяет такую область видимости в программе, внутри которой имена классов, интерфейсов, функций, переменных и констант не будут вызывать конфликтов с элементами, обладающими такими же именами в другом пространстве имён.

До PHP 5.3, классам часто давали длинные имена, чтобы отразить в их названии структуру пакетов и избежать конфликтов имён. Предположим, что у нас есть класс Dog, определенный в модели приложения Dog_Pound. Без пространства имён полное имя класса выглядело бы следующим образом:

class Dog_Pound_Model_Dogs { // ух, попробуй выговори
    function getDogs();
}

Явное задание пространства имён, в области видимости которого будут определены все элементы (классы, переменные и т.д.), позволяет разработчику решить эту проблему. При использовании пространства имён, приведенное выше многословное название класса можно заменить на следующее:

namespace dog_pound\model;  // указываем текущее пространство имён
class Dogs {   // объявляем только "Dogs" в dog_pound\model
    function getDogs();
}
$dogs = new Dogs; // класс создаваемого объекта определён однозначно,
// т.к. в текущем пространстве имён он один имеет такое имя

Также поддерживаются относительные ссылки на пространства имён. Например, когда вы находитесь в пространстве dog_pound, вы сможете создать экземпляр класса Dogs вот таким образом:

$dogs = new model\Dogs  // только одна "model" определена в dog_pound

Кроме того, элементы из одного пространства имён могут быть импортированы в другое и использоваться в нём непосредственно (т.е. без ссылок на исходное пространство имён). Этого можно добиться с помощью ключевого слова use:

namespace this\new\namespace;  // текущее пространство имён

// импортируем Dogs из пространства dog_pound\model
use dog_pound\model\Dogs;

$dogs = new Dogs;

К списку вопросов

Вопрос: Что такое типажи (traits, трейты)? Опишите их основные характеристики и расскажите, чем они полезны. Приведите пример объявления типажа и класса, использующего несколько типажей.

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

Пара примеров объявления типажей:
trait Movement {
    public function topSpeed() {
        $this->speed = 100;
        echo "Running at 100 %!" . PHP_EOL;
    }
    public function stop() {
        $this->speed = 0;
        echo "Stopped moving!" . PHP_EOL;
    }
}

trait Speak {
    public function makeSound(){
        echo $this->sound . PHP_EOL; 
    }
}


Теперь воспользуемся ключевым словом use, но не для импорта пространства имён, а для включение типажей в определение класса:
Пример кода
class Dog {
    use Movement, Speak;  // теперь у класса Dog есть функционал из этих типажей
    protected $sound;
    
    function __construct() {
        $this->sound = 'bark';
    }
}

$dog = new Dog();
$dog->topSpeed();  // из типажа Movement
$dog->stop();      // из типажа Movement
$dog->makeSound(); // из типажа Speak


К списку вопросов

PHP на пальцах


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

Вопрос: Расскажите, как связаны между собой php://input и $_POST и как получить доступ к потоку php://input?

Говоря простым языком, $_POST – это суперглобальный массив, представляющий проанализированное и отформатированное тело запроса, отправленного на сервер методом post.
Пример текста запроса
POST /php-hiring-guide/php-post.php HTTP/1.1
Host: toptal.com
Referer: http:///toptal.php/php-hiring-guide/php.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 63
article_name=PHP Hiring Guide&tag_line=You are hired!&action=Submit


Доступ к телу запроса можно получить через входной поток таким же образом, как читаются любые файлы:
$input = file_get_contents("php://input"); 

К списку вопросов

Вопрос: Назовите не менее пяти суперглобальных переменных, имена которых начинаются с $_, и дайте им определение. Расскажите об их связи с переменной $GLOBALS.

Список суперглобальных переменных
$_POST – ассоциативный массив, содержащий пары ключ-значение, отправленные на сервер методом post.
$_GET – ассоциативный массив, содержащий пары ключ-значение, отправленные на сервер методом get.
$_REQUEST – объединение пар ключ-значение из $_POST и $_GET.
$_SERVER – представляет параметры веб-сервера, имеющие отношение к выполнению программы.
$_ENV – содержит значения, связанные с сервером (хостом) и его конфигурацией.
$_SESSION – ассоциативный массив, хранящий значения переменных сессии между переходами по страницам и запусками приложения.
$_COOKIE – предоставляет доступ к переменным, сохраненным в кукисах на клиенте.
$_FILES – специальный массив для входных данных, полученных при отправке на сервер файлов методом post.

Суперглобальная переменная $GLOBALS – родственник ключевого слова global. В массиве $GLOBALS хранятся все переменные, доступные в глобальной области видимости, в том числе и суперглобальные массивы. Например, доступ к $_ENV можно получить вот таким образом: $GLOBALS['_ENV'];
К списку вопросов

Вопрос: Объясните назначение и применение магических методов __get, __set, __isset, __unset, __call, и __callStatic. Когда, как и почему их стоит использовать (или не использовать)?

Первые четыре метода в нашем списке используются для перегрузки свойств объекта. Они позволяют определить, каким образом будет взаимодействовать внешний мир со свойствами, объявленными с модификатором видимости private или protected, либо вообще отсутствующими у объекта.

Пример кода
// свойство 'whiskers' не объявлено в классе Dog,
// но обрабатывается методом __get:
function __get($name) {
    if ($name == 'whiskers') {
        // создаем единственный whiskersService для экземпляра
        if (! isset($this->whiskersService)) {
            $this->whiskersService = new Whiskers($this);          
        }
        // при обращении к 'whiskers' будет выполнена загрузка whiskersService
        return  $this->whiskersService->load();
    }
}


Теперь можно просто обратиться к свойству 'whiskers':

$hairs = $dog->whiskers;


Используя метод __get для перехвата ссылок на свойства, внешне похожие на публичные, мы способны скрыть детали реализации этих свойств.
Метод __set применяется аналогично:

Пример кода
function __set($name, $value) {
    if ($name == 'whiskers') {
        if ($value instanceOf Whisker) {
            $this->whiskersService->setData($value);
            return $this->whiskersService->getData();
        } else {
            throw new WhiskerException("That's not a whisker");
            return false;
        }
    }       
}


Изменение значения свойства 'whiskers' будет выглядеть так:

$dog->whiskers = $hairs; 

Это выражение приведет к автоматическому вызову метода __set() с передачей ему в качестве первого параметра имени свойства ('whiskers') и правой части оператора присвоения в качестве второго.

Наконец, __isset и __unset завершают квартет методов для перегрузки свойств. Каждый из них принимает только один параметр – имя свойства, и вызываются при выполнении операций isset() и unset() над этим свойством.
Пример кода
function __isset($name) {
    if ($name == 'whiskers') {
        return (bool) is_object($this->whiskersService) &&
                                !empty($this->whiskersService->getData());
    }
}
function __unset($name) {
    if ($name == 'whiskers' && is_object($this->whiskersService)) {
        // мы не хотим полностью останавливать сервис
        $this->whiskersService->reset();
    }
}


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

Прим. переводчика: __call вызывается при обращении к члену класса в контексте объекта, а __callStatic при обращении к статическому члену класса. В качестве аргументов, они оба принимают имя $name вызываемого метода и массив $args с переданными методу $name параметрами.

Например, вот такая реализация __call приведет к тому, что при обращении к любому «невидимому» методу будет выполнена полезная нагрузка на whiskersService:

public function __call($method, $args) {
    return $this->whiskersService->load();
}

Метод __callStatic работает таким же образом и принимает те же аргументы, однако он вызывается при обращении к «невидимому» методу не в контексте объекта, а в статическом контексте. Если мы определим этот метод, то получим возможность обрабатывать вызовы, подобные ClassName::notVisibleMethod().

public function __callStatic($method, $args) {
    if (!is_object(static::$whiskersService)) {
        static::$whiskersService = new Whiskers(__CLASS__);
    }
    return static::$whiskersService->load(); 
}

$hairs = Dog::whiskers();

В приведённых выше примерах мы инкапсулировали реализацию whiskers от внешнего мира, сделав его единственным объектом, доступным таким способом. Клиенты определенных нами методов и свойств ничего не знают о базовом классе whiskersService и о том, как вообще Dog хранит свои данные.

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

Используя магические методы, следует помнить, что получаемые выгоды имеют свою цену. Во-первых, эти методы выполняются медленнее, чем обращение к явно определенным публичным свойствам, а также медленнее установщиков и получателей. Во-вторых, они препятствуют применению многих полезных средств, таких как рефлексия, автодополнение в IDE, утилиты автоматической документации (например, PHPDoc). Если эти вещи имеют значение, то, возможно, стоит определить публичные методы и свойства явно. Как и во многих других случаях, на вопрос о том, стоит ли использовать магические методы, однозначно ответить нельзя. Достоинства и недостатки нужно оценивать в каждом конкретном случае.
К списку вопросов

Вопрос: Опишите несколько структур данных из стандартной библиотеки PHP (SPL). Приведите примеры использования.

Процесс разработки на PHP по большей части связан с получением и обработкой данных из разных источников, таких как базы данных, локальные файлы, удаленные API и т.д. Разработчики тратят очень много времени на организацию данных, их получение, перемещение и обработку. Наиболее используемая структура для представления данных на PHP – это массив. Однако в некоторых случаях массивы не годятся для решения задач из-за недостаточной производительности и избыточного потребления памяти, и поэтому требуются более подходящие структуры данных.

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

Применение стандартной библиотеки PHP (Standard PHP Library, SPL) и знание её состава – та область, владение которой может подтвердить компетентность PHP-разработчика. Если у кандидата имеется приличный опыт работы с SPL, то, скорее всего, он сможет успешно работать над вашим приложением, вне зависимости от используемых в вашем окружении фреймворков.

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

Список структур данных из SPL с краткими пояснениями
SplDoublyLinkedList – двусвязный список. Каждый узел такого списка хранит ссылку на предыдущий и на следующий за ним узел. Представьте, что вы находитесь в очереди в банке и при этом можете видеть только человека перед вами и позади вас. Это аналогия отношения связи между элементами в SplDoublyLinkedList. Вставка элемента в список соответствует ситуации, когда кто-то влез в очередь, а вы вдруг забыли, кто стоял перед вами (и этот кто-то забыл о вас). Двусвязный список позволяет эффективно обходить и добавлять большие наборы данных без необходимости повторного хеширования.

SplQueue и SplStack очень похожи на SplDoublyLinkedList. Обе эти структуры, по сути, представляют собой двусвязные списки с разными флагами итераторов(IT_MODE_LIFO – Last In First Out – последним пришёл, первым ушёл; и IT_MODE_FIFO – First In First Out – первым пришёл, первым ушёл), которые регулируют порядок обработки узлов и что делать с этими элементами после того, как они будут обработаны. Ещё одно отличие между этими структурами заключается в том, что интерфейс SplQueue содержит более интуитивно понятные методы enqueue() и dequeue() в отличие от методов push() и pop() у SplStack.

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

SplMaxHeap и SplMinHeap — конкретные реализации абстрактного класса SplHeap. SplMaxHeap реализует метод compare() таким образом, чтобы дерево было отсортировано в порядке убывания значений узлов, а SplMinHeap – в порядке возрастания значений.

SplPriorityQueue – очередь, похожая на SplHeap, но в отличие от SplHeap сортировка осуществляется на основании значения свойства priority (приоритет), заданного для каждого узла.

SplFixedArray – массив фиксированной длины, индексами которого могут быть только целые числа. Эти ограничению обеспечивают более высокую скорость обработки массива, которая достигается, в том числе, благодаря тому, что в SplFixedArray нет хеширования ключей элементов при их добавлении (в отличие от обычных массивов).

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

Также о структурах данных из SPL можно почитать на хабре.
К списку вопросов

Вопрос: Чему будет равен $x после выполнения выражения $x = 3 + "15%" + "$25"?

Правильный ответ – 18. Давайте разберёмся почему.

PHP поддерживает автоматическое приведение типов, основанное на контексте, в котором используется переменная.

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

Зная это, мы можем увидеть, что в выражении $x = 3 + "15%" + "$25" строка "15%" будет соответствовать числу 15, а строка "$25" — нулю. Поэтому результат выражения будет равен 18 (3 + 15 + 0).

Одни программисты считают автоматическое приведение типов ценной особенностью, другие – отвратительным недостатком PHP. С одной стороны, осознанное и правильное его применение добавляет удобства разработчику (нет необходимости писать код для конвертации «15» в 15 перед применением арифметической операции). С другой стороны, оно может легко привести к трудноулавливаемым ошибкам, если будет задействовано случайно (т.к. не будет выдано никаких предупреждений и ошибок).
К списку вопросов

Вопрос: Объясните назначение ключевого слова static при вызове метода или обращении к свойству. Расскажите, когда и зачем его нужно использовать, а также чем оно отличается от ключевого слова self. Приведите пример.

В PHP, начиная с версии 5.3, реализовано позднее статическое связывание. Термин «позднее связывание» означает, что ключевое слово static связывает метод или свойство (к которому происходит обращение через static::) не с тем классом, в котором был определён использующий его метод, а с тем, в котором этот метод был вызван во время выполнения. Кстати, название «статическое связывание» не совсем корректно, т.к. static допустимо использовать не только для обращения к статическим членам класса.

Пример кода
class Dog {
    static $whoami = 'собака';
    static $sound = 'лает';

    function makeSounds() {
        echo self::makeSound() . ', ';
        echo static::makeSound() . PHP_EOL;
    }

    function makeSound() {
        echo static::$whoami . ' ' . static::$sound;
    }
}
Dog::makeSounds();


Результат выполнения этого кода не удивляет: «собака лает, собака лает».
Расширим пример:

class Puppy extends Dog {
    static $whoami = 'щенок';
    static $sound = 'гавкает';

    function makeSound(){
        echo static::$whoami . ' скулит';
    }
}

Puppy::makeSounds();

Вы можете удивиться, но результат выполнения этого кода: «щенок гавкает, щенок скулит». Чтобы понять, почему это происходит, давайте снова посмотрим на метод makeSounds() в классе Dog.

В методе makeSounds() сначала вызывается self::makeSound(). self:: всегда указывает на контекст того класса, в котором происходит обращение через него (в данном случае это класс Dog). Поэтому self::makeSound() всегда будет приводить к версии метода makeSound() из класса Dog. Это справедливо и в том случае, когда происходит вызов Puppy::makeSounds(), т.к. в классе Puppy нет собственного метода makeSounds(), и поэтому вызывается метод makeSounds() из класса Dog. В результате, при вызове Puppy::makeSounds() выполняется self::makeSound(), что приводит к появлению текста «щенок гавкает».

Затем в методе makeSounds() класса Dog вызывается static::makeSound(). static:: обрабатывается иначе, чем self::. При обращении через static:: используется версия метода, определённая в контексте вызвавшего этот метод класса во время выполнения (в данном случае, это класс Puppy). Следовательно, вызов static::makeSound() приводит к выполнению версии метода makeSound() из класса Puppy. Именно по этой причине при вызове из Puppy::makeSounds() метода static::makeSound() выводится текст «щенок скулит».

К списку вопросов

Под капотом PHP


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

Вопрос: Расскажите о внутреннем устройстве массивов в PHP?

С массивами связана значительная часть процесса разработки на PHP. Они хорошо подходят для тех случаев, когда вам необходима итерируемая структура данных. Изнутри массивы представлены, как и многие другие структуры данных, в виде хэш-таблиц. PHP написан на C, в котором нет ассоциативных массивов – массивы в C могут иметь только целочисленные индексы. Для трансляции индексов PHP-массива в целочисленные индексы массива на C применяется хэш-функция, преобразующая индексы PHP-массива (и целочисленные, и строковые) в целые числа. Значения элементов массива располагаются в получившейся хеш-таблице.
Сложность процесс поиска элементов по хэш-ключам составляет O(1) (в нотации большое О) благодаря тому, что для нахождения элемента не требуется итерация по хешу (хеш-функция возвращает значение, точно указывающее на положении элемента в таблице).

Прим. переводчика: Здесь я сознательно исключил из текста понятие бакета (bucket, корзина), т.к. автор оригинального текста не даёт никаких пояснений на его счет. Если вам действительно интересно устройство хеш-таблиц, то рекомендую к прочтению вот этот материал.

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

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

Вопрос: В чем различие между ArrayAccess и ArrayObject?

ArrayAccess – это просто интерфейс, требующий определения следующих методов: offsetGet, offsetSet, offsetExists, и offsetUnset.

ArrayObject – класс, реализующий интерфейс ArrayAccess. Удобство ArrayObject заключается в том, что к его свойствам можно получать доступ таким же образом, как к обычному массиву через оператор [], например: $dogs['sound'], или через метод offsetGet('имя свойства'), например: $dogs->offsetGet('sound').
Подробнее об использовании объектов как массивов.
К списку вопросов

Вопрос: Что такое генераторы? Когда их можно использовать вместо итераторов и обычных массивов? Для чего нужны ключевые слова yield и send?

Генератор внешне похож на обычную функцию, и обладает некоторым сходством с замыканиями. При первом вызове в контексте итератора (например, в цикле foreach), генератор возвращает объекта класса Generator, который, в свою очередь, реализует интерфейс Iterator. Значения для итерации генерируются в реальном времени, что избавляет от необходимости загружать весь набор данных в память, получая значения по мере необходимости. Из этого следует, что итерация по результату осуществляется так же, как по массиву, не считая некоторых отличий:

  1. итерация направлена только вперед, нельзя вернуться к предыдущему значению или к началу. Вызов метода rewind() выбросит исключение;
  2. скорость выполнения итерации ниже, чем в случае с предварительно заполненным массивом, т.к. для получения следующего значения генератор выполняет какие-то действия;
  3. крайне эффективное использование памяти.


Невозможность возврата значения через оператор return – это одно из основных отличий генератора от других функций или замыканий. Вместо него для передачи значения из генератора применяется ключевое слово yield (например, yield $dog). Указание значения после return приведет к синтаксической ошибке, а применение пустого оператора – простой способ остановить работу генератора.

Оператор yield приостанавливает выполнение и возвращает текущее значение в контекст структуры, использующей генератор. Эта структура может отправить информацию обратно в генератор во время его выполнения с помощью метода send (например, $generator->send(-1);).

Пример кода с использованием генератора
Сначала объявим сам генератор:

function dog_generator() {
    foreach (range(0, 2) as $value) {
        // некоторый ложный источник данных
        $dog = DogHelper::getDogFromDataSource($value);
        // перехватываем input от send или используем $dog
        $input = (yield $dog);    
        if ($input == -1) {
            return; // останавливаем генератор
        }
    }
}

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

А теперь напишем код, использующий объявленный генератор:

// получаем экземпляр генератора и присваиваем его переменной
$generator = dog_generator();
foreach ($generator as $dog) { // $dog - это результат вызова yield
    echo $dog . PHP_EOL;
    // мы хотим найти терьера
    if ($dog == 'Terrier') {
        // отправляем входные данные генератору через yield
        $generator->send(-1);
        echo 'Мы нашли терьера, останавливаем просмотр';
    }
}

Если запустить этот код, то он прекратит своё выполнение после обнаружения терьера:

Далматинец
Терьер
Мы нашли терьера, останавливаем просмотр


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

Общая картина


Вопрос: Перечислите ключевые отличия между версиями PHP 5.3, 5.4, 5.5.

Версия PHP 5.3 была выпущена в июне 2009 года, но до сих пор широко распространена. Основные новшества: замыкания, лямбда-функции, пространства имён и позднее статическое связывание.

Релиз PHP 5.4 состоялся в марте 2012 года. В нём были представлены типажи (трейты), возможность объявления массива с коротким синтаксисом [] (например, ['шит-цу', 'ротвейлер']). Ко всему прочему, в этой версии была убрана опция register_globals.

PHP 5.5 был выпущен в июне 2013 года, и это текущий релиз. В этой версии появились генераторы (и связанное с ними ключевое слово yield), в инструкцию try/catch добавлен блок finally, на замену APC Cache пришел включённый по умолчанию OpCache (базируется на Zend Optimizer). Также был расширен синтаксис литералов массивов (['шит-цу', 'ротвейлер'][1] вернёт 'ротвейлер'), и добавлена удобная возможность обращения к символам в строке как к элементам массива (например, '6e5d4'[0] вернёт «6»).
К списку вопросов

Подведём итоги


Найти PHP-разработчика достаточно просто, а вот найти действительно серьёзного PHP-програмиста – весьма непростая задача. Вопросы, представленные в этом руководстве, могут стать полезным инструментом, который позволит вам определить тех, кто овладел языком на первоклассном уровне. Поиск таких кандидатов оправдывает вложенные в него усилия, т.к. они, несомненно, окажут значительное позитивное влияние на производительность вашей команды и на результаты её работы.

Ссылка на оригинал
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+50
Comments 107
Comments Comments 107

Articles