Блог Александра Денисюка

Программирование на PHP и вёрстка сайтов

Вконтакте Твиттер Хабрахабр

Любые предложения или замечания принимаю на
почту a@denisyuk.by или на номер +375 (29) 203-79-48.

Уменьшаем итерабельную сложность массива в PHP

Задача. Объедините массив, приведённый ниже, таким образом, чтобы по значениям результирующего массива работал поиск array_search() и in_array(). Массив должен быть проходимым за один цикл. Ключи сохранять не обязательно.
$data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

На первый взгляд кажется, что можно скормить массив в array_merge() и получить желаемый результат, но array_merge() принимает как минимум два аргумента с массивами, а у нас один. Заметьте, вложенные массивы с данными обёрнуты в ещё один массив. Это усложняет доступ к этим данным. Можно, конечно, обойти весь массив двумя foreach() и извлечь все данные, но такой подход довольно-таки топорный:

<?php

$data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

$flattened = [];

foreach ($data as $array) {
    foreach ($array as $key => $value) {
        $flattened[] = $value;
    }
}

var_dump($flattened);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9]

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

<?php

$data = [ // <- съест call_user_func_array()
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]; // <- съест call_user_func_array()

call_user_func_array('array_merge', $data);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9]

Начиная с версии 5.6 мы можем распаковать массив-обёртку, если перед передаваемым аргументом с массивом данных при вызове функции array_merge() подставим многоточие. Читайте подробнее про списки аргументов переменной длины. В нашем случае массив будет распакован и вложенные в него массивы объединятся:

<?php

$data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

array_merge(...$data);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9]

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

<?php

///////
// 1 //
///////
if ( !function_exists('array_flatten') ) {

    /**
     * Объединяет все массивы в один без сохранения ключей
     *
     * @see https://gist.github.com/intedinmamma/246665
     *
     * @param  array $data
     * @param  array $flattened
     *
     * @return array
     */
    function array_flatten(array $data, array $flattened = [])
    {
        foreach($data as $value) {
            is_array($value)                               ?
            $flattened = array_flatten($value, $flattened) :
            $flattened[] = $value;
        }

        return $flattened;
    }
}

///////
// 2 //
///////
if ( !function_exists('array_flatten') ) {

    /**
     * Объединяет все массивы в один с сохранением ключей
     *
     * @see http://brandonwamboldt.github.io/utilphp/#array_flatten
     *
     * @param  array $data
     * @param  bool  $preserve_keys
     * @param  array $flattened
     *
     * @return array
     */
    function array_flatten(
        array $data,
        bool $preserve_keys = true,
        array $flattened = []
    ) {
        array_walk_recursive($data, function ($value, $key) use (&$flattened, $preserve_keys) {
            $preserve_keys && !is_int($key) ?
            $flattened[$key] = $value       :
            $flattened[] = $value;
        });

        return $flattened;
    }
}

///////
// 3 //
///////
if ( !function_exists('array_flatten') ) {

    /**
     * Объединяет все массивы в ООП-стиле с сохранением ключей или без
     *
     * @see http://stackoverflow.com/a/12205381
     *
     * @param  array $data
     * @param  bool  $use_keys
     *
     * @return array
     */
    function array_flatten(array $data, bool $use_keys = false)
    {
        $flattened = new \RecursiveIteratorIterator(
            new \RecursiveArrayIterator($data)
        );

        return iterator_to_array($flattened, $use_keys);
    }
}


Пример для любой из функций array_flatten():

<?php

$data = [
    [1, 2, 3],
    [4, [5, 6, 7], 8],
    [9, [10, 11], 12],
];

array_flatten($data);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
24 апреля   php   массив

Как сократить вероятность до 50% при четырёх вариантах ответа

