margaux Posted March 11, 2013 Share Posted March 11, 2013 I'm struggling to understand how class inheritance works. I have 2 classes, one inherited by the other. class DatabaseObject { public static function find_all() { return static::find_by_query("SELECT * FROM " . self::$table_name); } } class Photograph extends DatabaseObject { public function image_path() { return $this->upload_dir .DS. $this->filename; } } In another file I try to access the method $photos = Photograph::find_all(); foreach ($photos as $photo) { echo '<figure><img src=../"'. $photo->image_path() . '" width="100"><figcaption>' . $photo->caption . ' ' . $photo->size_as_text() . '</figcaption></figure>'; } but get the error message Fatal error: Call to undefined method DatabaseObject::image_path() in /Applications/MAMP/htdocs/photo_gallery/public/admin/view_photos.php on line 22 I don't understand why its trying to access the method through the DatabaseObject class when I have specified the Photograph class. Would anyone be able to explain what is going on here? Hopefully I've provided enough information. Thanks Link to comment Share on other sites More sharing options...
Edward Posted March 11, 2013 Share Posted March 11, 2013 Shouldn't you create an instance of the Photograph object with the "new" keyword. I don't see how that would work because those methods are not even static. Link to comment Share on other sites More sharing options...
Edward Posted March 11, 2013 Share Posted March 11, 2013 If one method is static I think they all have to be? Maybe you can't access it because of variable scope. Link to comment Share on other sites More sharing options...
margaux Posted March 11, 2013 Author Share Posted March 11, 2013 Edward - thanks for your responses and your questions as they are raising fundamentals that I haven't quite got to grips with yet. find_all() is a static method defined in the databaseObject class which is inherited by other classes including Photograph. The find_all() method gets all the records in the database, instantiates each record as a new instance of the calling class and returns an array of objects. so $photos in the original code should be an array of Photograph objects which I can loop through and display. I've dumped out the returned array being returned and its full of objects not fully formed e.g. Here is my databaseObject class array(3) { [0]=> object(DatabaseObject)#5 (1) { ["db_fields":protected]=> array(0) { } } [1]=> object(DatabaseObject)#6 (1) { ["db_fields":protected]=> array(0) { } } [2]=> object(DatabaseObject)#7 (1) { ["db_fields":protected]=> array(0) { } } } class DatabaseObject { protected static $table_name; protected $db_fields=array(); //common database methods public static function find_all() { return static::find_by_query("SELECT * FROM " . self::$table_name); } public static function find_by_id($id) { global $database; $result_array = static::find_by_query("SELECT * FROM " . self::$table_name . " WHERE id = {$id} LIMIT 1"); return !empty($result_array) ? array_shift($result_array) : false; } public static function find_by_query($q="") { global $database; $r = $database->query($q); $object_array = array(); while ($row = $database->fetch_array($r)) { $object_array[] = static::instantiate($row); } return $object_array; } protected static function instantiate($record) { $object = new self; foreach ($record as $attribute=>$value) { if ($object->has_attribute($attribute)){ $object->attribute = $value; } } return $object; } protected function has_attribute($attribute) { // returns an assoc array with all attributes as the // keys and their current values as the values $object_vars = $this->attributes(); //use array_key_exists to determine if the passed attribute exists as an // attribute of the current object return array_key_exists($attribute, $object_vars); } protected function attributes() { //return an array of attribute key value pairs for db fields only $attributes=array(); foreach ($this->db_fields as $field) { if (property_exists($this, $field)) { $attributes[$field]=$this->$field; } } return $attributes; } and here is the Photograph class class Photograph extends DatabaseObject { protected static $table_name="photographs"; public $db_fields=array('id', 'filename', 'type', 'size', 'caption'); //attributes correponding to d/b table columns public $id; public $filename; public $type; public $size; public $caption; I imagine I've made a mess of the visibility and I'm not sure that the array $db_fields from the Photograph class has been properly coded. Hope this makes sense and you can give me some guidance. If you don't want to look through all this, I understand. Link to comment Share on other sites More sharing options...
Antonio Conte Posted March 11, 2013 Share Posted March 11, 2013 The reason for the error message is your problem here. Static methods are not inherited as they are static. Static methods do not get inherited by childeren that extends a parent. I would GUESS the confusion comes after seing YII code. As that is a framework, it can't be looked at as normal PHP code. However, this is done through some "magic" that hides the essential parts of the code. Larry might be able to clarify on this. It's way over my head without diving into the framework. (I can't give a CLEAR explanation at least) A better suggestion might be to look at another library using the factory pattern. RedBeans PHP is an ORM (Active Records is an ORM pattern) build in PHP. It also uses the factory pattern for object creation. Here is how you perform simple CRUD: // Insert (Note: Returns Post objects) $post = R::dispense('post'); $post->text = 'Hello World'; //Create or Update $id = R::store($post); // Get $post = R::load('post',$id); // Delete R::trash($post); This is very similar to something like User::get_by_pk() or what it's called in YII. However, these methods are not INHERITED. A possible solution is using the magic method like __get() and __set(). (which I actually know YII does) These methods could do a call to a method found in ActiveRecords (which most DB models in YII extend) looking something like: (__set() is as we know called when a method is not found in a class) __set( $method ) { return parent::set(__CLASS__, $method) } Active Records could then transform that into something like: (ta-da) __set( $class = __CLASS__ ) { return ActiveRecordsClass::$method($class); } Obviously, this is framework magic and not very interesting for most of us, but that's a very plausible explanation at least. Hope my stupid ramblings clearify something atleast. 1 Link to comment Share on other sites More sharing options...
margaux Posted March 11, 2013 Author Share Posted March 11, 2013 I'm not using / haven't looked at a framework. I was under the impression that static methods and properties could be inherited by child classes as long as the visibility was set correctly i.e. not private. Maybe I should move the static methods find_by_id(), find_all() and find_by_query() into the child classes but seems a shame to repeat all that code in several classes. I was hoping with the use of late static binding to be able to make the databaseObject class more abstract. Because the objects aren't being instantiated properly, I wonder if the problem is how the array $db_fields is set up. Link to comment Share on other sites More sharing options...
margaux Posted March 12, 2013 Author Share Posted March 12, 2013 Getting closer - I knew that something was wrong with how the object was being instantiated.. Have changed the method to use get_called_class in the instantiate method. protected static function instantiate($record) { $class_name = get_called_class(); $object = new $class_name; foreach ($record as $attribute=>$value) { if ($object->has_attribute($attribute)){ $object->attribute = $value; } } return $object; } now the object looks like this array(4) { [0]=> object(Photograph)#5 (11) { ["db_fields"]=> array(5) { [0]=> string(2) "id" [1]=> string(8) "filename" [2]=> string(4) "type" [3]=> string(4) "size" [4]=> string(7) "caption" } ["id"]=> NULL ["filename"]=> NULL ["type"]=> NULL ["size"]=> NULL ["caption"]=> NULL ["temp_path":"Photograph":private]=> NULL ["upload_dir":protected]=> string(6) "images" ["errors"]=> array(0) { } ["upload_errors":protected]=> array(8) { [0]=> string(26) "File uploaded successfully" [1]=> string(41) "File size larger than upload_max_filesize" [2]=> string(40) "File size larger than form MAX_FILE_SIZE" [3]=> string(28) "File only partially uploaded" [4]=> string(19) "File does not exist" [6]=> string(34) "Temporary directory does not exist" [7]=> string(19) "Can't write to disk" [8]=> string(34) "File upload prevented by extension" } ["attribute"]=> string(10) "sunflowers" } Need to figure out why the values from the d/b aren't getting into the object. Link to comment Share on other sites More sharing options...
margaux Posted March 12, 2013 Author Share Posted March 12, 2013 Found the error. This line in the instantiate function $object->attribute = $value; should be $object->$attribute = $value; This part of the project working nicely now - I can extend databaseObject class to User class and Photograph class and all the CRUD is taken care of. Now to try to create a logfile using sessions so I will probably be back Link to comment Share on other sites More sharing options...
sendy Posted March 12, 2013 Share Posted March 12, 2013 erm i don't know if this would help or not, but try to use new static() //for static binding will return the class that call it instead of new self() // will return the class that have the method definition (if u dont override the method) note: sorry for the bad english.. correct me if im wrong Link to comment Share on other sites More sharing options...
margaux Posted March 13, 2013 Author Share Posted March 13, 2013 To clarify my understanding, it would be good to get some feedback re some of the comments in this thread. Static methods do not get inherited by childeren that extends a parent. In my parent class(databaseObject) is a public static function find_by_all() which I'm accessing via child classes (Comment and Photograph) If one method is static I think they all have to be? I've not seen this before and my classes have both static and non static functions. new self() // will return the class that have the method definition (if u dont override the method) I understood the point of inheritance was that the child class could access the parent method through an instance of itself (the child) without the need for overriding. Link to comment Share on other sites More sharing options...
sendy Posted March 13, 2013 Share Posted March 13, 2013 im still newbie and i dont know much, but from what i know , protected static function instantiate($record) { $object = new self; foreach ($record as $attribute=>$value) { if ($object->has_attribute($attribute)){ $object->attribute = $value; } } return $object; } new self() will return the class that has define the method(even if u call Photograph::instantiate()) which is your DatabaseObject. then in that code, u try to do : $object->attribute = $value /* $object is instance of DatabaseObject because of the new self() , and because of that, it doesn't have the attribute u need.. */ its like this case : class ParentObject { protected static function getObject() { return new self(); } } class ChildObject extends ParentObject { public static function getObjectChild() { return self::getObject(); } } $obj = ChildObject::getObjectChild(); echo '<pre>' . print_r($obj) . '</pre>'; the print_r will show ParentObject, but if u change the ParentObject::getObject() to : class ParentObject { protected static function getObject() { return new static(); } } print_r will show ChildObject(); CMIIW Link to comment Share on other sites More sharing options...
Recommended Posts