Switch-hash структуры и вопрос с php-собеседования


У вас бывало такое, что нужно где-то получить данные из switch-case блока, при этом это нужно единожды и это где-то нужно здесь и сейчас. Например при инициализации массива? И не хочется куда-то выносить код, заводить отдельные переменные, которые будут использованы единожды, заводить отдельные структуры и выносить все это в отдельный файл или неймспейс? Если да, то можно воспользоваться альтернативой: использовать объекты и/или ассоциативные массивы для реализации логики switch-case — так называемые switch-hash структуры.

Типичная ситуация может выглядеть как-то так в мире JS:

var switchval = 'c';
var somearr = { foo: 1, somevalue: '' };
switch(switchval) {
case 'a': somearr.somevalue = 'foo';
case 'b': somearr.somevalue = 'bar';
default : somearr.somevalue = 'defualt';
}

Немного громоздко, вам не кажется? Можно было бы написать как-то так:

var somearr = {
foo: 1,
somevalue: (function(switchval){
switch(exprvalue) {
case 'a': return 'foo';
case 'b': return 'bar';
default : return 'default';
}
})(switchval)
};

Слава самовызывающимся анонимным лямбда функциям. И такой код частенько можно увидеть в разных опенсорс проектах, на гитхабе, в npm модулях. Но если мы можем писать так, так почему тогда не избавиться от IIFE заменить их вместе с блоком switch-case на объект или массив?

Реализация switch-hash в JS

const somevar = { a: 'foo', b: 'bar' }[switchval] || 'default';

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

В случае если у вас переменная выбора является числом, вы могли бы использовать массив:

let switchval = 3;
let somevar = ['a','b'][switchval] || 'default';

Не нужно switch-case блока, не нужно функций.

Реализация switch-hash в PHP

В PHP так же можно реализовать switch-array структуры, но специфика языка добавляет свои нюансы на реализацию:

<?php
$smvr = @['a'=>'foo','b'=>'bar'][$switchval] or $smvr = 'dfault';

И точно так же в случае числовых switch-value используем не ассоциативный массив:

<?php
$smvr = @['foo','bar'][$switchval] or $smvr = 'dfault';

В целом очень похоже на JS, но есть специфика. Первое — используем символ @, который считается плохой практикой использовать, как бы… Но вопрос в том когда и зачем. Психотерапевты когда начинают лечить, первым делом добиваются того, чтобы человек принял себя таким, какой он есть. 🙂

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

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

  • || — или
  • or — или

И тут мы сразу натыкаемся на вопрос с собеседования по PHP:

В чем разница между операторами OR и || и почему выше в коде мы использовали идиоматическое OR а не символьное?

На собеседовании вам может попасться такая задачка:

<?php
$a = 0;
$b = 1;
$foo = $a || $b;
$bar = $a or $b;
var_dump($foo, $bar); // ?

Что будет выведено?

Если вы собеседуетесь на позицию php-fullstack , то вам добавят еще задачку из JS:

var a = 0, b = 1;
var foo = a || b;
console.log(foo); // ?

Разбираем по порядку.

  • В PHP: $foo = true, $bar = 0
  • В JS: foo = 1

Разбираемся с PHP.

В случае использования оператора || вычисляется сначала левая часть, так как это лево ассоциативный оператор, затем правая часть и затем вычисляется логическое ИЛИ, после чего результат присваивается в переменную.

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

$a = 0 or 1;
// Эквивалентно записи
($a = 0) || 1;

На основе этого вам может попасться вариация задачки:

$s = ($a = 0 or 1);
$s = ($a = 0 || 1);
var_dump($s, $a);

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

Итого

  • Вместо switch-case операторов можно использовать switch-hash структуры
  • В JS и в PHP результат работы оператора ИЛИ разный.
  • В JS сначала вычисляется левая часть ИЛИ и если она эквивалентна Bool(resultOfCalculation) === false, возвращается правая часть как есть. И это значение может быть присвоено в переменную. Про приведение типов и вычисления читай статью: https://medium.com/@frontman/9d6f1845ea96
  • В PHP есть 2 вида операторов ИЛИ и поведение у них отличается (|| vs or).
  • В PHP оператор “||” вычисляет сначала левую часть, затем правую и после этого вычисляет общий логический результат, который будет передан в плевую часть выражения
  • В PHP оператор OR сначала вычисляет левую часть и если она false, вычисляет правую. При этом происходит группировка левой и правой частей, в результате чего без явной группировки выражений нельзя присвоить результат логического вычисления. Эту особенность используют для реализаций условных вычислений и присвоений по аналогии с Bash скриптами. По этой же причине мы использовали этот оператор в switch-hash записи. По такой же аналогии можно работать с оператором AND и создавать короткую запись IF выражения. Например:
Пример короткого IF на bash
#!/bin/bash
[ -f 'some.txt' ] && cat 'some.txt'
-----------------------------------
Этот же эквивалент на PHP
<?php
file_exists('some.txt') and print file_get_contents('some.txt');
  • PHP и JS очень интересно могут отличаться друг от друга, что может приводить к интересным результатам. Читай статью:

https://medium.com/@frontman/fun-js-php-2-434b02ea4894

Switch-hash с использованием тернарного оператора

Логическое ИЛИ можно заменить на короткую запись тернарного оператора и тогда в PHP можно сделать такую конструкцию:

$smvr = ['a'=>'foo','b'=>'bar'][$switchval] ?? 'default';

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