Предположим, перед вами тест из четырёх вариантов ответа, из которого только один верный. Вам необходимо извлечь из этих вариантов правильный. Как это сделать, когда вы не знаете ответа на поставленный вопрос в тесте? Можно, конечно же, сразу выбрать один на удачу, но тогда вероятность успеха будет один к четырём или 25%, а это очень низкие шансы:

    1) Ответ А 25%
    2) Ответ Б
    3) Ответ В
    4) Ответ Г

Ещё можно вычеркнуть сразу два варианта ответа и выбрать что-то одно из оставшихся, но, в таком случае, вероятность не сократится до 50%, как хотелось бы, а останутся прежние 25%. На самом деле, этот сценарий забирает у вас ещё 50% дать правильный ответ и остаётся 12,5%, т. к. тот один из двух вариантов миновал смысловую оценку, но это невозможно отобразить математически, значит будет всё-таки 25%, а не 12,5%.

Когда вы по порядку загружаете варианты ответа и пытаетесь сопоставить с заданным вопросом, то ваш предыдущий опыт ранжирует их по степени наибольшего соответствия. Это явление происходит в фоне и вы, скорее всего, даже не догадываетесь об этом. По ощущениям какой-то ответ нравится больше, а какой-то меньше. Я бы не стал сразу выбирать тот, который манит сильнее всего. Перед выбором ещё нужно провести небольшой фокус.

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

    1) Ответ А 25%
    2) Ответ Б 25%
    3) Ответ В
    4) Ответ Г

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

    1) Ответ А
    2) Ответ Б
    3) Ответ В
    4) Ответ Г

Как его найти? Всё очень просто, начинайте искать ответ в самом вопросе. Я не советую слушать логику, а искать какие-то зацепки варианта ответа за вопрос: ключевые слова, похожие смысловые ряды, странные названия или то, чего никогда не слышал. Кому-то легче искать неправильный вариант и вписывать правильный. Уж какая стратегия вам больше по душе, разберитесь сами. Но никогда не ставьте ответ на халяву, старайтесь собрать как можно больше подтверждений и только потом выдать ответ.

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

Закладки разработчика сайтов — выпуск #5

Продолжаю разгружать свои закладки из браузера. Этот дайджест будет состоять не из 12 ссылок в виде сетки и превьюшками как раньше, а в виде длинного списка, т. к. полезных ссылок для веб-разработчика накопилось действительно много. Из него я просмотрел 90% и всего лишь 5% применил на практике. В этом списке получилось 62 ссылки, ещё столько же висит в браузере. Самые лучшие опубликую в следующем дайджесте. Подписывайтесь: Твиттер RSS

PHP

    Что должен знать Junior разработчик
    Upgrade до Middle PHP-разработчика за 3 месяца
    Как пережить полный конец обеда, или безопасность в PHP
    Classes vs. Namespaces
    Как сделать свой сервис коротких ссылок
    PHP: неправильный путь

Архитектура

    Проектирование сущностей предметной области
    Как два программиста хлеб пекли
    Гексагональная архитектура
    Шпаргалка по SOLID-принципам с примерами на PHP

 
Composer

    Оптимизация загрузчика Composer
    Чем отличается install от update в Composer
    Шпаргалка по командам Composer

 
Git

    Git How To — интерактивный тур по Git
    Пошаговый тренажёр для Git
    [Видеокурс] Быстрый старт Git
    Git: меньше теории, больше практики
    Подробнее о файле .gitignore

 
Open Source

    Как прокачать свой open-source проект, используя бейджики качества кода
    Setting up a PHP project with PHPUnit, Travis and Codeclimate
    Нумерация версий программного обеспечения
    Построчный разбор лицензии MIT

 
БД

    Резервное копирование данных в MySQL
    Как думать на SQL
    Active Record против Data Mapper для сохранения данных

 
Тестирование

    Автоматизированное тестирование
    Unit-тестирование в сложных приложениях
    Разработка через тестирование
    Простое написание тестов — это не TDD
    Yii2 и мои попытки запустить первый Unit-тест

 
