I am fairly new to OOP and PHP. I worked my way through Kevin Yank’s book and wanted to keep advancing to some OOP concepts. But I am a bit lost and I need help.
I followed Kevin’s instructions and created a db.inc.php file for my PDO db connection:
<?php
//db connection using PDO class object
try {
//create new PDO class object
$pdo = new PDO('mysql:host=122.2.2.2;port=3307;dbname=mydbname','dbuser','password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('SET NAMES "utf8"');
}
catch (PDOException $e)
{
//if unable to connect, tell us why
$output = 'Unable to connect to the database server 2.'.$e->getMessage();
include 'output.html.php';
exit();
}
I created a the following class:
<?php
class Vendor {
protected $name;
protected $address;
protected $city;
protected $state;
protected $postal;
protected $country;
protected $tel;
protected $email;
protected $web;
public function __construct(){
//typically first function to appear in a class
// Constructor using MagicMethod
$this->name = $name;
$this->address = $address;
$this->city = $city;
$this->state = $state;
$this->postal = $postal;
$this->country = $country;
$this->tel= $tel;
$this->email = $email;
$this->web = $web;
}
public function getVendors($pdo){
//get all vendor data from db
try
{
//prepares a statement for execution and returns a statement object
$query = $pdo->prepare("SELECT * FROM vendor ORDER BY name");
$query->execute();
$vendors = array();
foreach ($query->fetchAll() as $row){
//create array of member objects
$vendors[] = new Vendor($row);
}
return array($vendors);
}
catch (PDOException $e)
{
//handle any errors
$output = 'Error fetching vendors: ' . $e->getMessage();
include 'output.html.php';
exit();
}//end try catch
}//end function
//setters
public function setName( $name ){
$this->name = $value;
}
public function setAddress( $address ){
$this->address = $value;
}
public function setCity( $city ){
$this->city = $value;
}
public function setState( $state ){
$this->state = $value;
}
public function setPostal( $postal ){
$this->postal = $value;
}
public function setCountry( $country ){
$this->country = $value;
}
public function setTel( $tel ){
$this->tel = $value;
}
public function setEmail( $email ){
$this->email = $value;
}
public function setWeb( $web ){
$this->web = $value;
}
//getters
public function getName(){
return $this->name;
}
public function getAddress(){
return $this->address;
}
public function getCity(){
return $this->city;
}
public function getState(){
return $this->state;
}
public function getPostal(){
return $this->postal;
}
public function getCountry(){
return $this->country;
}
public function getTel(){
return $this->tel;
}
public function getEmail(){
return $this->email;
}
public function getWeb(){
return $this->web;
}
}
I have an index.php file that is calling the list of vendors
<?php
//get all vendors
include_once $_SERVER['DOCUMENT_ROOT'] .'/db.inc.php';
include_once $_SERVER['DOCUMENT_ROOT'] .'/includes/Vendor.Class.php';
//instantiate class
$vendor = new Vendor();
//calling my function to get a list of vendors
$vendors = $vendor->getVendors($pdo);
//trying to receive an array of objects but basically getting empty objects
echo $vendors;
print_r($vendors);
//output to template
include 'output.html.php';
The idea is to then use a template/output html file to output data:
In my vendor class I am creating an array of Vendor Objects - is this the correct OOP way of returning my rows from DB?
If my country and city are IDs, and each has their own class, when and where is the appropriate time to call those classes and use getCity($id) and getCountry($id) to get name values instead of ids?
Also, if am have to loop through the result set, and that does involve getting the city and country name values, is it ok to do that in the html output file? or should that be done in my index.php file (controller)?
First off, I spotted a small problem with your Vendor class. You’re trying to pass in a row from the DB when you create a new instance, but the constructor doesn’t have any arguments defined. What you want is something like this:
also, in your getVendors method, when you return the array of vendors you’re actually wrapping it in a second array.
In answer to your questions:
I think it would be better to have a separate mapper class that is responsible for retrieving and persisting Vendor objects.
class VendorMapper
{
protected $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function fetchAll()
{
$query = $this->pdo->prepare("SELECT * FROM vendor ORDER BY name");
$query->execute();
$vendors = array();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row)
{
$vendors[] = new Vendor($row);
}
return $vendors;
}
public function fetchById($id)
{
// ...
}
public function save(Vendor $vendor)
{
// ...
}
}
$mapper = new VendorMapper($pdo);
$vendors = $mapper->fetchAll();
I would say objects for concepts like City and Country are better represented as value objects, rather than entities (i.e the object should be immutable and its identity comes from its values, not an id).
class City
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function __toString()
{
return $this->name;
}
}
// In your VendorMapper (or wherever you create your Vendor objects
$this->city = new City($values['city']);
You don’t want to be retrieving any data from the DB within your view, as it’s the controller’s responsibility to pass data to the view. In my answer to your second question, I added a __toString magic method to the City class, which means that in your view you can just do:
Thanks for your input.
So you are suggesting that I use Mapper Classes to handle all DB interactions?
And can you explain what you mean by using value objects vs. entities for fields such as city and country?
Yeah its a very good practice to use data mapper classes to handle DB interactions, you can even further factor out query factories outside of data mapper but for beginners the data mappers are sufficient to carry out all SQL database interaction code. Its about separation of concerns/responsibilities, and to modularize your code into reusable classes.
The difference between value objects and entities is that the former is immutable, by immutable I mean that they are read-only objects whose internal values do not get changed at runtime. A city for instance, remains the same city throughout the lifetime of your script. You can create other cities, but when you do so you create a different value object. Entities, on the other hand, get changed frequently in your script, they are mutable.
Fret, stopping in with a suggestion to fetch all (and the whole class in general) you may want to return the PDOStatement object immediately after execute, and let the itteration occur procedurally so that the entire result does not have to be read into memory (may be dealing with a large table).
Originally, I thought I would have a table for “cities” and a table for “countries”, and then I would store the ID of each city and country in my Vendor table. I would then create a class for each entity. But it sounds like you guys are recommending a different approach. Do I still store cities and countries in a db table, and do I create classes and mapper classes for them?
On a separate note, do you know of any good books that explain these concepts? I have read Kevin Yanks book PHP & MySQL: Novice To Ninja, 5th Edition, and started to read “PHP Master: Write Cutting-edge Code” as well as Beginning PHP 5.3 by Matt Doyle. None of these books mention “Mapper Classes” or “immutable ojects”. I find that there seems to be a large information gap between coding a data driven PHP website procedurally, and an OOP data driven one - especially for beginners.
That’s a fair point, but it seems odd to be mapping the DB results to objects outside of the mapper class. What about a creating a collection class which wraps the PDOStatement and creates Vendor objects as it’s iterated over?
I’m in the process of learning to incorporate value objects in my code more myself, so others here might have more experience on the best way to deal with persisting them in the DB. I’d say you probably still want a table for countries and cities to be able to generate dropdown lists for forms etc.
The VendorMapper class should be responsible for building out the Vendor object though, including any objects that it’s composed of. If your city is linked to a vendor by an ID, you pull it in together with the vendor data via a JOIN query. Alternatively, you could denormalize the data a little by simply store the city name directly in the vendor table.
Check out PHP 5 Objects, Patterns, and Practice by Matt Zandstra, it covers things such as value objects, entities, mappers and other design patterns.