I'm fairly new to the Zend Framework and MVC and I'm a bit confused by Zend_DB and the proper way to interact with the database.
I'm using the PDO MySQL adapter and have created some classes to extend the abstract classes:
class Users extends Zend_Db_Table_Abstract {
protected $_name = 'users';
protected $_primary = 'user_id';
protected $_rowClass = 'User';
public function getUserbyID($id) { /* code */ }
// More code here
}
class User extends Zend_Db_Table_Row_Abstract {
// Code here
}
class Widgets extends Zend_Db_Table_Abstract {
protected $_name = 'widgets';
protected $_rowClass = 'Widget';
public function getWidgetsfromUser($userid) { /* code */ }
// More code here
}
class User extends Zend_Db_Table_Row_Abstract {
public function doSomethingWithWidget() { /* code */ }
// More code here
}
There seems to be so many ways to access the DB (fetchAll(), find(), fetchAll() through adapter, insert(), createRow() and save(), select() object) that I always find myself going back to the docs to figure out what I should be doing.
SO has taught me prepared statements are the way to go, and I've been trying to use rowsets and row (should I be?), but I'm still confused as to what's the best way to interact with the database?
(apologies for the terribly open ended question)
In general, people prefer to access the database through the Table and Row objects, to match their habits of object-oriented programming.
The OO approach is useful if you need to write code to transform or validate query inputs or outputs. You can also write custom methods in a Table or Row class to encapsulate frequently-needed queries.
But the object-oriented interface is simplified, unable to perform all the types of database operations you might need to do. So you can delve deeper and run a SQL query against the Zend_Db_Adapter methods like query() and fetchAll() when you require finer control over your SQL.
This is pretty common for object-oriented interfaces to databases. An OO layer that could duplicate every SQL feature would be insanely complex. So to compromise, an OO layer usually tries to provide easy ways to do the most common tasks, while giving you the ability to go under the covers when necessary.
That's a very general answer to your very general question.
Using Zend_Db you probably don't want to get into the details of prepared statements and the like. You just want to use the model objects to do basic CRUD (Create, Read, Update and Delete). I know the Programmer's Reference Guide is extensive, but its a great introduction to Zend_Db. You may want to take a closer look at the Zend_Db_Table documentation.
But to give a quick answer to your question. Unless you need to override some default behavior you shouldn't need to extend the Zend_Db_Table_Row_Abstract. Also you can probably simplify the Users class to just be:
class Users extends Zend_Db_Table_Abstract {
protected $_name = 'users';
// Code here
}
Then to use it you would do some of things you mentioned using the following:
//Create a Users Model Object
$usersDb = new Users();
//Find the record with user_id = 4 and print out their name
$row = $usersDb->find(4);
echo $row->first_name . ' ' . $row->last_name
//Change the first name of this user to Brian
$row->first_name = 'Brian';
$row->update();
//Insert a user into the database
$data = array(
'first_name' => 'Gilean',
'last_name' => 'Smith');
$usersDb->insert($data);
//Retrieve all users with the last name smith and print out their full names
$rows = $usersDb->fetchAll($usersDb->select()->where('last_name = ?', 'smith'));
foreach ($rows as $row) {
echo $row->first_name . ' ' . $row->last_name
}
I recommend using the save method.
//Create a Users Model Object
$usersDb = new Users();
//Find the record with user_id = 4 and print out their name
$row = $usersDb->find(4);
echo $row->first_name . ' ' . $row->last_name
//Change the first name of this user to Brian
$row->first_name = 'Brian';
$row->save();
//Insert a user into the database
$newUser = $usersDb->fetchNew();
$newUser->first_name = 'Gilean';
$newuser->last_name = 'Smith';
$newUser->save();
// OR if you get your data from post or any array
$newUser = $usersDb->fetchNew();
$newUser->setFromArray($data_from_post);
$newUser->save();
the reason why i like this approach more is because this why you always have an instance of the user model and you can have custom methods in them ( ex isAdmin ) and also because you might want to ovewrite the save/insert/update function on userRow to do something before they are inserted/updated.
Related
I have the following example in which I tend to use a couple of classes, to create a simple web app.
The file hierarchy seems like this.
> cupid
- libs
- request
- router
- database
- view
- bootstrap.php
- index.php
The index.php just calls the bootstrap.php which in turn contains something like this:
// bootstrap.php
namespace cupid
use request, router, database, view;
spl_autoload_register(function($class){ /* autoload */ });
$request = new view;
$response = new response;
$router = new router;
$database = new database;
$router->get('/blog/{id}', function($id) use ($database, $view) {
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
$view->layout('blogPage', ['article'=>$article]);
});
As you can probably tell, my problem is this line:
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?", [$id]);
Which I don't want to use, and instead try a " Domain Object Model " approach.
Now, given that I will add another folder called domain, with blog.php
> cupid
- domain
- Blog.php
- libs
...
And fill blog.php with properties mapping table rows, and getter and setters ..
namespace App\Domain;
class Blog {
private $id, $title, $content, $author;
public function getTitle(){
return $this->title;
}
public function setTitle($title){
$this->title = $title;
}
...
}
My question is: Assuming my understanding of DOM is so far correct, and that I have a CRUD/ORM class, or a PDO wrapper to query the database;
"How can I tie together, i.e. the blog model with the PDO wrapper to fetch a blog inside my bootstrap file?"..
As far as a Domain Object you basically already have written one, your blog object. To qualify as a domain model all a class must to is to provide a representation along with any of the functionality of a concept within your problem space.
The more interesting problem here and the one you appear to be struggling with is how to persist a domain model. Keeping with the tenet of the single responsibility principle your Blog class should deal with being a blog post and doing the things that a blog post can do, not storing one. For that you would introduce the concept of a repository of blog posts that would deal with storing and retrieving objects of this type. Below is a simple implementation of how this can be done.
class BlogRepository {
public function __construct(\cupid\database $db){
$this->db = $db;
}
public function findById($id){
$blogData = $this->db->select("select * from blog where id = ?", [$id]);
if ($blogData){
return $this->createBlogFromArray($blogData);
}
return null;
}
public function findAllByTag($tag){...}
public function save(Blog $blog) {...}
private function createBlogFromArray(array $array){
$blog = new Blog();
$blog->setId($blogData["id"]);
$blog->setTitle($blogData["title"]);
$blog->setContent($blogData["content"]);
$blog->setAuthor($blogData["author"]);
return $blog;
}
}
Then your controller should look something like this.
$router->get('/blog/{id}', function($id) use ($blogRepository, $view) {
$article = $blogRepository->findById($id);
if ($article) {
$view->layout('blogPage', ['article'=>$article]);
} else {
$view->setError("404");
}
});
To truly be SOLID the above class should be a database specific implementation of a BlogRepository interface to adhere to IoC. A factory should also probably be supplied to BlogRepository to actually create the blog objects from data retrieved from the store.
In my opinion one of the great benefits of doing this is you have a single place where you can implement and maintain all of your blog related interactions with the database.
Other Advantages to this method
Implementing caching for your domain objects would be trivial
Switching to a different data source (from flat files, blogger api, Document Database Server,PostgresSQL etc.) could be done easily.
You can alternatively use a type aware ORM for a more general solution to this same problem. Basically this Repository class is nothing more than a ORM for a single class.
The important thing here is that you are not talking directly to the database and leaving sql scattered throughout your code. This creates a maintenance nightmare and couples your code to the schema of your database.
Personally I always tend to stick the database operations in a database class which does all the heavy lifting of initialising the class, opening the connection etc. It also has generic query-wrappers to which I pass the SQL-statements which contains the normal placeholders for the bound variables, plus an array of the variables to be bound (or the variable number of parameters approach if thats suits you better). If you want to bind each param individually and not use the $stmt->execute(array()); You just pass in the types with the value in a data structure of your choosing, multi dim array, dictionary, JSON, whatever suits your needs and you find easy to work with.
The model class it self (Blog in your case) then subclasses the Database. Then you have a few choices to make. Do you want to use the constructor to create only new objects? Do you want it to only load based on IDs? Or a mix of both? Something like:
function __construct(id = null, title = null, ingress = null, body = null) {
if(id){
$row = $this->getRow("SELECT * FROM blog WHERE id = :id",id); // Get a single row from the result
$this->title = $row->title;
$this->ingress = $row->ingress;
$this->body = $row->body;
... etc
} else if(!empty(title,ingress,body)){
$this->title = title;
... etc
}
}
Maybe neither? You can skip the constructor and use the new(title, ingress, body), save() and a load(id) methods if thats your preference.
Of course, the query part can be generalised even further if you just configure some class members and let the Database-superclass do the query building based on what you send in or set as member-variables. For example:
class Database {
$columns = []; // Array for storing the column names, could also be a dictionary that also stores the values
$idcolumn = "id"; // Generic id column name typically used, can be overridden in subclass
...
// Function for loading the object in a generic way based on configured data
function load($id){
if(!$this->db) $this->connect(); // Make sure we are connected
$query = "SELECT "; // Init the query to base string
foreach($this->columns as $column){
if($query !== "SELECT ") $query .= ", "; // See if we need comma before column name
$query .= $column; // Add column name to query
}
$query .= " FROM " . $this->tablename . " WHERE " . $this->idcolumn . " = :" . $this->idcolumn . ";";
$arg = ["col"=>$this->idcolumn,"value"=>$id,"type"=>PDO::PARAM_INT];
$row = $this->getRow($query,[$arg]); // Do the query and get the row pass in the type of the variable along with the variable, in this case an integer based ID
foreach($row as $column => $value){
$this->$column = $value; // Assign the values from $row to $this
}
}
...
function getRow($query,$args){
$statement = $this->query($query,$args); // Use the main generic query to return the result as a PDOStatement
$result = $statement->fetch(); // Get the first row
return $result;
}
...
function query($query,$args){
...
$stmt = $this->db->prepare($query);
foreach($args as $arg){
$stmt->bindParam(":".$arg["col"],$arg["value"],$arg["type"]);
}
$stmt->execute();
return $stmt;
}
...
}
Now as you see the load($id), getrow($query,$args) and query($query,$args) is completely generic. ´getrow()´is just a wrapper on query() that gets the first row, you may want to have several different wrappers that to or interpret your statement result in different ways. You may also even want to add object specific wrappers to your models if they cannot be made generic. Now the model, in your case Blog could look like:
class Blog extends Database {
$title;
$ingress;
$body;
...
function __construct($id = null){
$this->columns = ["title","ingress","body","id",...];
$this->idcolumn = "articleid"; // override parent id name
...
if($id) $this->load($id);
}
...
}
Use it as so: $blog = new Blog(123); to load a specific blog, or $blog = new Blog(); $blog->title = "title"; ... $blog->save(); if you want a new.
"How can I tie together, i.e. the blog model with the PDO wrapper to fetch a blog inside my bootstrap file?"..
To tie the two together, you could use an object-relational mapper (ORM). ORM libraries are built just for glueing your PHP classes to database rows. There are a couple of ORM libraries for PHP around. Also, most ORMs have a built in database abstraction layer, which means that you can simply switch the database vendor without any hassle.
Considerations when using an ORM:
While introducing a ORM also introduces some bloat (and some learning), it may not be worthwhile investing the time for simply a single Blog object. Although, if your blog entries also have an author, one or multiple categories and/or associated files, an ORM may soon help you reading/writing the database. Judging from your posted code, an ORM will pay off when extending the application in the future.
Update: Example using Doctrine 2
You may have a look at the querying section of the official Doctrine documentation to see the different options you have for read access. Reconsider the example you gave:
// current implementation
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
// possible implementation using Doctrine
$article = $em->getRepository(Blog::class)->find($id);
However, ideally you define your own repository to separate your business logic from Doctrines API like the following example illustrates:
use Doctrine\ORM\EntityRepository;
interface BlogRepositoryInterface {
public function findById($id);
public function findByAuthor($author);
}
class BlogRepsitory implements BlogRepositoryInterface {
/** #var EntityRepository */
private $repo;
public function __construct(EntityRepository $repo) {
$this->repo = $repo;
}
public function findById($id) {
return $this->repo->find($id);
}
public function findByAuthor($author) {
return $this->repo->findBy(['author' => $author]);
}
}
I hope the example illustrates how easily you can separate your business domain models and logic from the underlying library and how powerful ORMs can come into play.
I have a medium-sized MVC application. The use cases for the app are such that I require a bit more control of the SQL used to interact with my database (among other things, data is highly normalized and we use a LOT of joins). So, I've grown my own design for models, rather than using Doctrine or Eloquent or anything. I recently discovered my design is called the Table Data Gateway pattern. At any rate, it looks like this (simplified a bit for clarity):
We have an an abstract class which all my TableGateways extend. It knows how to talk to PDO and do SQL stuff, but not about any particular table:
abstract class TableGateway {
protected $db = null;
public $tableName = '';
public $outputClass = null;
function __contruct(\PDO $pdo) {
$this->db = $pdo;
}
/**
* $return \StdClass[]
*/
function all() {
$result = $this->query(sprintf('SELECT * FROM %s',$this->tableName));
return $result;
}
/**
* #param $query
* #param array $params
* #return \StdClass[]
*/
public function query($query, $params = array()) {
$stmt = $this->db->prepare($query);
if($this->outputClass) {
$stmt->setFetchMode(PDO::FETCH_CLASS, $this->outputClass);
} else {
$stmt->setFetchMode(PDO::FETCH_OBJ);
}
$success = $stmt->execute($params);
return $stmt->fetchAll();
}
}
Then each table has a gateway class which extends the parent class and applies the table-specific stuff. There will be a lot of functions in here which call $this->query(), bringing their knowledge of the table's structure to build a useful query, but there are also some basic functions, like PostsGateway::all() which are the same for every table, so they live in the parent class.
class PostsGateway extends TableGateway {
public $tableName = 'posts';
public $outputClass = 'Post';
}
Each result from the query is loaded into an object. By default they are just StdClass objects, but you can specify an alternate $this->outputClass to have them output in that other class, like this one:
class Post {
/** #var int $id */
/** #var string $content */
}
All this is working pretty well for me. However, my IDE can't keep up. You and I know that PostsGateway::all() is going to return an array Post objects, but the IDE thinks it's just a regular array. This means I don't autocomplete hints for the properties of the Post objects.
Does anyone know any clever ways to resolve this, so the IDE will know about the change?
I was thinking about a script to automatically generate doc blocks for PostsGateway, but I'm a little leary of code gen in general, and especially code gen in a class I'm going to modify periodically. The script would have to periodically update the doc blocks, based on changes to TableGateway, and not mess up anything I've added to PostsGateway.
Are there any other cool ways to do get around this? Or can you recommend some good libraries for generating this code blocks?
Just recently I started rewriting a previously procedurally written website by myself, I chose PDO as the wrapper since I'm also getting used to the OOP way of doing things. I would like some advice about the structure of the classes.
Mostly everything is database-driven, like adding categories and subcategories, brands of products, products, users, etc. I suppose each of them could be one class and since I need CRUD operations on all of them, I need a generic way of inserting, updating, deleting records in the MySql database. The problem is not the code, I'd like to (and already have) coded some of the CRUD operations by myself according to my needs, the real problem is the structure and how would I go to correctly distribute and extend those classes.
Right now I've coded 3 different approaches:
A class called 'Operations' which will be extended by all the other classes that need CRUD functions, this class contains pretty generic properties such as $id, $atributes, $fields and $table, and of course the generic methods to insert, update, delete. That way I can create, let's say my Product object with some parameters (name, category, price) and immediately Product->insert() it into the database, without passing any parameters to the insert function. The CRUD functions in this class don't accept parameters, they depend on the created object's properties.
Same as above but the CRUD functions accept parameters, making them (I suppose) more generic, in case I just need to insert something without creating an object with useless properties previously.
The 'Operations' class extends PDO, the way of working is similar to 2, but now they can be directly accessed when I create the database connection, not depending of other objects.
I'm leaning towards the first option because I think, for the most part, that it will satisfy everything I'll do with this website, again the website is already coded but procedurally, which has been a mess to maintain, so basically I need to re-do things but OO.
CMSs or already coded wrappers aside (the purpose of doing this is to learn PDO and getting used to OOP), which would be the best way to do that? not limited to the options I mentioned.
Here's the 'Operations' class I've managed to code so far, where I've been doing tests sandbox-like, don't mind the spanish variable names. Advices on the code are welcome too.
class Operaciones {
private $database;
protected $id;
protected $atributos;
protected $tabla;
protected $campos;
public function __construct($link) {
$this->database = $link;
}
public function insertar() {
if (!$this->verificarCamposNulos($this->atributos, $this->campos))
echo 'Campos nulos<br />';
else {
$this->prepararCampos();
$placeholders = $this->generarPlaceholders();
$stmt = $this->database->prepare("INSERT INTO {$this->tabla} ({$this->campos}) VALUES ({$placeholders})");
$valores = array_values($this->atributos);
$stmt->execute($valores);
$stmt = NULL;
echo 'Se ha insertado exitosamente';
}
}
public function modificar() {
if (!$this->verificarCamposNulos() || empty($this->id))
echo 'Campos nulos<br />';
else {
$this->prepararCampos('=?');
$stmt = $this->database->prepare("UPDATE {$this->tabla} SET {$this->campos} WHERE id = {$this->id}");
$valores = array_values($this->atributos);
$stmt->execute($valores);
$stmt = NULL;
echo 'Se ha modificado exitosamente';
}
}
private function generarPlaceholders() {
for($i=0;$i<count($this->atributos);$i++)
$qmarks[$i] = '?';
return implode(',', $qmarks);
}
// Check if the values to be inserted are NULL, depending on the field format given
private function verificarCamposNulos() {
$n_campos = explode(',', $this->campos);
$valores = array_values($this->atributos);
foreach($n_campos as $i => $result) {
if (strstr($result, '#'))
if (empty($valores[$i]))
return false;
}
return true;
}
// Removes the '#' from each field, used to check which fields are NOT NULL in mysql
private function prepararCampos($sufijo = NULL) {
$n_campos = explode(',', $this->campos);
foreach($n_campos as $i => $result)
$n_campos[$i] = str_replace('#', '', $result) . $sufijo;
$this->campos = implode(',', $n_campos);
}
}
While working on a mapping structure for our applications we ran into some trouble regarding the code consistency. While it's easy to make select query's with the Zend_Db_Select class (with functions like: $select->from('table')->where('id=?,1), it wouldn't work for update/delete query's. There isn't a class like Zend_Db_Update or Zend_Db_Delete to build the update and delete query's like you build the select. To fix this we extended the Zend_Db_Select class as you can see in the code below. The code shows the custom class that extends the Zend_Db_Select class with some minimal example code at the bottom to show how it's used.
<?php
class Unica_Model_Statement extends Zend_Db_Select
{
public function __construct($oMapper)
{
parent::__construct($oMapper->getAdapter());
$this->from($oMapper->getTableName());
}
/**
* #desc Make a string that can be used for updates and delete
* From the string "SELECT `column` FROM `tabelnaam` WHERE `id` = 1" only the part "`id = `" is returned.
* #return string
*/
public function toAltString()
{
$sQuery = $this->assemble(); // Get the full query string
$sFrom = $this->_renderFrom(''); // Get the FROM part of the string
// Get the text after the FROM (and therefore not using the "SELECT `colname`" part)
$sString = strstr($sQuery,$sFrom);
// Delete the FROM itself from the query (therefore deleting the "FROM `tabelnaam`" part)
$sString = str_replace($sFrom, '', $sString);
// Delete the word "WHERE" from the string.
$sString = str_replace('WHERE', '', $sString);
return $sString;
}
}
################################################
# Below code is just to demonstrate the usage. #
################################################
class Default_IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$person = new Unica_Model_Person_Entity();
$statement = new Unica_Model_Statement($person->getMapper());
$statement->where('id = ?' , 1);
$person->getMapper()->delete($statement);
}
}
class Unica_Model_Person_Mapper
{
public function delete($statement)
{
$where = $statement->toAltString();
$this->getAdapter()->delete($this->_tableName,$where);
}
}
Everything works fine using this class but it got us wondering if we were maybe missing something. Is there a reason there aren't default update/delete classes like there is for select and will using this class give us trouble elsewhere?
Advice would be appreciated.
Thanks in advance,
Ilians
The class is fine if you are sure you are not going to make it evolve too much in the future. I assume your approach is to benefit from the automatic quoting in the Zend_Db_Select class. In my humble opinion, however, it has some design pitfalls that could lead to extensibility troubles if you need to modify or extend it:
It's receiving some data that is discarded afterwards (the entity object, to use the "from" clause).
It's manipulating directly the SQL output of the select query, which can be something dangerous to rely upon. If the format changes, and also if you need to include more elements to the where clause, your code could get quite "muddy" to adapt to the changes.
My approach would just be to use the where clauses directly in the code, and quote them wherever it's necessary. It doesn't look particularly less clean to me. Something like the following:
$db->delete('tablename', 'id = ' . $db->quote('1'));
Eventually, you could even abstract it into a method or class, to avoid having to spread $db->quote() calls all over the place, something like that:
private function myDelete($tableName, $fieldName, $fieldValue)
{
$this->db->delete($tableName, $fieldName . ' = ' . $db->quote($fieldValue));
}
EDIT: Including multiple clauses in the where part would make it a bit more involved, and how flexible you want to be will depend on your particular application. For instance, a possible solution for several "ANDed" clauses could be the following:
private function myDelete($tableName, array $fields)
{
$whereClauses = array();
foreach ($fields as $fieldName => $fieldValue) {
$whereClauses[] = $fieldName . ' = ' . $db->quote($fieldValue);
}
$this->db->delete($tableName, implode(' AND ', $whereClauses));
}
$this->myDelete('tablename', array('id' => '1', 'name' => 'john'));
Hope that helps,
I dont know the exact reasoning behind Zend_Db_Select not offering CUD methods. The obvious explanation is that Select means you should use it for just that: dynamic query building. To insert, update and delete you would either use Zend_Db_Adapter or the proxy methods to it in Zend_Db_Table and Zend_Db_Table_Row or the generic Zend_Db_Statement.
However, with that said, I think there is nothing wrong with extending Zend_Db_Select and adjusting it your needs (just like any other component that doesnt do what you want initially)
So basically I'm making a leap from procedural coding to OOP.
I'm trying to implement the principles of OOP but I have a nagging feeling I'm actually just writing procedural style with Objects.
So say I have a list of pipes/chairs/printers/whatever, they are all all listed as products in my single table database. I need to build a webapp that displays the whole list and items depending on their type, emphasis is on 'correct' use of OOP and its paradigm.
Is there anything wrong about just doing it like:
CLass Show
{
public function showALL(){
$prep = "SELECT * FROM myProducts";
$q = $this->db-> prepare($prep);
$q->execute();
while ($row = $q->fetch())
{
echo "bla bla bla some arranged display".$row['something']
}
}
and then simply
$sth = new show();
$sth->showAll();
I would also implement more specific display methods like:
showSpecificProduct($id)->($id would be passed trough $_GET when user say clicks on one of the links and we would have seperate product.php file that would basically just contain
include('show.class.php');
$sth = new show();
$sth->showSpecificProduct($id);
showSpecificProduct() would be doing both select query and outputing html for display.
So to cut it short, am I going about it allright or I'm just doing procedural coding with classes and objects. Also any ideas/hints etc. on resolving it if I'm doing it wrong?
As well as the model practices described by #Phil and #Drew, I would urge you to separate your business, data and view layers.
I've included a very simple version which will need to be expanded upon in your implementation, but the idea is to keep your Db selects separate from your output and almost "joining" the two together in the controller.
class ProductController
{
public $view;
public function __construct() {
$this->view = new View;
}
public function indexAction() {
$model = new DbProductRepository;
$products = $model->fetchAll();
$this->view->products = $products;
$this->view->render('index', 'product');
}
}
class View
{
protected $_variables = array();
public function __get($name) {
return isset($this->_variables['get']) ? $this->_variables['get'] : null;
}
public function __set($name, $value) {
$this->_variables[$name] = $value;
}
public function render($action, $controller) {
require_once '/path/to/views/' . $controller . '/' . $action . '.php';
}
}
// in /path/to/views/product/index.php
foreach ($this->products as $product) {
echo "Product ID {$product['id']} - {$product['name']} - {$product['cost']}<br />\n";
}
A better fit would be to implement a repository pattern. An example interface might be
interface ProductRepository
{
public function find($id);
public function fetchAll();
}
You would then create a concrete implementation of this interface
class DbProductRepository implements ProductRepsoitory
{
private $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function find($id)
{
// prepare execute SQL statement
// Fetch result
// return result
}
public function fetchAll()
{
// etc
}
}
It's generally a bad idea to echo directly from a method or function. Have your methods return the appropriate objects / arrays / whatever and consume those results.
The scenario you are describing above seems like a good candidate for MVC.
In your case, I would create a class strictly for accessing the data (doing selects of product categories or specific products) and then have a different file (your view) take the output and display it.
It could look something like this:
class Product_Model {
public function find($prodId) { ... }
public function fetchAll($category = '') { ... }
public function search($string) { ... }
}
Then somewhere else you can do:
$products = new Product_Model();
$list = $products->fetchAll(37); // get all from category 37
// in true MVC, you would have a view that you would assign the list to
// $view->list = $list;
foreach($ilst as $product) {
echo "Product ID {$product['id']} - {$product['name']} - {$product['cost']}<br />\n";
}
The basic principle of MVC is that you have model classes that are simply objects representing data from some data source (e.g. database). You might have a mapper that maps data from the database to and from your data objects. The controller would then fetch the data from your model classes, and send the information to the view, where the actual presentation is handled. Having view logic (html/javascript) in controllers is not desirable, and interacting directly with your data from the controller is the same.
first, you will want to look into class autoloading. This way you do not have to include each class you use, you just use it and the autoloader will find the right file to include for you.
http://php.net/manual/en/language.oop5.autoload.php
each class should have a single responsibility. you wouldn't have a single class that connects to the database, and changes some user data. instead you would have a database class that you would pass into the user class, and the user class would use the database class to access the database. each function should also have a single responsibility. you should never have an urge to put an "and" in a function name.
You wouldn't want one object to be aware of the properties of another object. this would cause making changes in one class to force you to make changes in another and it eventually gets difficult to make changes. properties should be for internal use by the object.
before you start writing a class, you should first think about how you would want to be able to use it (see test driven development). How would you want the code to look while using it?
$user = new User($db_object);
$user->load($id);
$user->setName($new_name);
$user->save();
Now that you know how you want to be able to use it, it's much easier to code it the right way.
research agile principles when you get a chance.
One rule of thumb is that class names should usually be nouns, because OOP is about having software objects that correspond to real conceptual objects. Class member functions are usually the verbs, that is, the actions you can do with an object.
In your example, show is a strange class name. A more typical way to do it would be to have a class called something like ProductViewer with a member function called show() or list(). Also, you could use subclasses as a way to get specialized capabilities such as custom views for particular product types.