HTTPS

    Как перейти на HTTPS и не потерять поисковый трафик
    Настройка HTTPS и HTTP/2 в Nginx
    Переход на HTTPS: инструкция по переезду в Яндекс и Google

 
NetBeans

    Настройка Netbeans: все фишки, о которых вы могли не знать
    Настройка Netbeans для PHP-программиста
    Хитрости и трюки Netbeans на живых примерах

 
Фронтенд

    Гид по вёрстке адаптивных писем
    Свойства для выравнивания всего и их новые тайны
    Проблема выбора структуры документа
    Asynchronous vs Deferred JavaScript
    Сжатие и оптимизация изображений на сайте за один клик

 
Мануалы

    Что люди читают, почему, когда и как
    План обучения джедаев
    Вёрстка — это не тупо
    Шаблоны проектирования
    Паттерны проектирования с примерами на PHP
    Справочник «Паттерны проектирования»
    Паттерны работы с базой данных
    PHP: правильный путь
    Перевод стандартов PSR-0, PSR-1, PSR-2, PSR-3, PSR-4
    Frequently Asked PHP Questions
    Основы Symfony 3 и не только
    Zend Framework 3 на русском
    Eloquent ORM на примерах
    Справочное руководство Doctrine2
    Разъяснение HTTP2

 
Холивары

    [Опрос] Какой PHP-фреймворк вы используете
    Вносите изменения в код понемногу
    Инструменты машинного дизайна
    Как улучшить свой стиль программирования
    Как стать Senior/Middle программистом
    Эго в профессии программиста

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

Отдельная почта для «спама»

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

У меня получилась одна основная почта a@denisyuk.by для коммуникации с реальными людьми и вторая, отдельная почта, rb@inbox.ru для «спама». Эту «спамную» почту я очистил от нежелательных подписок и откровенное говно перенёс в папку «Спам». Теперь они не должны меня больше беспокоить. Ещё в эту почту я добавил несколько рассылок: Большие планы, Главред, Дизайн текста, Лига читающих, Т—Ж, Финолог. Для регистрации на сайтах я так и буду дальше использовать «спамную» почту, а для общения с людьми — новую. Таким образом, я отделил мух от котлет:

    a@denisyuk.by — для общения с людьми и пересылки документов
    rb@inbox.ru — для регистраций на сайтах и email-подписок

Обычная почта во многом конкурирует с социальными сетями, хотя последние предлагают общение в realtime-режиме. На самом деле в мире бизнеса рулит почта. Её указывают на визитках, сайтах, рекламной продукции и т. д. Наличие почты на своём домене можно прировнять к хорошим часах на вашей руке. Если вы программист, дизайнер или маркетолог и у вас ещё нет своего домена, то можете завести почту на @gmail.com, так делают многие, даже те, у кого есть свой сайт. Дело в том, что они просто не хотят заморачиваться по переносу почты и её настройке, а вы возьмите и заморочьтесь.

Файл .htaccess и RewriteRule

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

Чтобы не томиться ожиданием, давайте сразу перейдём к задаче: нужно сделать перенаправление со страницы /pages/about.php на /about, т. е. убрать в адресе папку /pages и расширение .php в названии файла. Ещё нужно позаботиться о том, чтобы в конце URL не было закрывающего слеша. Вот такую файловую структуру содержит наш пример:

┌public_html/
├──pages/
│  ├──about.php
│  ├──contact.php
│  └──subscribe.php
├──index.php
└──.htaccess

В Apache для преобразования URL’ов есть специальный модуль mod_rewrite. Он отвечает за создание ЧПУ — создаёт красивые URL для PHP-скриптов на уровне веб-сервера. Его мы и задействуем для решения нашей задачи. Для начала создадим конфигурационный файл .htaccess в корне сайта со следующим содержимым, а затем я объясню как он отработает:

# Включаем mod_rewrite
RewriteEngine On

# Убираем последний слеш
RewriteRule ^(.*)/$ /$1 [L,R=301]

