possibly, Phil - but that's not the way I would do it.
I'd associate the ID of the topping in the database with the pizza. then
perhaps associate a full object on render time, but more likely I would use a factory class to do the rendering. Because the pizza itself never needs to know
what its topping might be, simply that it has one.
When the pizza object was loaded, I would call a method that looked like this
Code:
public function loadToppings(){
if(empty($this->id)):
$this->toppings = array();
return;
endif;
global $pdo;
$sql = "SELECT id from pizzaToppingsJoin where pizzaID = ?";
$s = $pdo->prepare($sql);
$s->execute($this->id);
$this->toppings = $s->fetchAll(PDO::FETCH_COLUMN, 0);
}
then to add a topping I would do something like this
Code:
public function AddTopping($topping){
$toppingObj = new topping;
if(!is_numeric($topping)):
$toppingObj->searchByName($topping);
$toppingID = $toppingsObj->getPrimary();
if($toppingID == 0): //we know the topping does not exist in the database yet
$toppingObj->Name($topping);
$toppingObj->save();
endif;
$toppingID = $toppingObj->getPrimary();
else:
$toppingObj->searchByID($topping);
$toppingID = $toppingsObj->getPrimary();
if($toppingID != $topping): //we know the topping does not exist in the database yet
return false; // we cannot do anything as we don't know the name of the topping
endif;
endif;
$this->toppings[] = $toppingID; //add the topping ID to the list of current toppings
}
for rendering, if I had to I'd most likely construct a factory class that looked like this.
Code:
class renderTopping{
public function renderDropDown($toppingID){}
public function renderList{$toppingID){}
/etc
}
I would not add the renderer into the main class structure however. I used to do this a few years ago and found that it just wasn't efficient when coming back and altering code later on. The renderer should be part of the template or view. the object definition should be part of the model. if you're following a design paradigm like MVC, for example.
Depending on whether my app were to allow searching for pizzas by toppings, I would either store the association of pizza ID and topping ID in a join table or, if no searching were needed, in a serialized array. However 9 times out of ten, even if you think searching won't be needed upfront, there comes a time later when you wish you'd gone the extra step and had a join table...
so I would use this as a save routine inside the pizza class (the load routine is above)
Code:
public function save(){
parent::save(); //save this record
//then deal with toppings
global $pdo;
$sql = "delete from pizzatoppingsjoin where pizzaID=?";
$s = $pdo->prepare($sql);
$s->execute(array($this->getPrimary());
$sql = "Insert into pizzatoppingsjoin (pizzaID, toppingID) values (?,?)";
$s = $pdo->prepare($sql);
$pizzaID = $this->getPrimary();
foreach($this->toppings as $toppingID):
$s->execute(array($pizzaID, $toppingID));
endforeach;
}
and just in case people are wondering about the getPrimary() method - this is a standard method i use in my inherited classes. This is because all entity classes inherit from a baseclass which intermediates database access. An example of this base class is as follows
Code:
<?php
class base {
public function __construct(){
$this->load(); //instantiate variables
}
public function getPrimary(){
return isset($this->{$this->primary}) ? $this->{$this->primary} : false;
}
public function save(){
return $this->getPrimary() === false ? $this->insert() : $this->update();
}
public function insert(){
global $pdo;
$params = array();
$sql = "INSERT into {$this->table} SET ";
foreach($this->fields as $key=>$field):
$fields[] = "$field = :{$field}";
$params[$field] = $this->{$field};
endforeach;
$query = $sql . implode(',', $fields);
$s = $pdo->prepare($query);
if($s === false) $this->bail($pdo->errorInfo());
$r = $s->execute($params);
if($r === false) $this->bail($s->errorInfo());
return $s->rowCount();
}
public function update(){
global $pdo;
$params = array();
$sql = "UPDATE {$this->table} SET ";
foreach($this->fields as $key=>$field):
if($field === $this->primary) continue;
$fields[] = "$field = :{$field}";
$params[$field] = $this->{$field};
endforeach;
$query = $sql . implode(',', $fields) . " WHERE {$this->primary} = :{$this->primary}";
$params[$this->primary] = $this->getPrimary();
$s = $pdo->prepare($query);
if($s === false) $this->bail($pdo->errorInfo());
$r = $s->execute($params);
if($r === false) $this->bail($s->errorInfo());
return $s->rowCount();
}
public function delete(){
global $pdo;
$sql = "delete from {$this->table} where {$this->primary} = ?";
$params = array($this->getPrimary());
$s = $pdo->prepare($sql);
if ($s === false) $this->bail($pdo->errorInfo());
$r = $s->execute($params);
if($r === false) $this->bail($s->errorInfo());
return $s->rowCount();
}
public function loadFromID($id){
global $pdo;
$sql = "Select * from {$this->table} where {$this->primary} = ?";
$s = $pdo->prepare($sql);
if ($s === false) $this->bail($pdo->errorInfo());
$r = $s->execute(array($id));;
if($r === false) $this->bail($s->errorInfo());
$row = $s->fetchObject();
$this->load($row);
}
public function searchByID($id){
return $this->loadFromID($id);
}
public function load($row){
if(is_array($row)) $row = (object) $row;
foreach ($this->fields as $field):
if(isset($row->{$field})) $this->{$field} = $row->{$field};
endforeach;
}
public function bail($array){
echo '<pre>' . print_r($array, true) . '</pre>';
die;
}
}
?>
so taking the pizza class as an example
Code:
<?php
Class Pizza extends base{
public $id, $name;
public $toppings = array();
public $fields = array('id','name'); // NB no toppings
public $table = 'pizzas';
public $primary = 'id';
public function __construct{
parent::__construct();
}
public function load($array = array()){
parent::load($array);
$this->loadtoppings();
}
public function save(){
parent::save(); //save this record
//then deal with toppings
$this->saveToppings();
}
public function saveToppings(){
global $pdo;
$sql = "delete from pizzatoppingsjoin where pizzaID=?";
$s = $pdo->prepare($sql);
$s->execute(array($this->getPrimary());
$sql = "Insert into pizzatoppingsjoin (pizzaID, toppingID) values (?,?)";
$s = $pdo->prepare($sql);
$pizzaID = $this->getPrimary();
foreach($this->toppings as $toppingID):
$s->execute(array($pizzaID, $toppingID));
endforeach;
}
public function AddTopping($topping){
$toppingObj = new topping;
if(!is_numeric($topping)):
$toppingObj->searchByName($topping);
$toppingID = $toppingsObj->getPrimary();
if($toppingID == 0): //we know the topping does not exist in the database yet
$toppingObj->Name($topping);
$toppingObj->save();
endif;
$toppingID = $toppingObj->getPrimary();
else:
$toppingObj->searchByID($topping);
$toppingID = $toppingsObj->getPrimary();
if($toppingID != $topping): //we know the topping does not exist in the database yet
return false; // we cannot do anything as we don't know the name of the topping
endif;
endif;
$this->toppings[] = $toppingID; //add the topping ID to the list of current toppings
}
public function loadToppings(){
if(empty($this->id)):
$this->toppings = array();
return;
endif;
global $pdo;
$sql = "SELECT id from pizzaToppingsJoin where pizzaID = ?";
$s = $pdo->prepare($sql);
$s->execute($this->id);
$this->toppings = $s->fetchAll(PDO::FETCH_COLUMN, 0);
}
}
?>
and you would use this like so
Code:
$pizza = new pizza;
$pizza->loadFromID($pizzaID);
if($pizza->getPrimary() == $pizzaID): //record loaded
//do something with the pizza
endif;
// or if you were submitting a form with new pizza information ...
[code]
$pizza = new pizza;
$pizza->loadFromID($_POST['pizzaID']);
//override the current data with new data
$pizza->load($_POST); //note that the base object will automatically filter the POST superglobal
// deal with toppings
//delete all existing toppings
$pizza->toppings = array();
foreach($_POST['pizzaToppings'] as $toppingID): // assumes an enumerated list of toppings.
$pizza->addTopping($toppingID);
endforeach;
$newToppings = explode(',',$_POST['newToppings']);
foreach($newToppings as $newTopping):
$pizza->addTopping( trim($newTopping) );
endforeach;
//now save the pizza
$pizza->save();
//and maybe display it ...
require_once BASEDIR . 'templates/pizzas/pizzaDisplay.php';
exit;
Just to revisit an earlier post too - my auto getter/setter using magic methods was not quite right (I was typing on a phone). it should look like this
Code:
public function __call($name, $arguments=NULL){
if(is_null($arguments) || ( is_array($arguments) && count($arguments) == 0):
return isset($this->$name) ? $this->$name : NULL;
else:
if(count($arguments) > 1 ):
$this->$name = $arguments;
else:
$this->$name = $arguments[0];
endif;
return true;
endif;
}
i have not tested this overloaded call and i note that unexpected behaviour may occur if you try to use it to assign an empty array to a property. but other than that, it is (or looks) quite neat.
because this is a fully abstracted function then I would definitely put this in the base inherited class.
and to revisit my earlier reference to the iterator with an example (although I have not used an intermediate toppings object in my example of the pizzas class above, it remains possible)
Code:
Class Toppings implements Iterator {
public $pizzaID;
public $toppings = array();
public function Add($topping, $Name = null){
if(is_null($Name)):
$this->toppings['Topping_'.$topping->ID] = $topping;
else:
$Topping=new Topping();
$Topping->ID=$topping;
$Topping->Name=$Name;
$this->toppings['Topping_'.$Topping->ID] = $Topping;
endif;
}
public function count(){
return count($this->toppings);
}
/* iterator logic */
private $pos = 0;
public function rewind(){$this->pos = 0;}
public function current(){return $this->toppings[$this->pos];}
public function key(){return $this->pos;}
public function next(){++$this->pos;}
public function valid(){return isset($this->toppings[$this->pos]);}
}
then with the above in the class you can just do this from the pizza class
Code:
foreach($pizza->Toppings as $toppingID=>$toppingObj):
// do stuff
endforeach;
note as well that objects naturally implement iterator too, however the iterator that is natively implemented iterates over an implicit associative array of all public properties.
one thing to be aware of is that I am not certain whether a class and extend another class and implement an iterator. It may well be that the class must be left only extending another class and then an iterface to that class would be built that implements the iterator. I will investigate and mention this only for fullness as the toppings class is not an entity in itself, but a holder; so it would not need to inherit from base anyway.