Public, private и $this в ООП на PHP
Внутри классов, перед методами и свойствами, можно встретить слова public, private. Разберёмся для чего они нужны. А также посмотрим как обратиться к методу или свойству изнутри класса.
Переменная $this внутри класса
На реальных проектах сайтов используется множество классов. И часто возникает необходимость обратиться к свойству или методу класса изнутри его самого. Конечно, можно написать «new . » и создать новый объект класса внутри класса, но зачем это делать, если мы уже в этом классе? Есть ругой выход — специальная переменная $this , которая работает внутри классов. Продемонстрируем способ её применения на примере получения значения свойства:
string; > > $a = new Mouse; $b = $a->Name(); echo $b; ?>
В результате выполнения этого кода будет распечатано слово «Мышь».
Как можно догадаться, через $this можно обратиться не только к свойству, как в приведённом примере, так и к методу внутри класса. Продемонстрируем это:
public function Description()< return $this->Name(); > > $a = new Mouse; $b = $a->Description(); echo $b; ?>
Модификаторы доступа (public, private)
Внутри классов можно встретить слова public и private — это модификаторы доступа. Они нужны для того, чтобы блокировать или разрешить доступ к методу или свойству извне. То есть если установить «private», то будет доступ только изнутри класса, с помощью $this. При этом нельзя будет вызывать метод или свойство за пределами класса. Продемонстрируем это на примере:
> $a = new Mouse; $b = $a->Name(); echo $b; ?>
Если выполнить такой код, то PHP выдаст следующую ошибку:
[Error] Call to private method Mouse::Name() from context ''
Давайте модифицируем пример так, чтобы оставить «private», но выполнить функцию «Name()»:
Name(); > private function Name() < return 'Мышь'; >> $a = new Mouse; $b = $a->Description(); echo $b; ?>
Посмотрите внимательно на пример: у функции «Name()» всё так же остался модификатор доступа «private», который не даёт обратиться к методу извне. Но код успешно выполнится, потому что обращение к «Name()» идёт через функцию «Description()», в которой стоит «$this->Name();».
Модификаторы public и private нужны для того, чтобы запретить доступ к тем методам и свойствам, которые не должны быть публичными. К примеру, представьте что есть свойство, которое возвращает пароль доступа к системе. Очевидно, что если программист доспустит ошибку, то эта секретная информация может стать публичной. Поэтому лучше задать ей модификатор «private».
Правилом хорошего тона программирование является использование «private» для новых методов и свойств. А если в процессе разработки потребуется сделать запрос к ним извне класса, то «private» меняется на «public». Но изначально лучше везде ставить «private».
ООП в PHP
Следует учитывать, что в PHP несколько упрощенная реализация ООП (объектно-ориентированное программирование). Поэтому, когда речь идёт об ООП как абстрактной парадигме, то следует использовать какой-то более серьёзный язык, вроде Java, С++ или Object Pascal. Потому что на этих языкам можно посмотреть практическую реализацию принципов ООП. В PHP программисты пытаются подражать другим ЯП, что в итоге приводит к излишней сложности и путанице, поскольку язык сам по себе не позволяет сделать «как в теории».
Главная проблема такого (спагетти) кода в том, что у него низкая читабельность и слишком большая запутанность. Там где нужно выполнить какой-то один метод, подтягивается еще десяток классов. При этом каждый класс в отдельном файле, что может окончательно свести с ума даже опытных программистов.
Смысл ООП как раз и заключается в том, чтобы упростить разработку. Если программа после использования принципов ООП стала более запутанной, то это говорит либо о том, что ООП никуда не годится (что навряд ли), либо о том, что программист что-то неверно понял в этом самом ООП (скорее всего).
Попробуем разобраться. Кратенько без «воды».
В ООП главное — это объекты, которые в PHP есть не что иное как переменные абстрактного типа данных (который задаёт программист).
Такой тип данных может содержать поля и методы/функции. Типы данных могут быть простыми, например integer, string, boolean. Но могут быть и более сложными, например array. В Паскале есть специальный тип record (запись), которая содержит поля данных произвольного типа.
Я иногда буду приводить примеры из Паскаля, поскольку в нём очень хорошо показывается что к чему, и он прост для понимания, даже тем, кто его не изучал.
type stud = record fio: string[20] bal: 6..15; sum: real; end;
В PHP нет аналога record, хотя к нему близок массив из-за особенностей типизации. Если запись сделать «активной», то есть снабдить собственными функциями, то получится тип данных, который в ООП называется класс class. В классе можно задавать область видимости.
class Stud < // поля данных private $fio; public $bal; // методы private function in() < >public function out() < >>
Объект — это экземпляр класса (читай типа данных). Но, в отличие от обычного присваивания, объекты создаются через специальную php-конструкцию new. В ней происходит инициализация (выделяется память и т.п.) и возвращается ссылка на готовый объект. Дальше мы получаем доступ к полям и методам класса.
$s = new Stud; $s->ball = 12; $s->out();
По сути методы класса — это те же самые функции, но есть одно большое отличие — это область видимости. Если обычные функции всегда (почти) имеют глобальную область видимости, то методы ограничены только классами. Это позволяет создавать разные классы с одними и теми же именами методов (и полей).
class A < public function sum() < >> class B < public function sum() < >> $a = new A; $b = new B; $a->sum(); $b->sum();
Выделяют специальные статические методы, с помощью которых можно получить доступ к методам класса без инициализацию через new(). Обычно такие классы представляют собой набор функций, которые могут быть выполнены сами по себе. Если делать без static, то вначале пришлось бы выполнить инициализацию объекта.
class Ar < public static function sum($a, $b) < return $a + $b; >> echo Ar::sum(2, 6);
Способность объекта скрывать свою реализацию, называется инкапсуляцией. То есть класс можно рассматривать как черный ящик, о котором мы знаем только о его полях и методах. Это позволяет разделить код на части, что немного напоминает концепцию модулей в других языках: сам класс выносится в отдельный файл, а в другом он уже используются.
В ООП главное не только объекты, но и связи между ними. Основной механизм взаимоотношения между классами — наследование, когда один класс может быть наследником другого. С помощью наследования строится иерархическая цепочка классов. Это имеет большое практическое значение.
Например у нас есть библиотека для работы с базой данных, реализованная в виде класса DB. Пусть это будет даже сторонняя библиотека, которую мы не можем изменить. В процессе работы, нам понадобилось добавить новый метод. Вместо того, чтобы переписывать родительский класс, будет достаточно создать новый в виде потомка с помощью extends.
class DB < . >class MyDB extends DB < public function myselect() < . >> $db = new MyDB(); $db->myselect();
С другой стороны, класс DB тоже может измениться, например появятся новые функции, а значит они автоматически станут доступны у всех потомков.
При построении сложных классов не всегда бывает возможность заранее определить конкретную реализацию. Например при разработке несколькими программистами необходимо заранее договориться что в таком-то классе будут такие-то методы. Для решения таких задач используются интерфейсы — это практически те же классы, только не имеющие реализации.
interface IDB < public function select(); public function where(); public function join(); >class QBuilder implements IDB < public function select() < . >public function where() < . >public function join() < . >>
Класс QBuilder должен реализовать все методы, описанные в интерфейсе IDB. В «больших» языках программирования, интерфейсы помогают детально проработать не только иерархию классов, но и необходимую функциональность (и приведение типов). В PHP потребность интерфейсов достаточно мала, поскольку как правило один интерфейс используется только одним классом (то есть интерфейс лишняя сущность).
В некоторых случаях интерфейс можно рассматривать как «каркас» класса, который обладает большей читабельностью, чем реализованный класс.
Ещё одной разновидностью классов являются абстрактные классы. Это такие классы, у которых не может быть создан объект. С практической точки зрения абстрактный класс можно рассматривать точно также как и интерфейс. Разница между ними по сути в том, что ваш класс должен реализовывать интерфейс, а абстрактный класс нужно расширять (наследовать). При этом в PHP интерфейсы могут наследовать другие интерфейсы (но не классы).
abstract class ADB < public function select() < >public function where() < >public function join() < >> class QBuilder extends ADB < public function select() < . >public function where() < . >public function join() < . >>
Примером использования может быть класс базы данных. Поскольку реализация будет зависеть от конкретной базы (MySQL, PDO, Sqlite и т.п.), то используя в вершине иерархии интерфейс или абстрактный класс, можно сделать несколько потомков, гарантируя, что у всех будут те же методы (и тип), при этом расширив их специфичными методами.
interface IDB < public function select(); public function where(); public function join(); >class QMySQL implements IDB < public function select() < . >public function where() < . >public function join() < . >public function get_db() < . >> class QSqlite implements IDB < public function select() < . >public function where() < . >public function join() < . >public function get_file() < . >> class QPDO implements IDB < public function select() < . >public function where() < . >public function join() < . >public function connect() < . >>
Еще одним важным принципом ООП является полиморфизм . Вначале рассмотрим что такое «настоящий» полиморфизм (полиморфизм) — это способность функции обрабатывать данные разных типов.
На примере Паскаля это может выглядеть так:
program Adhoc; function Add(x, y : Integer) : Integer; begin Add := x + y; end; function Add(s, t : String) : String; begin Add := Concat(s, t); end; begin Writeln(Add(1, 2)); Writeln(Add('Hello, ', 'World!')); end.
В данном примере функция Add объявлена несколько раз с разными входными параметрами. Компилятор будет смотреть какой входящий тип данных и выполнять подходящую функцию. Реализуется это за счёт того, что компилятор использует «сигнатуру» функции, в которую входит не только название, но и типы принимаемых данных.
В PHP есть два ограничения. Первое — не может быть двух одноименных функций и второе — динамическая типизация, когда компилятор сам решает какой тип данных использовать (в PHP 7/8 идёт работа в сторону строгой типизации).
Нужно понимать, что PHP никогда не создавался как объектно-ориентированный. Это всегда был функциональный скриптовый язык, в задачу которого входило быстро выполнить небольшой код-вставку в HTML. ООП в PHP был добавлен по сути только в 5-й версии (2004 год), да и то в слишком простой форме. Более-менее говорить об ООП в PHP можно лишь с версии 5.3 (2009 год). Поэтому PHP не просто в роли «догоняющего» — в нём есть довольно существенные внутренние ограничения, которые не позволяют вывести поддержку того же полиморфизма на уровень Java или C++.
Поскольку в PHP функции не могут быть перегружены (то есть мы не можем создать две одноименные функции), а значит на этом уровне ad-hoc-полиморфизм просто отсутствует. Точно такая же ситуация и в методах классов — невозможно создать одноименную функцию.
Поэтому в PHP полиморфизм рассматривается как переопределение (или перекрытие), то есть когда потомок переопределяет метод родительского класса.
class A < function out() < echo 'Class A (out)'; >function in() < echo 'Class A (in)'; >> class B extends A < function out() < echo 'Class B (out)'; >> $b = new B; $b->out(); // "Class B (out)" $b->in(); // "Class A (in)"
Здесь мы видим то же самое наследование, но при этом есть возможность переопределить класс родителя. Это достигается за счёт того, что в PHP все методы виртуальные. В некоторых других языках для переопределения следует явно указывать «виртуальность».
Часто приходится встречать выражение «Один интерфейс — много реализаций» (сказал Бьёрн Страуструп, автор C++). Выражение на само деле подходит лишь к «настоящему» полиморфизму, то есть не реализуемый в PHP. Часто приходится видеть совершенно бездумное раздувание кода, когда класс разбивается на абстрактный класс и интерфейс (потому что об этом сказал Страуструп. ). То есть вместо одной сущности получается сразу несколько. При этом классы получают сложную логику наследования.
В качестве примера посмотрим на переделанный код, построенный якобы «правильно»:
interface IProto < function out(); >abstract class Out implements IProto < function out() <>> class A extends Out < function out() < echo 'Class A (out)'; >function in() < echo 'Class A (in)'; >> class B extends A < function out() < echo 'Class B (out)'; >>
PHP по сути тот же «Algol, только с классами». Сложная иерархия никак не способствует ни читабельности кода, ни его выполнению (компилятору нужно ещё разгрести все эти завалы связей).
Поскольку в PHP «ограниченный» полиморфизм, часто используются разные приёмы, которые призваны нивелировать такие неудобства. В качестве примера приведу код, показывающий как всё-таки можно получать данные разных типов через один.
interface IDraw < public function draw($text); >class Circle implements IDraw < public function draw($text) < echo $text; >> class Square implements IDraw < public function draw($text) < echo $text; >> class Disp < public function get($class) < if (class_exists(ucfirst($class))) return new $class; >> $disp = new Disp(); $circle = $disp->get('Circle'); $square = $disp->get('Square'); $circle->draw('This is Circle'); $square->draw('This is Square');
Классы Circle и Square содержат конечную реализацию методов. В нашем случае это просто вывод текста. Оба класса реализуют интерфейс IDraw с той целью, чтобы их методы совпадали.
Класс Disp выполняет роль диспетчера и содержит метод get, который по входящему параметру ищет существующий класс и если есть, возвращает на него ссылку. Таким образом объекты $circle и $square можно получить через Disp, при том, что с ним нет никакой связи. Можно даже сделать Disp статическим, чтобы упростить его использование без new.
Данный алгоритм может использоваться например в роутинге, когда можно выделить обработчик запроса в разные классы. Скажем адрес сайт/about будет вызывать класс About, а сайт/contact — класс Contact.
$segment = $GET. ; // данные из URL $page = $disp->get($segment);
Очевидно, что если необходимо будет «перехватить» новый адрес, например, news, то достаточно будет сделать лишь класс News, без правки существующего кода.
Класс Disp можно усложнить так, чтобы он сразу принимал не только класс реализации, но и выполнял его методы.
В простых случаях эмуляция полиморфизма реализуется на уровне метода/функции, где явно проверяется входящий тип данных. Приведенный выше пример из Паскаля на PHP можно реализовать примерно так:
function add($x, $y) < if (is_string($x) and is_string($y)) < return $x . $y; >elseif (is_numeric($x) and is_numeric($y)) < return $x + $y; >else < return 0; >> echo add(2, 6); // 8 echo add('Hello ', 'World!'); // Hello, World!
То есть PHP не позволяет создать две функции add(), поэтому входящий тип определяется уже внутри одной функции. На уровне классов используется похожий подход с использование instanceof (особенно для классов с общим интерфейсом).
Итого
Главная проблема использования ООП в PHP только в том, что многие решили, что php-код должен соответствовать принятым стандартам в других ООП-языках. Сам по себе язык PHP очень мощный и покрывает почти все потребности разработчиков. Там где можно спокойно обойтись без сложных классов имитирующих Java, лучше использовать более простой и понятный код в рамках базовых возможностей PHP.
PHP — ООП или процедурный подход
PHP один из самых популярных скриптовых языков программирования. Почти 60% веб серверов используют PHP.Миллионы веб-сайтов и веб-приложений разрабатываются на PHP каждый месяц.
PHP изначально разрабатывался как простая замена языку Perl, и уже спустя пару лет он стал чрезвычайно мощным и популярным. Язык PHP, сам по себе очень похож на ANSI C.
Одна из причин почему PHP стал таким популярным это его короткий период обучения.
Изучение PHP абсолютно не тяжёлое занятие, особенно если вы хорошо знакомы с синтаксисом Java или C.
Так как писать PHP скрипты достаточно просто, любой может написать PHP код без соблюдения каких-либо соглашений и смешивая уровень представления с бизнес логикой (это одна из основных причин существования большого количества неуправляемых проектов). Потому что в PHP не обязательно строгое соответствие соглашений написания кода, с годами когда проект становится всё больше и больше, он превращается в громадное неуправляемое приложение.
ООП или Объе́ктно-ориенти́рованное программи́рование хорошо применяется в практике программирования для более лёгкого создания управляемых проектов.
Процедурный подход подразумевает написание программного кода без использования объектов. Процедурное программирование заключается в написании кода с или без подпрограмм.
ООП обучает любой язык программирования более хорошему программному коду и используется, для получения более высокой производительности и написания больших проектов, не боясь запутаться в их управлении. ООП даёт вам возможность создавать объекты которые можно будет использовать многократно, для того что бы вы или другие разработчики могли использовать их в своих проектах не переделывая их снова и снова. ООП убирает барьеры и сложности в написании и управлении большими приложениями.
PHP позволяет нам писать приложения 2мя разными способами, первый — процедурный, а второй объектно ориентированный. Если вы до сих пор не поняли разницу между этими двумя подходами, давайте посмотрим на эти куски кода — один и тот же пример написанный разными подходами.
Процедурный:
$user_input = $_POST[‘field‘];
$filtered_content = filter($user_input); //user input filtering
mysql_connect(«dbhost»,«dbuser»,«dbpassword»); //database
mysql_select_db(«dbname»);
$sql = «some query»;
$result = mysql_query($sql);
while ($data = mysql_fetch_assoc())
process ($data);
>
process_user_input($filtered_content);
А вот тот же кусок кода с использованием ООП:
$input_filter = new filter();
$input_filter->filter_user_input(); //filter the user inputs
$db = new dal(«mysql»); //data access layer
$db->connect($dbconfig);//we wre using mysql
$result = $db->execute($sql);
ReportGenerator::makereport($result); //process data
$model = new Postmodel($filter->get_filtered_content());
$model->insert();
Если внимательно посмотреть на эти 2 куска кода то можно заметить, что код с использованием ООП более читабельный и легче для восприятия.
Код с ООП организован лучше потому что в нём понятно какой объект чем обрабатывается. Большие приложения написанные на процедурном подходе становится практически не возможно воспринимать уже после выхода нескольких версий. Конечно вы можете следовать жёстким правилам написания программного кода, но они утверждены миллионами разработчиков которые знают что это не даст вам в конечном итоге управляемости и юзабилити проекта, если вы не используете в своей программе ООП.
Почти все большие приложения написаны с использованием Объектно ориентированного
подхода.
Исходя из изложенного выше, можно вынести преимущества использования ООП:
ООП был создан что бы облегчить жизнь разработчикам. Используя ООП вы можете разбить ваши большие проблемы на маленькие проблемы, которые решать гораздо проще.
Основное требование ООП: всё что вы хотите сделать — делайте объектами. Объекты это отдельная маленькая часть кода которая может объединять данные и свойства вместе. В приложениях все объекты взаимодействуют друг с другом.
ООП может быть рассмотрен лучше с разных сторон, особенно когда вам важно время разработки и последующее развитие приложения.
Основные преимущества использования ООП можно выразить как:
* Повторное использование: Объект это логический объект у которого есть комплект свойств и методов и он может взаимодействовать с другими объектами.. Объект может быть абсолютно независимым или может зависеть от других объектов. Объект обычно создают для решения специфических поставленных проблем. Следовательно когда другие разработчики сталкиваются с похожими проблемами, они могут подключить ваш класс к своему проекту и использовать его не боясь что он нарушит процесс их разработки. Это позволяет избежать DRY, что расшифровывается как Don’t Repeat Yourself ( не повторяйся). В процедурном или модульном программировании, повторное использование возможно только в совокупности.
* Рефакторинг: Когда вам необходимо в проекте использовать рефакторинг, ООП предоставляем вам максимум преимуществ, так как все объекты это маленькие элементы и содержат свои свойства и методы как часть себя. По этому использовать рефакторинг относительно легко.
* Расширяемость: Если вам необходимо расширять функциональность вашего проекта, вы можете достичь лучших результатов при помощи ООП. Одна из основных функциональностей ООП это расширяемость. Вы можете использовать рефакторинг объектов что бы добавить функциональность. Работая над этим, вы по прежнему можете сохранить
прежнюю совместимость объекта — следовательно вы можете прекрасно работать и с прежним кодом. Или же вы можете расширить объект и создать абсолютно новый, который будет содержать все необходимые свойства и методы родительского объекта от которого происходит новый, а потом уже добавить в него новые функции. Это называется “наследование” и это очень важная возможность ООП.
* Поддержка: объектно-ориентированный код легче поддерживать так как
он следует весьма жёстким соглашениям написания кода и пишется в самопоясняющейся форме.
К примеру, когда разработчик дополняет, перерабатывает код, или отлаживает его, он может легко найти внутреннюю структуру кода и поддерживать код время от времени. Более того, когда в вашем окружении работает команда разработчиков ООП может быть лучшим решением так как вы можете распределять ваш код между членами команды, после разбития его на маленькие части. Эти маленькие части могут быть разработаны как отдельные объекты, следовательно разработчики могут работать практически независимо друг от друга. В конечном итоге объеденить все части в одно приложение не составит большого труда.
* Эффективность: Идея ООП в действительности была разработана для повышения эффективности и облегчения процесса разработки. Несколько шаблонов проектирования разработаны что бы создавать более эффективный и хороший код.
Более того в ООП вы можете вы можете размышлять над вашими решениями в более удобной форме чем в процедурном подходе. Поскольку вы разбиваете вашу проблему на несколько маленьких проблем и вы находите решение для каждой из них отдельно, большая проблема решается сама по себе.
Авторский перевод из книги Object Oriented Programming with PHP5
P.S мой первый хабратопик, если понравится буду переводить книгу дальше, как по мне довольно интересная и содержательная
Зачем нужно ООП
Класс — описание логики поведения (через свойства и методы).
Объект — экземпляр класса, который содержит данные и может выполнять логику класса.
Интерфейс — функции, которые должен реализовать класс.
Ещё есть наследование (когда один класс расширяет другой), полиморфизм (когда класс-потомок меняет логику поведения родителя), инкапсуляция (красивое слово, которое означает «сокрытие логики» или «давай запихаем эту хрень так, чтобы её никто никогда не нашёл»). Не расстраивайтесь, если вы не знаете точного определения какого-то из модных программерских слов, в 99% случаев можете сказать, что это «хрень какая-то«, не ошибётесь.
Важно помнить: в ООП данные хранятся в объектах, а логика в классах. Объекты всегда создаются из класса.
Код без ООП ¶
Представим себя на месте программиста, которому нужно сделать простую задачу: вывести список пользователей.
/** * Скрипт, который выводит пользователей в виде списка */ // Данные пользователей $users = [ ['name' => 'Вася'], ['name' => 'Петя'], ['name' => 'Коля'], /* ещё миллион записей */ ]; // Показываем список foreach($users as $user) < echo $user['name'], PHP_EOL; >
Код выше выведет список всех пользователей, даже если их миллион. Если мы хотим работать со списком в более удобном формате, то нужно сделать постраничную навигацию:
// Текущая страница и лимит записей на странице $page = 1; $limit = 2; // Показываем пользователей на текущей странице foreach (array_slice($users, ($page - 1) * $limit, $limit) as $user) < echo $user['name'], PHP_EOL; >
Давайте добавим в вывод ещё тэги HTML, чтобы список был более красивым:
Для полного счастья здесь не хватает разделения чётных и нечётных строк. Давайте добавим:
Как видите, код получился, мягко говоря, не очень красивый и понятный. Такой код называют «смешанный» или «грязный» (ну или «говнокод»). Тут переплетается высокоуровневая логика (показ списка пользователей, постраничная навигация) и низкоуровневое форматирование (HTML-тэги, экранирование, обращение к массиву и так далее).
А что будет, если нам скажут, что имя пользователя должно быть ссылкой? А что будет, если вместо списка потребуется таблица со множеством колонок? А что будет, если нам надо будет выгрузить список в PDF? А что будет, если . (в этом месте можно подставить любые обстоятельства).
Если код оставить в том виде, в котором он сейчас есть, то рано или поздно он перестанет быть управляемым и начнёт диктовать разработчикам свои условия. То есть вместо того, чтобы добавлять новые плюшки, программист будет больше думать, как бы не сломать старое.
Философия ООП ¶
Первое, что нужно понять, ООП это лишь один из способов организации кода. С помощью объектов мы разделяем большую логику на на много маленьких, чтобы нам легче было управлять своим же кодом. Это значит, что не нужно во все места совать классы и интерфейсы. Если ООП не упрощает код, а только запутывает, то это неправильное ООП! Каждый раз спрашивайте себя: «А что я упрощу, если сделаю объект?»
Но давайте вернёмся к примеру выше и посмотрим, как можно упростить себе жизнь.
Первое, на что нужно обратить внимание, это то, что мы работаем с пользователем как с массивом:
$user['name']
А что будет, если свойств у пользователя будет много или ключи массива будут меняться? Тогда придётся где-то вести документацию, какие ключи можно использовать, а какие нет. И кто-то должен будет контролировать, чтобы программисты использовали только правильные ключи. Оказывается, эту задачу можно переложить на среду исполнения, если использовать ООП.
Давайте создадим класс пользователя, который на вход принимает данные пользователя и имеет функцию для возврата имени:
/** * Пользовтель */ class User < private $data = []; function __construct(array $data = []) < $this->data = $data; > function name() < return $this->data['name'] ?? null; > >
Исправим объявление списка:
// Данные пользователей $users = [ new User(['name' => 'Вася']), new User(['name' => 'Петя']), new User(['name' => 'Коля']), /* ещё миллион записей */ ];
Теперь для доступа к имени пользователя можно использовать функцию $user->name() . Мы «спрятали» (инкапсулировали) структуру исходного массива с данными пользователя за функциями класса User . Если исходный массив поменяется, то изменения нужно будет внести только в функции класса.
Дальше решим проблему с постраничной навигацией. Поскольку базовый массив не поддерживает такую навигацию, «спрячем» его в объект, а уже объект «научим» выдавать страницы. Для начала опишем класс коллекции:
class Collection < private $array = null; function __construct(array $array = []) < $this->array = $array; > // Возвращает данные на одной странице function itemsOnPage($page, $limit) < return array_slice($this->array, ($page - 1) * $limit, $limit); > >
Изменим объявление исходных данных:
// Данные пользователей $users = new Collection([ new User(['name' => 'Вася']), new User(['name' => 'Петя']), new User(['name' => 'Коля']), /* ещё миллион записей */ ]);
Теперь можно обращаться к нашим данным так:
// Постраничный вывод пользователей foreach($users->itemsOnPage($page, $limit) as $user) < echo $user->name(), PHP_EOL; >
Осталось решить проблему с форматом вывода пользователей. Дело в том, что echo в этом цикле является низкоуровневой логикой, которую надо «прятать». Собственно, foreach также является лишним звеном в этой цепочке. В идеале логику отображения нужно вынести в шаблон и передавать ему список пользователей:
// Получаем список пользователей $users = $users->itemsOnPage($page, $limit); // Используем шаблонизатор Twig для отображения списка echo $twig->render('users.html', [ 'start' => ($page - 1) * $limit, 'users' => $users, ]);
Шаблон представляет из себя псевдо язык разметки, в котором в теории удобно делать вёрстку:
ol start=">"> for user in users %> li class=">">> li> endfor %> ol>
Основная цель таких шаблонов — разделить логику отображения от логики данных. Преимущество, которое нам дают шаблоны заметны сразу: основной код стал «чище» и проще. Ходят легенды, что подобные шаблоны могут делать дизайнеры, верстальщики, папы, мамы, кошки без участия программистов. То есть налицо разделение труда (а если вспомнить историю за 5-й класс, то это уже прогресс в развитии).
Когда нужно использовать ООП ¶
Вот ситуации, когда, на мой взгляд, ООП даёт преимущества:
- Работа с базой данных Через ООП можно «скрыть» внутреннюю структуру базы от конечного пользователя (в данном случае программиста). Если база будет меняться, то это не приведёт к переписыванию всего проекта. Если вы используете объект, вам неважно, как он хранится в базе данных.
- Сложная логика С помощью ООП можно разбить сложную логику на несколько простых классов. Преимущество в том, что можно создавать универсальный интерфейс и узкоспециализированные классы. Вам не придётся менять код, если потребуется добавить «что-то похожее вон на тот класс».
- Большой проект Ахиллесовой пятой больших проектов является сильное связывание, когда один код вызывается из множества разных частей системы. В последствии такой код становится «неприкасаемым». То есть разработчики боятся в нём что-то менять, потому что неизвестно, какие части проекта после этого отвалятся. Если использовать ООП, то с такой проблемой будет разы проще справиться. Объект можно разбить, сделать фасадом или написать тест. С обычным кодом такое сделать труднее.
- Автоматическое тестирование Поскольку объекты несут в себе конечную логику (имеется в виду, что объекты имеют конечное число свойств и методов), то их можно тестировать. Вызвал один метод, проверил результат, вызвал другой метод, проверил результат и так далее. Автоматические тесты помогают избежать ошибок и за счёт этого улучшают скорость и качество разработки.
Когда НЕ нужно использовать ООП ¶
Я часто слышу мнение, что ООП нужно использовать везде и всегда. Нет! ООП должно помогать программисту писать код, а не превращать его жизнь в ад из «высоких стандартов опп».
Ситуации, когда ООП не даст вам преимуществ:
- Однофайловые скрипты Бывают ситуации, когда нужно сделать что-то быстро и одноразово. Например, выгрузку или отчёт. НЕ нужно тратить время на продумывание объектов и разнесение логики. Сделайте всё в одном файле. Это будет быстрее и проще для изменения. Удалить один файл легче, чем выковыривать потом классы по всему проекту.
- Отчёты Три раза подумайте перед тем, как пытаться сделать «универсальный построитель отчётов». Логику в больших отчётах далеко не всегда можно описать идиомами ООП. Лучше использовать однофайловые скрипты с простой линейной логикой, чем тратить время на придумывание монстров из классов.
- SQL-билдеры Чем проще SQL, тем крепче спит программист. Если вы используете ООП для генерирования SQL, то и понятия не имеете, насколько сложным будет конечный запрос. А что ещё хуже, билдеры имеют свои баги, которые приводят к скрытым ошибкам проекта. Вообще, SQL в проектах нужен только в двух местах: отчётах и репозиториях. Но в отчётах слишком сложный SQL, чтобы использовать билдер, а в репозиториях слишком простой.
- MVC в популярных PHP-фрейморках Я уже писал о проблемах MVC в PHP. Если кратко, то использование MVC приносит больше вреда, чем пользы, из-за неправильного понимания самого шаблона. Классы-контроллеры пухнут и превращаются в монстров, которыми сложно управлять. Единственный плюс MVC, это наличие шаблонов. Но шаблоны можно подключить и без MVC,
Не забывайте, что суть программирования — постоянный баланс между технологиями и задачами, которые надо решать. ООП лишь одна из техник написания кода, которая имеет свои достоинства и недостатки. Не нужно зацикливаться на какой-то одной технике, игнорируя преимущества других. Есть ещё и функции, и старый добрый include . Самое главное, соблюдать баланс и писать простой и понятный код (используйте метод утёнка, чтобы объяснить логику своего кода).
Ну вот опять.
* сумма должна быть пропорциональна размеру вашего достоинства