# Преобразуем /pages/about.php в /about
RewriteRule ^(\w+)$ /pages/$1.php [L,NC]

Командой RewriteEngine On мы включаем модуль mod_rewrite, а с помощью RewriteRule задаём правила преобразования адресов. RewriteRule просто преобразует входной URL по порядку в соответствии с регулярными выражениями. В конце каждого правила можно задать флаги для указания поведения mod_rewrite при обработке URL. Синтаксис прост как три копейки:

RewriteRule новый реальный [флаг1,флаг2,флаг3]

Давайте разберём первое правило из примера: ^(.*)/$ /$1 [L,R=301] — между маркерами ^ и $ мы указали начало строки с любого символа и любой длины (.*), где конец строки должен заканчиваться на слеш /. Всё, что заключено в круглые скобки (.*) образует группу символов, которую можно использовать через макрос $1.

Теперь все URL’ы, которые заканчиваются на слеш, будут переброшены на адрес без последнего слеша, где /$1 — все входные символы без последнего слеша. Флаг [L] останавливает чтение .htaccess, а [R=301] генерирует HTTP-код ответа сервера HTTP/1.1 301 Moved Permanently. После того, как правило отработало и Apache обрезал закрывающий слеш, файл .htaccess выполнится ещё раз и первое правило больше не отработает.

Второе правило: ^(\w+)$ /pages/$1.php [L,NC] — пользователь запросит адрес страницы, который имеет буквенное или цифровое содержимое от одного и больше символов (\w+). Это образует группу символов, которые мы подставим в адрес скрипта /pages/$1.php. По факту выполниться скрипт под другим URL. Флаг [L] останавливает .htaccess, а [NC] отменяет проверку регистра символов.

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

Флаг  Описание
———————————————————————————————————————————————————————————————————————————————
[C]   Chain — объединяет несколько правил в цепочку. Если первое правило
      цепочки не срабатывает, то вся цепочка игнорируется.

[F]   Forbidden — возвращает ошибку 403 Forbidden (запрещено).

[G]   Gone — возвращает ошибку 410 Gone (удалён).

[L]   Last — останавливает процесс преобразования, и текущая ссылка
      считается окончательной.

[N]   Next — запускает процесс преобразования с первого по порядку правила.

[NS]  NoSubreq — разрешает срабатывание правила только для настоящих
      запросов, игнорируя подзапросы.

[NC]  NoCase — отключает проверку регистра символов.

[P]   Proxy — даёт команду Apache выполнить подзапрос к указанной странице
      с использованием программного модуля mod_proxy, при этом пользователь
      ничего не узнает об этом подзапросе. Если модуль mod_proxy отсутствует,
      то произойдет ошибка.

[PT]  PassThrough — останавливает процесс преобразования и передает
      полученную новую ссылку дальше по цепочке.
      
[QSA] Qsappend — добавляет исходные параметры запроса (Query String)
      к замене. Если замена не включает в себя новые параметры запроса,
      то исходные параметры запроса добавляются автоматически. Если же
      включает, то без флага QSA исходные параметры запроса будут утеряны.

[R]   Redirect — останавливает процесс преобразования и возвращает
      результат браузеру клиента как редирект на новую страницу.
      По умолчанию передаётся HTTP-код 302 Moved Temporarily (перемещенно
      временно), но его можно изменить путём присвоения нового статуса
      через знак равенства [R=301]. В этом случае будет передан HTTP-код
      301 Moved Permanently (перемещено навсегда).

[S]   Skip — пропускает следующее правило, если текущее правило сработало.
      Можно указать количество последующих игнорируемых правил [S=2].

Полезные ссылки:

    Остальные флаги в документации Apache
    Как на самом деле работает mod_rewrite
    Описание основных флагов mod_rewrite
    Волшебный файл .htaccess
    Коллекция сниппетов .htaccess

Ctrl + ↓ Ранее