Прекрати злоупотреблять массивами в PHP

в 17:42, , рубрики: array, best practice, php, Веб-разработка, Программирование

php arrays everywhere

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

Взглянем на типичный пример ужасающего, на мой взгляд кода, злоупотребляющего массивами. Для примера, предположим, мы извлекаем данные из БД и работаем с ними посредством массива. Вот такие примеры я вижу ежедневно:

$ponds = array(
	array(
		"name" => "Breakspear",
		"size" => "large",
		"pegs" => "23",
		"amenities" => array(
			"toilets" => false,
			"shop"    => true,
		),
		"fishBreeds" => array(
			array(
				"name"    => "Bream",
				"stocked" => "2013-10-04 12:16:47",
				"number" => "100",
				"record"  => "5.4lbs",
			),
			array(
				"name"    => "Perch",
				"stocked" => "2012-02-02 05:23:32",
				"number" => "50",
				"record"  => "1.2lbs",
			),
			array(
				"name"    => "Common Carp",
				"stocked" => "2011-01-23 14:42:59",
				"number" => "10",
				"record"  => "15.4lbs",
			),
		),
	),
);

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

$ponds->getNamed("Breakspear")->getTotalStocked();

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

$ponds = new PondCollection();
 
$pond = new Pond("Breakspear");
$pond->addStockData(new StockData("Bream", 100, "2013-10-04 12:16:47"));
$pond->addStockData(new StockData("Perch", 50, "2012-02-02 05:23:32"));
$pond->addStockData(new StockData("Common Carp", 10, "2011-01-23 14:42:59"));
$pond->addAmenity(new ShopAmenity());
 
$ponds->add($pond);

Излишняя сложность

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

foreach ($ponds as $pond)
{
    foreach ($pond["fishBreeds"] as $breed)
    {
        $stockedFish += $breed["number"];
    }
}

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

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

Ты делаешь это неверно

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

Работа с наборами данных

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

class PondsCollection implements IteratorAggregate
{
    private $collection = array();
 
    public function getIterator()
    {
        return new ArrayIterator($this->collection);
    }
 
    public function add($pond)
    {
        $this->collection[] = $pond;
    }
}

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

$ponds = new PondsCollection();
$ponds->add(...);
 
foreach ($ponds as $pond)
{
    ...
}

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

Подробнее в документации:

Автор: kriptomen

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js