5 складних питань з PHP-тесту Академії
Щороку команда Binary Studio Academy готується до нового навчального сезону так само старанно, як і апліканти. Ми апдейтимо лекційний матеріал, оновлюємо завдання відбору. Починаємо, зазвичай, із перегляду минулорічних питання зі вступних тестів, аналізу відповідей та вдосконалення першого етапу. В іншій статті Едуард Долинський, JS коуч, докладно розглянув питання JS напрямку. Якщо ви ще не читали його статтю, дуже раджу з нею ознайомитися.
Я також відібрав 5 найбільш проблемних питань з РНР, з якими у 2018 році виникли труднощі у переважної більшості студентів, і спробував їх розібрати.
Для початку пропоную пройти тест по цим питанням:
Пройти тест
Питання Unus
Яким з перелічених методів можна отримати доступ до класів Bar з просторів імен A, A\B, A\C, в просторі імен D в PHP 7.2?
namespace A {
class Bar {
public function foo() {
return 1;
}
}
}
namespace A\B {
class Bar {
public function foo() {
return 2;
}
}
}
namespace A\C {
class Bar {
public function foo() {
return 3;
}
}
}
namespace D {
// ... here
}
a)
use A\{
Bar as ABar,
B\Bar as BBar,
C\Bar as CBar
};
$a = new ABar;
$b = new BBar;
$c = new CBar;
b)
use A\{B, C};
$a = new A\Bar;
$b = new B\Bar;
$c = new C\Bar;
c)
use A\B;
use A\C;
$a = new A\Bar;
$b = new B\Bar;
$c = new C\Bar;
d)
use A;
use A\B\Bar as BBar;
use A\C\Bar as CBar;
$a = new Bar;
$b = new BBar;
$c = new CBar;
88% відповіли неправильно.
Давайте розберемося, що саме тут відбувається. Є три класи з ім'ям Bar в різних просторах імен A, B і C. Необхідно використати всі ці три класи в просторі імен D, при цьому уникнувши конфлікту імен.
Це питання можна вирішити в один із двох способів: або імпортувати класи з перейменуванням (use A\Bar as ABar
), або використовувати ім'я класу разом з простором імені (new A\Bar
). Тут потрібно розуміти, як працює оператор use в PHP 7.
У варіантах b) і c) імпортуються простори імен B і C, але інтерпретатор нічого не знає про A, відповідно буде викинуто виняток. У варіанті d) класи з просторів імен B і C імпортуються з перейменуванням, а також імпортується простір імен А. На перший погляд, все зроблено правильно, крім того, що клас Bar
, в рядку $a = new Bar
; не було імпортовано. Відповідно, відбудеться помилка.
Правильний варіант: a), який використовує можливість PHP 7 імпортувати класи через фігурні дужки:
use A\{
Bar as ABar,
B\Bar as BBar,
C\Bar as CBar
};
Мета цього питання - перевірити знання того, як можна імпортувати класи в PHP 7 і як вирішувати конфлікти імен. Тема про простори імен стала актуальною з введенням автозавантаження класів та є невід'ємною частиною сучасних PHP проектів. Також варто відзначити, що стандарти PSR-4 й PSR-0 (останній вважається застарілим) детально описують правила автозавантаження, які реалізовані і використовуються менеджером залежностей composer.
Детальніше про простори імен можна почитати в офіційній документації PHP.
Питання Duo
Чому буде дорівнювати результат наступного виразу?
[
function (int $a) {
return function (int $b) use ($a) {
return [ $a * $b ];
};
}
][0](2)(2)[0] === ?
a) 4
b) Fatal error
c) 2
d) 0
e) 8
87% відповіли неправильно.
Такий запис виглядає заплутано, і немає жодного приводу використовувати такий код у реальних проектах, але він добре підходить для перевірки знань щодо анонімних функцій або Closure.
По-перше, створюється масив через нотацію квадратних дужок, яка була запроваджена у версії 5.4 та вважається найбільш уживаною . Далі оголошується анонімна функція, яка приймає параметр $a
і повертає іншу функцію. Функція, яка повертається, приймає параметр $b
і повертає масив з результатом множення $a
і $b
. Тут варто звернути увагу на оператор use
, який використовується для того, щоб передати $a у scope функції, що повертається. Якщо цього не зробити, то $a`буде вважатися невизначеною, і буде помилка при спробі помножити`$a
на $b
.
Далі оголошені функції викликаються, і ми отримуємо результат. Не тільки в PHP, але й в інших C-подібних мовах доступ до елементів масиву здійснюється через квадратні дужки, а виклик функцій - через круглі. Відповідно, з масиву обирається функція:
[0](2)(2)[0],
їй в параметр $a передається значення 2:
[0](2)(2)[0],
функції, що повертається, також передається значення 2:
[0](2)(2)[0],
і потім повертається масив, з якого вибирається добуток $a
і $b
:
[0](2)(2)[0].
Відповідно, правильна відповідь a), оскільки 2 * 2 = 4.
Анонімні функції принесли функціональні можливості -у мову PHP й використовуються повсюдно. Наприклад, у функціях array_filter, array_map, array_reduce
. Робота з функціями відмінно описана в документації.
Питання Tres
Що не дозволяється в PHP 7.2 вказати у якості типу, що повертається?
a) void
b) function
c) iterable
d) callable
e) int
85% відповіли неправильно.
Щоб відповісти на це питання, потрібно знати про можливості PHP 7.0 визначати у функції типи значень, що повертаються, знати типи даних PHP, а також про те, що з версії PHP 7.1 можна явно вказати, що функція не повертає ніяких значень. Ґрунтуючи відповідь на цих даних, можна зробити висновок, що правильний варіант b) function, тому що такого типу в PHP немає. А якщо необхідно вказати, що повертається функція, то вказується callable
або \Closure
.
Питання Quattuor
Виберіть спосіб виклику методу foo()
з методу bar()
, щоб, викликаючи метод bar()
на нащадку класу А (наприклад, клас B), викликався метод foo()
відповідного класу.
class A {
static function foo() {
// …
}
static function bar() {
// ... foo() ?
}
}
class B extends A {
static function foo() {
// ...
}
}
a) static::foo()
b) self::foo()
c) $this->foo()
d) (__CLASS__)::foo()
e) base::foo()
80% відповіли неправильно.
Завдання полягає в тому, щоб при виклику методу B::bar()
виконувалася логіка методу B::foo()
, а не A::foo()
, в якому метод bar()
визначено.
Варіанти a) self::foo()
або d) (__CLASS __)::foo()
однакові. Буде використаний метод foo()
класу A, а не B, як очікується в завданні. Так відбувається, тому що ключове слово self
вказує на той клас, в якому воно використовується. Тобто при спадкуванні динамічне заміщення методу працювати не буде. Це стосується і константи __CLASS__
, яка містить ім'я того класу, в якому вона використовується.
Якщо взяти варіант c) $this->foo()
, то буде викинуто виняток, бо ключове слово $this
належить тільки до екземпляру класу (або об'єкту), а bar()
- статичний метод.
Ключового слова base
в PHP немає, отже, й e) - також неправильна відповідь.
Для вирішення цього завдання в PHP реалізовано пізнє статичне зв'язування (LSB - Late Static Binding), і для цього використовується ключове слово static. Відповідно, правильна відповідь a) static::foo()
.
LSB часто використовується, коли логіка статичного батьківського методу змінюється в залежності від дочірнього класу, на якому він викликається. При використанні late static binding не можна забувати про принципи підстановки Лісков та відкритості/закритості з SOLID.
Питання Quinque
Є два трейти, в яких збігаються деякі методи. Як в PHP 7.2 можна визначити обидва трейти в класі, якщо в цьому класі також є методи з іменами, які співпадають таким чином, щоб усі публічні методи трейтів і класу були доступні на інстансі цього класу?
trait Alpha {
function foo() {}
function bar() {}
}
trait Beta {
function foo() {}
function rar() {}
}
class Gamma {
use Alpha, Beta;
function bar() {}
}
a)
use Alpha, Beta {
Alpha::foo insteadof Beta;
Beta::foo insteadof Alpha;
}
b)
use Alpha, Beta {
Alpha::foo insteadof Beta;
Beta::foo as bFoo;
}
c)
use Alpha, Beta {
Alpha::foo insteadof Beta;
Beta::foo as bFoo;
Alpha::bar as aBar;
}
d)
use Alpha { bar as aBar; }
use Beta { foo as bFoo; }
74% відповіли неправильно. Трейт - це механізм перевикористання коду класами. Трейти необхідні для того, щоб не дублювати код або не створювати нескінченну ієрархію класів. У класі може бути використано необмежену кількість трейтів, відповідно, виникає питання вирішення конфліктів імен. Чи це не привід додати таке питання в тест?
Давайте розглянемо питання детально. У трейті Alpha
є методи foo()
і bar()
, в трейті Beta
є метод foo()
, як і в Alpha
. І в класі Omega
є метод bar()
, як і в Alpha
. Ми хочемо, щоб усі методи (Alpha::foo(), Alpha::bar(), Beta::foo(), Beta::rar(), Omega::bar()
) були доступні в класі Omega
.
У варіанті а) ми використовуємо метод Alpha::foo()
замість Beta::foo()
і в той самий час Beta::foo()
замість Alpha::foo()
. І, як наслідок, буде викинуто виняток при спробі звернутися до методу foo()
через те, що не зрозуміло, який з методів foo()
необхідно використовувати.
У варіанті b) метод foo()
буде доступний із трейту Alpha
, а до методу трейта Beta можна буде звернутися по імені bFoo()
. Але метод bar()
все ще доступний тільки з класу Omega
. Тож це також неправильна відповідь.
У варіанті d) буде Fatal Error
, тому що відбудеться колізія імен методів Alpha::foo()
й Beta::foo()
, тому що явно не було вказано, який з методів повинен бути використаний замість якого.
У варіанті c) метод Alpha::foo()
перекриває Beta::foo()
, метод Beta::foo()
доступний по імені bFoo()
, Alpha::bar()
- по імені aBar()
, Omega::bar()
і Beta::rar()
доступні за своїми іменами. Отже, варіант c) - правильний.
Не тільки трейти, але й всю тему об'єктів та класів з документації важливо знати, щоб успішно пройти перший відбірковий етап.
Підсумок
Решта питань, що стосуються PHP, мають аналогічний вигляд. Маючи базові знання PHP, а також виділивши необхідний час на підготовку й повторення, ви легко впораєтеся з тестом.
Хотілося б відзначити, що при підготовці до тесту варто звернути увагу на сучасні можливості синтаксису мови, концепції об'єктно-орієнтованого програмування, знання суперглобальних масивів, роботу зі структурами даних (масиви, списки) і рядками, а також на методи роботи з HTTP.
Краще джерело для підготовки - офіційний сайт php.net. Також в особистому кабінеті зібрано список літератури, яким не варто нехтувати. Крім того, бажано знати стандарти написання коду PSR-1 та PSR-2, хоч ці знання і не перевірятимуться на тесті.
Пишіть чистий код та хай щастить з тестом.