eldan88 Posted October 14, 2013 Share Posted October 14, 2013 Hey guys. I have a database class that defines all my database methods. I have added a attribute called $table_name on this class, and would like the $table_name to change depending what the parent class is. So for example when I have extended the databasobject class to the user class I would like $table_name to be users. On the users class it would have "SELECT * FROM users", because I assigned users to $table_name in the users class. When I extend the databaseobject class it doesn't do that. It looks for the $table_name in its own class not in the parent class. I believe this is an example of late static binding... Does anyone have any suggestions on how I can correct this? Below is my DatabaseObject Class(Child Class) <?php class DatabaseObject{ protected static $table_name public static function find_all() { return $result_set = self::find_by_sql("SELECT * FROM ".self::$table_name); } ?> Here is my user class(Parent Class) class User extends DatabaseObject { protected static $table_name = "users"; public $id; public $username; public $password; public $first_name; public $last_name; public static function authenticate($username="",$password ="") { global $database; $username = $database->escape_value($username); $password = $database->escape_value($password); $sql = " SELECT * FROM".self::$table_name; // I am looking to use the $table_name defined above $sql .= " WHERE username = ". "'$username'"; $sql .= " AND password = ". "'$password' "; $sql .= " LIMIT 1 "; $result_array = self::find_by_sql($sql); return !empty($result_array) ? array_shift($result_array) : false; } Quote Link to comment Share on other sites More sharing options...
AbraCadaver Posted October 14, 2013 Share Posted October 14, 2013 (edited) You have a parse error in DatabaseObject. Also, it is working you just are missing a space. Have you debugged this with echo $sql; or similar? SELECT * FROMusers WHERE username = '' AND password = '' LIMIT 1 I'm not sure you are going about this design the right way. Maybe someone can recommend a GOOD tutorial. Edited October 14, 2013 by AbraCadaver Quote Link to comment Share on other sites More sharing options...
vinny42 Posted October 14, 2013 Share Posted October 14, 2013 The design does look a bit weird, a user is not an improved databaseclass. A userhandler or usermanager can use SQL to fetch data from a database and polulate userobjects with that data, but a user does not know where it came from or where it should be saved. Don't extend database classes, use composition. The userhandler class sends hardcoded queries to a database-access class that executes them and returns raw data that the handler processes into userobjects. Also note that there is no one-to-one relation between PHP objects and database tables. This idea forces you to create separate objects for the user's basic data, his accessrights, his addresses, preferences etc. Before you know it, you'll have dozens of objects and dozens of queries to get the most simple pieces of information about a user. So just write queries that get what you need. As a final note; why do you put the tablename in a property, are you planning to change the tablename at runtime? :-) if you prefix the tablename properly you will never have a reason to change it ever. Quote Link to comment Share on other sites More sharing options...
trq Posted October 14, 2013 Share Posted October 14, 2013 Along the same lines as vinney42's post. A User is not of type "Database" do why would a user extend a database? You seem to be getting confused between inheritance and dependencies. Quote Link to comment Share on other sites More sharing options...
eldan88 Posted October 15, 2013 Author Share Posted October 15, 2013 (edited) Hey vinny42. I am sorry but can you please explain me what you mean by user handler or usermanager, and what do mean by "Don't extend database classes, use composition"? Sorry about all these questions I am kind of new to all this. Thanks! AbraCadaver, I got this code from a tutorial I was following, but wanted to change it up a little bit. Just wanted to play around with it a little bit to get a feel of how things work. Edited October 15, 2013 by eldan88 Quote Link to comment Share on other sites More sharing options...
AbraCadaver Posted October 15, 2013 Share Posted October 15, 2013 Hey vinny42. I am sorry but can you please explain me what you mean by user handler or usermanager, and what do mean by "Don't extend database classes, use composition"? Sorry about all these questions I am kind of new to all this. Thanks! AbraCadaver, I got this code from a tutorial I was following, but wanted to change it up a little bit. Just wanted to play around with it a little bit to get a feel of how things work. I'm not a hardcore (experienced) OOP guy so I was hoping that someone who is could point to GOOD tutorials, as there is quite a bit of crap out there. Quote Link to comment Share on other sites More sharing options...
vinny42 Posted October 15, 2013 Share Posted October 15, 2013 Perhaps the most important thing to keep in mind is the single-responisibility principle: each class does one thing. So you would have one class for holding the userdata, one class for storing the data in the the database, one class for presenting the data to the user, etc. What I usually do is something like this: userclass - only holdfs the user's data, it has no functionalitiy other than validating it's own data to see if the object is in a sane state. userhandler - knows which operations can be done on/to/with a userobject. userstorageMySQL - knows how to store a userobject in a MySQL database. userstoragePgSQL - knows how to store a userobject in a PostgreSQL database. dbMySQL - executes queries on a MySQL database dbPgSQl - executes queries on a PostgreSQL database When I want to load a user I instantiate the userhandler and do a "loadUserById(42)". The userhandler only knows that loading a user requires an instance of a "userstorage_*" class and the current configuration tells it if that's a MySQL or PostgreSQL database. A factory class then creates for example a userstorage_PgSQL instance which sends queries to a dbPgSQL instance to execute the queries that actually gets the data from the database. The userhandler then receives the data and fills a userobject with it and returns the object. The point of this mountain of classes is that you can replace any of the lower classes with anything else as long as the method calls are the same. (@see: interface, design by contract) The userhandler class uses "a" storage class. What that storage class does internally is completely hidden from the userhandler, so you can replace it with a MySQL-based object or even a mock object for testing, the userhandler will never know or care. I make all these classes work together by using composition. My userhandler has a property that I can fill with an instance of a storageclass and the storageclass has a property for an instance of a dbclass. So the storageclass is *not* an extension of a dbclass, and the userhandler class is not an extension of the storageclass. Does that shed any light on the subject? Quote Link to comment Share on other sites More sharing options...
KevinM1 Posted October 15, 2013 Share Posted October 15, 2013 Hey vinny42. I am sorry but can you please explain me what you mean by user handler or usermanager, and what do mean by "Don't extend database classes, use composition"? Sorry about all these questions I am kind of new to all this. Thanks! "Composition" is a fancy way of saying that objects can contain other objects. Newbies tend to overuse inheritance because that's pretty much where tutorials begin and end when it comes to adding functionality to classes. The problem is that inheritance creates rigid hierarchies, and can lead to weird relationships. Every class you make is a type. A type is both a kind of data and the actions that can be performed on that data. Integer is a type, which are whole numbers that can be acted on via arithmetic. String is a type, which are collections of characters that can be concatenated, counted, spliced, etc. Even though PHP is dynamically typed (a variable can hold anything), the idea of type is still important, especially in OO. Inheritance creates what's known as is-a relationships. Meaning, a child object is also considered to be an instance of the parent class. That's why vinny and trq were questioning you having a User extend a Database - it doesn't make sense for a User to be a Database. What you want to do is use a Database so a user can do what it needs to do. You do this by composing the User and Database, like so: class Database { // db code } class User { private $db; public function __construct($db) { $this->db = $db; } } $myDB = new Database(); $bob = new User($myDB);Boom, you now have a User that uses the Database. That kind of setup has a couple advantages: 1. You no longer have that weird, "A User is also a Database" relationship. 2. If written correctly, your User can use any kind of database. 3. It's cheap - objects are passed by reference by default in PHP 5+, so User doesn't have a full copy of the Database, but just a link to it. 4. Because of that, Singletons (which are bad anyway, as they're a form of global) are wholly unnecessary because you can create just one Database and then assign its reference to an infinite number of other objects. AbraCadaver, I got this code from a tutorial I was following, but wanted to change it up a little bit. Just wanted to play around with it a little bit to get a feel of how things work. Like I said in another thread, ditch the tutorials. Get the following books: http://www.amazon.com/Objects-Patterns-Practice-Experts-Source/dp/143022925X/ref=sr_1_1?ie=UTF8&qid=1381855818&sr=8-1&keywords=zandstra http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612/ref=sr_1_1?s=books&ie=UTF8&qid=1381855844&sr=1-1&keywords=gang+of+four Tutorials on OOP generally suck. Yeah, books cost money, but these two are definitely worth the investment. There simply isn't a better way to get introduced to OOP or the ideas behind it than these two. Quote Link to comment Share on other sites More sharing options...
eldan88 Posted October 16, 2013 Author Share Posted October 16, 2013 Hey Kevin thanks for that info. Def going to ditch the tutorials. I have trying using your code to access the database object but was having some troubles doing so. For instance I was trying to access the some_db() method through the user object. But it couldn't find the some_db() method... I know the code may look silly, as I am getting the hang of how things work class Database { public function some_db() { echo "This is some DB code"; } } class User { private $db; public function __construct($db) { $this->db = $db; } } $myDB = new Database(); $bob = new User($myDB); $bob->some_db(); Quote Link to comment Share on other sites More sharing options...
eldan88 Posted October 16, 2013 Author Share Posted October 16, 2013 Perhaps the most important thing to keep in mind is the single-responisibility principle: each class does one thing. So you would have one class for holding the userdata, one class for storing the data in the the database, one class for presenting the data to the user, etc. What I usually do is something like this: userclass - only holdfs the user's data, it has no functionalitiy other than validating it's own data to see if the object is in a sane state. userhandler - knows which operations can be done on/to/with a userobject. userstorageMySQL - knows how to store a userobject in a MySQL database. userstoragePgSQL - knows how to store a userobject in a PostgreSQL database. dbMySQL - executes queries on a MySQL database dbPgSQl - executes queries on a PostgreSQL database When I want to load a user I instantiate the userhandler and do a "loadUserById(42)". The userhandler only knows that loading a user requires an instance of a "userstorage_*" class and the current configuration tells it if that's a MySQL or PostgreSQL database. A factory class then creates for example a userstorage_PgSQL instance which sends queries to a dbPgSQL instance to execute the queries that actually gets the data from the database. The userhandler then receives the data and fills a userobject with it and returns the object. The point of this mountain of classes is that you can replace any of the lower classes with anything else as long as the method calls are the same. (@see: interface, design by contract) The userhandler class uses "a" storage class. What that storage class does internally is completely hidden from the userhandler, so you can replace it with a MySQL-based object or even a mock object for testing, the userhandler will never know or care. I make all these classes work together by using composition. My userhandler has a property that I can fill with an instance of a storageclass and the storageclass has a property for an instance of a dbclass. So the storageclass is *not* an extension of a dbclass, and the userhandler class is not an extension of the storageclass. Does that shed any light on the subject? Vinny42. Thanks a lot for that explanation. I do get what you are saying but, right now I am clueless when it comes to composition. I am having a hard time using composition with Kevin's code. Do you have any actual code examples? Quote Link to comment Share on other sites More sharing options...
Ch0cu3r Posted October 16, 2013 Share Posted October 16, 2013 (edited) Hey Kevin thanks for that info. Def going to ditch the tutorials. I have trying using your code to access the database object but was having some troubles doing so. For instance I was trying to access the some_db() method through the user object. But it couldn't find the some_db() method... I know the code may look silly, as I am getting the hang of how things work You cannot access the some_db() database method from outside of user class. You can only access that method from within the users class using $this->db->some_db(); The database object is stored within the $db property for the users class If you need to use the some_db() method outside of the users class. Then use the database object $myDB->some_db(): Edited October 16, 2013 by Ch0cu3r Quote Link to comment Share on other sites More sharing options...
vinny42 Posted October 16, 2013 Share Posted October 16, 2013 (edited) I quickly typed this up. All it does is pretend to load a gallery from a database which can either be mongodb or postgresql. At the bottom of the script you'll see a "router" class that looks at a GET parameter to select mongo or PgSQL. The way it makes this selection is to first create a galleryhandler and then inject an instance of a storagehandeler into it. The galleryhandler only knows how to talk to a storagehandler to load and save a galery, and the storagehandler only knows how to load and save a gallery to a postgresql or mongodb database. The injection is done through composition; a set() method pushes an instance of the storagehandler into the galleryhandler and this works because all storagehandlers have the same methods for loading and saving. As you'll see, the PostgreSQL version returns some dummy data and the mongodb version throws an exception, to prove that you are really accessing the different storagehandlers only by injecting them. Key things to notice are that the galleryhandler only knows that it should as "the storage handler" to load a gallery, it does not care how the storagehandler does it's work, it could access a database it could read tealeaves, the galleryhandler doesn't care. So, you are free to write any kind of storagehandler and composite it in. I wrote this very quickly so feel free to comment/ask/curse at it. <?php /** * Class gallery, only holds the data of a gallery. * */ class gallery { /** * @var */ private $_strName; /** * @param $pStrName */ public function __construct($pStrName) { $this->_strName = $pStrName; } } /** * Class dbPgSQL * * This class only knows how to run queries against a PostgreSQL database and to return the results. * */ class dbPgSQL { /** * Pretend that a query is being executed against the database and return a fake resultset * in the form of an array. * * @param $pStrQuery * * @return array */ public function executeQuery($pStrQuery, $pArrParameters) { return array(array("name" => "mygallery")); } } /** * Class dbMongoDB * * This class knows how to run queries against a MongoDB database. */ class dbMongoDB { /** * * @param $pStrQuery * * @return array */ public function executeQuery($pStrQuery, $pArrParameters) { Throw new exception("Not implemented"); } } /** * Class galleryStorage_PgSQL * * The gallerystore for postgresql knows which queries to run to get which part of the gallery data back. * It uses composition to keep a copy of a dbPgSQL() class inside it to run the queries through. * */ class galleryStorage_PgSQL implements interface_galleryStorageHandler { public function __construct() { /** * This is a hard dependency, which would normally be done using some injection but that fogs up the image * I'm trying to paint here. */ $this->_objDatabaseAccess = new dbPgSQL(); } /** * * GetGalleryByName runs a query to get gallerydata for the supplied gallery name. * * @param $pStrName * * @return gallery */ public function getGalleryByName($pStrName) { // Go to the composited database class database // and ask ir for the correct record. The dbPgSQL class should take care of SQL escaping etc. $arrData = $this->_objDatabaseAccess->executeQuery("SELECT gallerydata FROM database WHERE name=:name", array('name' => $pStrName)); // Return the first record that was found (this is purely for demo purposes) return new gallery($arrData[0]['name']); } } /** * Class galleryStorage_MongoDB * * The mongodb version of the gallery storage class. * It knows how to get a gallery out of a mongodb database */ class galleryStorage_MongoDB implements interface_galleryStorageHandler { /** * @param $pStrName * * @throws Exception */ public function getGalleryByName($pStrName) { throw new Exception('MongoDB is not implemented yet'); } } /** * Class interface_storageHandler * * Defines which methods a gallery storage handler must implement. */ interface interface_galleryStorageHandler { /** * @param $pStrName * * @return mixed */ public function getGalleryByName($pStrName); } /** * Class galleryHandler * * The gallery handler knows how to use a gallerystorage to fulfill the requests that the application will give to it. * Note that the galleryhandler does not know which of the two storage handlers it has been given. * As long as the object in $pObjStorageHandler follows the interface_galleryStorageHandler, it's ok. * */ class galleryHandler { /** * @var interface_storageHandler */ protected $_objStorageHandler; /** * @param $param */ public function setStorageHandler(interface_galleryStorageHandler $pObjStorageHandler) { $this->_objStorageHandler = $pObjStorageHandler; } /** * @param $pStrName * * @return mixed */ public function getGalleryByName($pStrName) { // Call the storagehandler to return the gallery. No clue which storagehandler it is, don't care, // as long as it returns a gallery. return $this->_objStorageHandler->getGalleryByName($pStrName); } } /** * Class router */ class router { /** * */ public function route() { // Create a galleryhandler because all this routine does is get a gallery. $objGalleryHandler = new galleryHandler(); // If the URL contains "?command=gomongo" then the mongodb version of the storagehandler // is injected into the galleryhandler, otherwise it will use the PgSQL one. $strCommand = ''; if (isset($_GET['command'])) { $strCommand = $_GET['command']; } switch ($strCommand) { case 'gomongo': $objGalleryHandler->setStorageHandler(new galleryStorage_MongoDB()); break; default: $objGalleryHandler->setStorageHandler(new galleryStorage_PgSQL()); } echo '<pre>'; // Make the galleryhandler return a gallery $objGallery = $objGalleryHandler->getGalleryByName("poo"); // Make it visible. var_dump($objGallery); } } $objRouter = new router(); $objRouter->route($_SERVER['REQUEST_URI']); Edited October 16, 2013 by vinny42 Quote Link to comment Share on other sites More sharing options...
Solution KevinM1 Posted October 17, 2013 Solution Share Posted October 17, 2013 (edited) Hey Kevin thanks for that info. Def going to ditch the tutorials. I have trying using your code to access the database object but was having some troubles doing so. For instance I was trying to access the some_db() method through the user object. But it couldn't find the some_db() method... I know the code may look silly, as I am getting the hang of how things work class Database { public function some_db() { echo "This is some DB code"; }}class User{ private $db; public function __construct($db) { $this->db = $db; }}$myDB = new Database();$bob = new User($myDB);$bob->some_db(); Since $db is private in User, you can't access it directly (even if I was public, you'd need to use $bob->db->some_db(); ). Create a public method like: public function echoDB() { $this->db->some_db(); } Edited October 17, 2013 by KevinM1 Quote Link to comment Share on other sites More sharing options...
eldan88 Posted October 17, 2013 Author Share Posted October 17, 2013 Thanks Kevin and Ch0cu3, and to everyone else who commented on this post. I will now start working this way. Vinny42, Thank a lot for typing up the code example for me. I will go through it, and try to understand what you where saying about each class needs to preform its separate actions. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.