Object-oriented PHP :: A guide for fellow ISys junkies
08.07.2007Putting it all together
We’ve gone through a fairly thorough intro to object-oriented PHP, but to help solidify things we need a good example to integrate a lot that we’ve learned. What better way to do it than by recreating the framework you learned during INTEX II?
Here’s the folder structure I’m working with on my server:
/PHPSandbox
/Includes
/BO
/DAO
And here are the files with their respective code:
/PHPSandbox/Includes/BO/ProductBO.php
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <? require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/StrictClass.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/ProductDAO.php"); class ProductBO extends StrictClass { private $id; private $sku; private $description; private $cost; private $vendor; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getSku() { return $this->sku; } public function setSku($sku) { $this->sku = $sku; } public function getDescription() { return $this->description; } public function setDescription($description) { $this->description = $description; } public function getCost() { return $this->cost; } public function setCost($cost) { $this->cost = $cost; } public function getVendor() { return $this->vendor; } // This should probably not ever get called outside of our BOs and DAOs. // Most likely it will get set when calling VendorBO->addProduct() public function setVendor($vendor) { $this->vendor = $vendor; } public function save() { ProductDAO::saveProduct($this); } } ?> |
/PHPSandbox/Includes/BO/VendorBO.php
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | <? require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/StrictClass.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/VendorDAO.php"); class VendorBO extends StrictClass { private $id; private $name; private $address; private $city; private $state; private $zip; private $phone; private $products; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getAddress() { return $this->address; } public function setAddress($address) { $this->address = $address; } public function getCity() { return $this->city; } public function setCity($city) { $this->city = $city; } public function getState() { return $this->state; } public function setState($state) { $this->state = $state; } public function getZip() { return $this->zip; } public function setZip($zip) { $this->zip = $zip; } public function getPhone() { return $this->phone; } public function setPhone($phone) { $this->phone = $phone; } public function getProducts() { return $this->products; } public function setProducts($products) { $this->products = $products; } public function addProduct(ProductBO $product) { // Verify that product does not already exist in the list if (!empty($this->products)) { if (in_array($product, $this->products)) { trigger_error(printf("%s already exists in the product list", $product->getDescription()), E_USER_ERROR); } } //if (!empty($this->products) && in_array($product, $this->products)) { // trigger_error(printf("%s already exists in the product list", $product->getDescription()), E_USER_ERROR); //} // Add product to list $this->products[] = $product; // Set vendor inside product BO $product->setVendor($this); } public function removeProduct(ProductBO $product) { // Verify that product already exists in the list if (empty($this->products) || !in_array($product, $this->products, true)) { trigger_error(printf("%s does not exist in the product list", $product->getDescription()), E_USER_ERROR); } // Remove product from the list unset($this->products[array_search($product, $this->products, true)]); // Re-index products list $this->products = array_merge($this->products); // Remove vendor from product BO $product->setVendor(null); } public function save() { VendorDAO::saveVendor($this); } } ?> |
/PHPSandbox/Includes/DAO/ProductDAO.php
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | <? require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DBConn.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/ProductBO.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/VendorDAO.php"); class ProductDAO { public static function saveProduct(ProductBO &$product) { if ($product->getId()) { self::updateProduct($product); } else { self::insertProduct($product); } } private static function updateProduct(ProductBO $product) { if (!$product->getVendor()) { trigger_error("Product does not have a vendor", E_USER_ERROR); } if (!$product->getVendor()->getId()) { trigger_error("The vendor of this product must be saved before saving the product.", E_USER_ERROR); } $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("UPDATE product SET sku=?, description=?, cost=?, vendorId=? WHERE id=?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'ssdii', $product->getSku(), $product->getDescription(), $product->getCost(), $product->getVendor()->getId(), $product->getId()); $stmt->execute(); } private static function insertProduct(ProductBO &$product) { if (!$product->getVendor()) { trigger_error("Product does not have a vendor", E_USER_ERROR); } if (!$product->getVendor()->getId()) { trigger_error("The vendor of this product must be saved before saving the product.", E_USER_ERROR); } $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("INSERT INTO product VALUES (null, ?, ?, ?, ?)") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'ssdi', $product->getSku(), $product->getDescription(), $product->getCost(), $product->getVendor()->getId()); $stmt->execute(); $product->setId($mysqli->insert_id); } public static function getProductById($id) { $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("SELECT * FROM product WHERE id = ?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'i', $id); $stmt->execute(); $stmt->bind_result( $id, $sku, $description, $cost, $vendorId); if ($stmt->fetch()) { $product = new ProductBO(); $product->setId($id); $product->setSku($sku); $product->setDescription($description); $product->setCost($cost); $product->setVendor(VendorDAO::getVendorById($vendorId)); return $product; } else { return false; } } public static function getProductsForVendor(VendorBO &$vendor) { $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("SELECT * FROM product WHERE vendorId = ?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'i', $vendor->getId()); $stmt->execute(); $stmt->bind_result( $id, $sku, $description, $cost, $vendorId); $productList = array(); while ($stmt->fetch()) { $product = new ProductBO(); $product->setId($id); $product->setSku($sku); $product->setDescription($description); $product->setCost($cost); $product->setVendor($vendor); $productList[] = $product; } return $productList; } public static function deleteProduct(ProductBO &$product) { // If the product is assigned to a vendor, remove it from the vendor's // product list just to keep our vendor BO clean if ($product->getVendor()) { $product->getVendor()->removeProduct($product); } $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("DELETE FROM product WHERE id = ?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'i', $product->getId()); $stmt->execute(); } } ?> |
/PHPSandbox/Includes/DAO/VendorDAO.php
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | <? require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DBConn.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/VendorBO.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/ProductDAO.php"); class VendorDAO { public static function saveVendor(VendorBO &$vendor) { if ($vendor->getId()) { self::updateVendor($vendor); } else { self::insertVendor($vendor); } if ($vendor->getProducts()) { foreach ($vendor->getProducts() as $product) { ProductDAO::saveProduct($product); } } } private static function updateVendor(VendorBO $vendor) { $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare(" UPDATE vendor SET name=?, address=?, city=?, state=?, zip=?, phone=? WHERE id=?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'ssssssi', $vendor->getName(), $vendor->getAddress(), $vendor->getCity(), $vendor->getState(), $vendor->getZip(), $vendor->getPhone(), $vendor->getId()); $stmt->execute(); } private static function insertVendor(VendorBO &$vendor) { $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("INSERT INTO vendor VALUES (null, ?, ?, ?, ?, ?, ?)") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'ssssss', $vendor->getName(), $vendor->getAddress(), $vendor->getCity(), $vendor->getState(), $vendor->getZip(), $vendor->getPhone()); $stmt->execute(); $vendor->setId($mysqli->insert_id); } public static function getVendorById($id) { $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("SELECT * FROM vendor WHERE id = ?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'i', $id); $stmt->execute(); $stmt->bind_result( $id, $name, $address, $city, $state, $zip, $phone); if ($stmt->fetch()) { $vendor = new VendorBO(); $vendor->setId($id); $vendor->setName($name); $vendor->setAddress($address); $vendor->setCity($city); $vendor->setState($state); $vendor->setZip($zip); $vendor->setPhone($phone); $vendor->setProducts(ProductDAO::getProductsForVendor($vendor)); return $vendor; } else { return false; } } public static function deleteVendor(VendorBO $vendor) { // If the product is assigned to the vendor, delete it if ($vendor->getProducts()) { foreach ($vendor->getProducts() as $product) { ProductDAO::deleteProduct($product); } } $mysqli = DBConn::getConn(); $stmt = $mysqli->prepare("DELETE FROM product WHERE id = ?") or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR)); $stmt->bind_param( 'i', $product->getId()); $stmt->execute(); } } |
/PHPSandbox/Includes/StrictClass.php
1 2 3 4 5 6 7 8 9 10 11 12 13 | <? abstract class StrictClass { public function __get($var_name) { trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR); } public function __set($var_name, $var_value) { trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR); } } ?> |
/PHPSandbox/Includes/DBConn.php
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 | <? Class DBConn { static private $host = '***YOUR MYSQL HOST***'; private static $username = '***YOUR USERNAME***'; private static $password = '***YOUR PASSWORD***'; private static $db = '***YOUR DATABASE NAME***'; // Prevent class instantiation private function __construct() { trigger_error('Instantiation is not allowed.', E_USER_ERROR); } // Return a new connection // Note that connection pooling is not a feature of mysqli // If you get a gabajillion hits to your site per minute, it may // be worth implementing your own connection pool, but // this will work for the majority of cases public static function getConn() { $mysqli = new mysqli(self::$host, self::$username, self::$password, self::$db); if (mysqli_connect_errno()) { trigger_error(printf("Mysqli error: %s", mysqli_connect_error()), E_USER_ERROR); } return $mysqli; } // Prevent class cloning public function __clone() { trigger_error('Clone is not allowed.', E_USER_ERROR); } } ?> |
Once we have our core files set up, we can now test them out with a couple functions I’ve set up. Note that these are strictly for testing, but they can give you an idea of how the different classes and objects interoperate and how you could go about programming a full application using object-oriented PHP.
/PHPSandbox/TestFunctions.php
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 | <? require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/ProductBO.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/ProductDAO.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/VendorBO.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/VendorDAO.php"); function createNewVendorWithNewProducts() { $brinkton = new VendorBO(); $brinkton->setName("Brinkton Manufacturing"); $brinkton->setAddress("100 N 100 S"); $brinkton->setCity("Provo"); $brinkton->setState("UT"); $brinkton->setZip("84606"); $brinkton->setPhone("801-555-5555"); $chair = new ProductBO(); $chair->setSku("KAL-12KDUFLKDU134"); $chair->setDescription("Blackstone Chair"); $chair->setCost(119.45); $brinkton->addProduct($chair); $desk = new ProductBO(); $desk->setSku("JKD-14LKJDFIUL183"); $desk->setDescription("Swingline Table"); $desk->setCost(212.98); $brinkton->addProduct($desk); // This will cascade and save all the products $brinkton->save(); } function removeExistingProductFromExistingVendor() { $brinkton = VendorDAO::getVendorById(3); $products = $brinkton->getProducts(); $productToRemove = $products[0]; // This will both remove the product from the database // and remove it from the vender BO that is loaded ProductDAO::deleteProduct($productToRemove); } ?> |
That’s it! That’s all I’ve got! If you have any questions or just want to chat, feel free to join the intimate conversation or email me. Thanks for reading.


Nice site you have here Aaron! There’s just one small point that I think you might be interested in. You mentioned that PHP doesn’t support object overloading, but it actually does. Several special methods can be set up on objects, including __get, __set, and __call. This may not be the same implementation as is found in other languages, but it is overloading. Check it out.