От автора: объектно-ориентированный подход как в PHP, так и в любом другом языке программирования достаточно не простой, так как требует особого мышления и поэтому новичкам тяжело перестроиться после процедурного подхода, который, как правило, изучается первым. Поэтому в данной статье мы с Вами рассмотрим типичные ошибки при проектировании ООП на PHP, которые как показывает практика, неизбежны на ранних этапах изучения.
Программирование с использование объектно-ориентированного программирования, это не просто решение поставленной задачи, это реализация гибкого и модульного проекта, который, конечно же, будет удовлетворять поставленным требованиям, будет прост в поддержке и расширении. При этом, как правило, хорошо спроектированные компоненты – классы – подобного проекта, без существенных правок, могут использоваться в других приложениях. Вообще, модульность проекта, это важнейшая его особенность, так как позволяет, просто вносить изменения и дополнения.
При этом, хоть и кажется, что достигнуть вышеуказанных характеристик просто, но на деле все оказывается совсем иначе, так как из за допущенных типичных ошибок, приложение получается совсем не гибким, трудно расширяемым и редактируемым.
Поэтому давайте рассмотрим типичные проблемы при проектировании ООП в PHP.
Ошибка №1. Отсутствие наследования.
Проектируя структуру будущего проекта, как правило, новички создают огромное количество самостоятельных классов, для каждого элемента скрипта, и при этом, дублируя некоторые общие участки кода. Конечно, класс максимально должен быть независим от других классов, тем самым обеспечивается возможность его повторного использования при решении других задач. Но это тоже не выход — плодить в приложении массу повторяющегося кода. Обратите внимание на следующий пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
class IndexPage { protected $title; protected $db; public function __construct() { $this->db = new mysqli(HOST,USER,PASS,DB); } public function getContent () { $query = "Select * FROM articles"; $data = $this->db->query($query); return $data; } public function render($data) { echo "<div>"; echo $data; echo "</div>"; } } class ArticlePage { protected $title; protected $db; public function __construct() { $this->db = new mysqli(HOST,USER,PASS,DB); } public function getContent ($id) { $query = "Select * FROM articles WHERE id = ".$id; $data = $this->db->query($query); return $data; } public function render($data) { echo "<div>"; echo $data; echo "</div>"; } } |
Мы видим два самостоятельных класса, которые выводят на экран содержимое определенных страниц веб-приложения. Каждый из них успешно выполняет поставленные задачи. Но при этом, они практически идентичные за исключением метода getContent(). Поэтому, не забывайте о таком понятии как наследование, благодаря которому, Вы сможете реализовать иерархию классов в разрабатываемом скрипте, а значит все общие элементы (свойства и методы), будут вынесены в классы, стоящие уровнем выше.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Page { protected $title; protected $db; public function __construct() { $this->db = new mysqli(HOST,USER,PASS,DB); } public function render($data) { echo "<div>"; echo $data; echo "</div>"; } } class IndexPage extends Page { public function getContent () { $query = "Select * FROM articles"; $data = $this->db->query($query); return $data; } } class ArticlePage extends Page { public function getContent ($id) { $query = "Select * FROM articles WHERE id = ".$id; $data = $this->db->query($query); return $data; } } |
Как раз это и показано на примере выше. Да, количество классов увеличилось, но в результате этого, код подклассов значительно сокращен и родительский класс Page – представляет собой основное логическое ядро, в котором сосредоточена вся общая логика проекта. Поэтому еще раз повторяюсь – используйте наследование для формирования иерархий классов в структуре будущего приложения.
Ошибка №2. Отсутствие интерфейсов.
Очень часто для выполнения неких определенных действий в веб-приложении предусмотрены различные способы выполнения поставленной задачи. К примеру, для сохранения определенных сообщений пользователей, может использоваться база данных, файловая структура или механизм сессий. То есть другими словами одно и то же действие – сохранение данных, Вы можете выполнить различными способами, в зависимости от неких условий или пожелания пользователя. Соответственно для описания функционала каждого из способов, конечно же, используются отдельные классы, структура которых может быть примерно следующей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class DbSave { public $db; public function __construct() { $this->db = new mysqli($host,$user,$pass,$db); } public function saveInDataBase($data) { if($data) { $query = "INSERT INTO comments(name,message) VALUES('".$data['name']."','".$data['message']."')"; $result = $this->db->query($query); } } } class FileSave{ public function saveInFile($data) { file_put_contents('messages.txt',$data); } } |
Обратите внимание, что каждый класс предлагает собственный метод по выполнению выше поставленной задачи и, казалось бы, все должно успешно работать, но для смены способа сохранения сообщения придется создать объект требуемого класса и вызвать на исполнение соответствующий метод. То есть другими словами много чего придется переписать. Так как для использования первого класса, необходимо прописать следующее:
1 2 |
$obj = new DbSave('localhost','user','password','database'); $obj->saveInDataBase(array('name','message')); |
А для второго:
1 2 |
$obj = new FileSave(); $obj->saveInFile(array('title','text')); |
Согласитесь, что достаточно не практично. Но все можно поправить, используя интерфейс, который определит способ доступа к необходимому функционалу, а значит, все классы, которые его реализуют, должны будут переопределить данный метод.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
interface ISave { public function save($data); } class DbSave implements ISave { public $db; public function __construct() { $this->db = new mysqli($host,$user,$pass,$db); } public function save($data) { if($data) { $query = "INSERT INTO post(name,messages) VALUES('".$data['name']."','".$data['messages']."')"; $result = $this->db->query($query); } } } class FileSave implements ISave { public function save($data) { file_put_contents('file.txt',$data); } } |
Таким образом, для выбора интересующего способа сохранения сообщения пользователя, достаточно выбрать интересующий класс и создать его объект и вызвать на исполнение метод save(). То есть объекты, Вы можете менять, но доступ к необходимому функционалу так или иначе осуществляется через один единственный метод save(), который определяется интерфейсом. Что очень удобно и сводит требуемые правки к минимуму.
1 2 |
$obj = new FileSave(); $obj->save(array('title','text')); |
Ошибка №3. Повсеместное использование спецификатора доступа public.
Конечно, удобно при объявлении методов и свойств, использовать вышеуказанную область видимости и обращаться к ним напрямую из объекта, к тому же код получается кратким и удобно читаемым.
1 2 3 4 5 6 7 8 9 |
<?php class User { public $login; } $user = new User(); $user->login = 'ben'; echo $user->login; |
Данный пример хоть и решает поставленную задачу, но с точки зрения парадигмы ООП — очень плохо спроектирован. Потому как, если что то изменить в объекте, то обязательно придется вносить правки в код, который его использует.
А теперь давайте рассмотрим следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php class User { private $login; public function setLogin($login) { $this->login = $login; } public function getLogin($login) { return $this->login; } } $user = new User(); $user->setLogin('ben'); echo $user->getLogin('ben'); |
Обратите внимание, используя спецификаторы доступа, Вы запрещаете прямой доступ к определенным элементам класса — в нашем случае к свойству $login. Таким образом для того чтобы определить или получить его значение, следует использовать открытые методы setLogin() и getLogin(), а значит если что то изменить в реализации класса, код использующий его функционал менять не придется. К примеру, если нужно добавить некую логику при формировании логина пользователя, необходимо только лишь отредактировать метод setLogin().
Таким образом, мы с Вами рассмотрели типичные проблемы при проектировании ООП PHP, конечно это только наиболее часто встречаемые среди новичков и на самом деле их очень и очень много. Но не пугайтесь, ошибки – это нормально как при обучении, так и при профессиональной работе, просто с ростом Вашего опыта их количество постепенно будет уменьшаться. Более подробно тема наследования и формирования интересов рассмотрена в премиум-курсе курсе Объектно-ориентированное программирование на PHP.
На этом данная статья завершена. Всего Вам доброго и удачного кодирования!!!