Я программирую на PHP процедурно (это даже слово?) Уже около пяти лет и решил попробовать подход ООП, но столкнулся с некоторыми проблемами концепции / дизайна. Предположим, у вас есть несколько модулей в программе, каждый модуль имеет возможность перечислить, добавить, отредактировать и удалить объект. Сущность может быть … неточно, пользователь, клиент, продукт и т. Д.
Как бы вы разработали классы для управления этими сущностями?
У меня возникли две возможности:
Заранее спасибо,
Я бы создал интерфейс, определяющий ваши методы списка, добавления, редактирования и удаления. Это дает вам класс «шаблон». Если ваши классы (Пользователь, Клиент, Продукт и т. Д.) Реализуют этот интерфейс, тогда методы в интерфейсе должны быть определены в этих классах.
Это даст вам аналогичный «API» для доступа ко всем функциям каждого класса, который реализует ваш интерфейс. Поскольку каждый из перечисленных объектов содержит разные данные, детали методов будут разными и, следовательно, разделены, но интерфейс будет таким же.
В стороне:
Ваше включение «списка» в ваш список методов касается меня немного. Похоже, что вы видите свои объекты как коллекции пользователей, клиентов, продуктов и т. Д., Где, скорее всего, должен быть класс пользователя, представляющий одного пользователя , класс клиента, который представляет собой один клиент и т. Д.
С другой стороны, «список» может обрабатываться как статический метод – метод, который можно вызывать без экземпляра класса.
$bob = new User('bob'); $bob->add(); // Adds bob to the database $fred = new User('fred'); $fred->add(); // Adds fred to the database $users = User::list(); // Gives an array of all the users in the database
Так или иначе, я все равно буду обращаться с вещами.
Вам понадобится создать прочную архитектуру и структуру для управления вашей моделью данных. Это непросто и будет только усложняться по мере роста модели данных. Я настоятельно рекомендую использовать фреймворк PHP (Symfony, CakePHP и т. Д.) Или, по крайней мере, ORM (Doctrine, Propel и т. Д.).
Если вы все еще хотите сворачивать самостоятельно, я бы начал с архитектуры, подобной приведенной ниже.
Вам понадобится класс DbRecord
который используется для отдельных операций записи (например, сохранение, удаление и т. Д.). Этот класс DbRecord
будет расширен конкретными объектами и обеспечит основу для операций с базовыми объектами.
class DbRecord { public function save() { // save logic (create or update) } public function delete() { // delete logic } // other record methods } class User extends DbRecord { private $name; private $email; public function setName($name_) { $this->name = $name_; } public function setEmail($email_) { $this->email = $email_; } }
Из которых вы можете выполнять отдельные операции записи:
$user = new User(); $user->setName('jim'); $user->setEmail('jim@domain.com'); $user->save();
Теперь вам понадобится класс DbTable
который используется для массовых операций в таблице (например, чтение всех объектов и т. Д.).
class DbTable { public function readAll() { // read all } public function deleteAll() { // delete all logic } public function fetch($sql) { // fetch logic } // other table methods } class UserTable extends DbTable { public function validateAllUsers() { // validation logic } // ... }
С помощью которых вы можете выполнять операции с массивом / таблицей:
$userTable = new UserTable(); $users = $userTable->readAll(); foreach ($users as $user) { // etc }
Архитектура кода – это ключ к правильному масштабированию веб-сайта. Важно разделить модель данных на соответствующие классы и иерархию.
Опять же, по мере роста вашего сайта, может быть очень сложно управлять моделью данных вручную. Именно тогда вы действительно увидите преимущества PHP-фреймворка или ORM.
ПРИМЕЧАНИЕ. DbRecord
и DbTable
– это произвольные имена – используйте имя w / e, которое вам нравится.
Используйте свой первый метод, когда вы создаете объект многократного использования с помощью методов. Это не пустая трата времени, поскольку вы только кодируете ее один раз.
class User { function __construct() { /* Constructor code */ } function load($id) { ... } function save() { ... } function delete() { ... } }
Вы на правильном пути с «общими классами» (также называемыми базовыми классами или абстрактными классами, если их поведение NEEDS дополняется дочерними классами, прежде чем их можно будет использовать).
Подход ООП должен заключаться в том, чтобы все поведение было общим для всех объектов в базовых классах.
Если вы используете что-то похожее на ActiveRecord, у вас уже есть общий (абстрактный) интерфейс для операций create-update-delete. Используйте это в своих интересах, и пусть ваши базовые классы работают ТОЛЬКО на этих методах интерфейса. Им не нужно знать, что они обновляют Продукт или пользователь aa, им просто нужно знать, что они могут вызывать метод update () для объекта.
Но даже без использования чего-то довольно функционального, подобного AR-структуре (посмотрите Doctrine, если вы заинтересованы в очень гибкой ORM ..), вы можете использовать интерфейсы для абстрактного поведения.
Позвольте мне привести более подробный пример …
/** * Interface for all entities to use */ interface Entity { static function newEntity(); static function fetch($id); function save(); function setProperties(array $properties); function delete(); } /** * A concrete product entity which implements the interface */ class Product implements Entity { public $productId; public $name; public $price; public $description; /** * Factory method to create a new Product * * @param integer $id Optional, if you have auto-increment keys you don't need to set it * @return Product */ public static function newEntity($id=NULL) { $product = new Product(); $product->productId = $id; return $product; } /** * Factory method to fetch an existing entity from the database * * @param integer $id * @return Product */ public static function fetch($id) { // make select with supplied id // let $row be resultset if (!$row) { return NULL; // you might devise different strategies for handling not-found cases; in this case you need to check if fetch returned NULL } $product = new Product(); $product->productId = $id; $product->name = $row['name']; $product->price = $row['price']; $product->description = $row['description']; return $product; } /** * Update properties from a propreties array * @param array $properties * @return void */ public function setProperties(array $properties) { $this->name = $properties['name']; $this->price = $properties['price']; $this->description = $properties['description']; } public function save() { // save current product properties to database } public function delete() { // delete product with $this->productId from database } } /** * An abstract CRUD controller for entities */ abstract class EntityCrudController { protected $entityClass = 'UNDEFINED'; // Override this property in child controllers to define the entity class name protected $editTemplate = NULL; // Override this to set an edit template for the specific entity protected $templateEngine; // Pseudo-Templating engine for this example /** * Display the edit form for this entity * @param integer $entityId * @return string */ public function editAction($entityId) { // Fetch entity - this is not the most clean way to fetch, you should probably consider building a factory that encapsulates this. $entity = call_user_func($this->entityClass, 'fetch', $entityId); // Assign entity to your edit template, in this example I'm assuming we're using a template engine similar to Smarty // You can generate the HTML output in any other way you might like to use. $this->templateEngine->setTemplate($this->editTemplate); $this->templateEngine->assign('entity', $entity); return $this->template->render(); } /** * Update an existing entity * * @param integer $entityId * @param array $postArray * @return string */ public function updateAction($entityId, array $formArray) { // Be sure to validate form data first here, if there are errors call $this->editAction() instead and be sure to set some error information $entity = call_user_func($this->entityClass, 'fetch', $entityId); $entity->setProperties($formArray); $entity->save(); // Again, using our imaginary templating engine to display... $this->templateEngine->setTemplate($this->editTemplate); $this->templateEngine->assign('entity', $entity); $this->templateEngine->assign('message', 'Saved successfully!'); return $this->template->render(); } // Devise similar generic methods for newAction/insertAction here } /** * Concrete controller class for products * This controller doesn't do much more than extend the abstract controller and override the 2 relevant properties. */ class ProductCrudController extends EntityCrudController { protected $entityClass = 'Product'; protected $editTemplate = 'editProduct.tpl'; } // Usage example: // Display edit form: $controller = new ProductCrudController(); $htmlOutput = $controller->editAction(1); // Save product: $htmlOutput = $controller->updateAction(1, array('name' => 'Test Product', 'price' => '9.99', 'description' => 'This is a test product'));
/** * Interface for all entities to use */ interface Entity { static function newEntity(); static function fetch($id); function save(); function setProperties(array $properties); function delete(); } /** * A concrete product entity which implements the interface */ class Product implements Entity { public $productId; public $name; public $price; public $description; /** * Factory method to create a new Product * * @param integer $id Optional, if you have auto-increment keys you don't need to set it * @return Product */ public static function newEntity($id=NULL) { $product = new Product(); $product->productId = $id; return $product; } /** * Factory method to fetch an existing entity from the database * * @param integer $id * @return Product */ public static function fetch($id) { // make select with supplied id // let $row be resultset if (!$row) { return NULL; // you might devise different strategies for handling not-found cases; in this case you need to check if fetch returned NULL } $product = new Product(); $product->productId = $id; $product->name = $row['name']; $product->price = $row['price']; $product->description = $row['description']; return $product; } /** * Update properties from a propreties array * @param array $properties * @return void */ public function setProperties(array $properties) { $this->name = $properties['name']; $this->price = $properties['price']; $this->description = $properties['description']; } public function save() { // save current product properties to database } public function delete() { // delete product with $this->productId from database } } /** * An abstract CRUD controller for entities */ abstract class EntityCrudController { protected $entityClass = 'UNDEFINED'; // Override this property in child controllers to define the entity class name protected $editTemplate = NULL; // Override this to set an edit template for the specific entity protected $templateEngine; // Pseudo-Templating engine for this example /** * Display the edit form for this entity * @param integer $entityId * @return string */ public function editAction($entityId) { // Fetch entity - this is not the most clean way to fetch, you should probably consider building a factory that encapsulates this. $entity = call_user_func($this->entityClass, 'fetch', $entityId); // Assign entity to your edit template, in this example I'm assuming we're using a template engine similar to Smarty // You can generate the HTML output in any other way you might like to use. $this->templateEngine->setTemplate($this->editTemplate); $this->templateEngine->assign('entity', $entity); return $this->template->render(); } /** * Update an existing entity * * @param integer $entityId * @param array $postArray * @return string */ public function updateAction($entityId, array $formArray) { // Be sure to validate form data first here, if there are errors call $this->editAction() instead and be sure to set some error information $entity = call_user_func($this->entityClass, 'fetch', $entityId); $entity->setProperties($formArray); $entity->save(); // Again, using our imaginary templating engine to display... $this->templateEngine->setTemplate($this->editTemplate); $this->templateEngine->assign('entity', $entity); $this->templateEngine->assign('message', 'Saved successfully!'); return $this->template->render(); } // Devise similar generic methods for newAction/insertAction here } /** * Concrete controller class for products * This controller doesn't do much more than extend the abstract controller and override the 2 relevant properties. */ class ProductCrudController extends EntityCrudController { protected $entityClass = 'Product'; protected $editTemplate = 'editProduct.tpl'; } // Usage example: // Display edit form: $controller = new ProductCrudController(); $htmlOutput = $controller->editAction(1); // Save product: $htmlOutput = $controller->updateAction(1, array('name' => 'Test Product', 'price' => '9.99', 'description' => 'This is a test product'));
Конечно, многое можно улучшить .. например, вы вообще не хотите делать запрос каждый раз, когда вы вызываете fetch () в сущности, но вместо этого запрашиваете один раз и сохраняете результирующий объект в IdentityMap , что также обеспечивает целостность данных ,
Надеюсь, что это поможет, получил немного больше, чем я предполагал, но я думаю, что похвально вы пытаетесь решить эту проблему, не бросая рамки на проблему 🙂