Proper way to access parent's methods in child class - php

I have a ChildClass that extends to a ParentClass. The ParentClass has a constructor that takes two arguments __construct('value2', ParentClass::COMMON). HOwever I am trying to call the inherited newSelect() method from within the child class. So far it has not been succesfull. How would I call NewSelect() from the ChildClass? Also is it possible even though the ParentClass has a constructor that takes two parameters?
Parent
class ParentClass {
const COMMON = 'common';
protected $db;
protected $common = false;
protected $quotes = array(
'Common' => array('"', '"'),
'Value2' => array('`', '`'),
'Value3' => array('`', '`'),
'Value4' => array('`', '`'),
);
protected $quote_name_prefix;
protected $quote_name_suffix;
public function __construct($db, $common = null) {
$this->db = ucfirst(strtolower($db));
$this->common = ($common === self::COMMON);
$this->quote_name_prefix = $this->quotes[$this->db][0];
$this->quote_name_suffix = $this->quotes[$this->db][1];
}
public function newSelect() {
return $this->newInstance('Select');
}
protected function newInstance($query) {
//Some more code, not relevant to example
}
}
Child
class ChildClass extends ParentClass {
public function __construct() {
}
// Call the inherited method
private $_select = Parent::newSelect();
// Do something with it
}

// you can't do this
private $_select = Parent::newSelect();
// try this
private $_select;
public function construct($value, $common)
{
Parent::__construct($value, $common);
$this->_select = $this->newSelect();
}
// and
$obj = new ChildClass('Value2', ParentClass::COMMON); // ParentClass::COMMON - not sure why you would do this
$select = $obj->newSelect(); // this is being called in constructor, why call it again
and to be honest, I don't even know what you're trying to do but everything about it looks wrong too!

Related

MongoDB - PHP - Factory method implementation

There are lots of articles regarding factory method implementation in PHP.
I want to implement such a method for my MongoDB implementation in PHP.
I wrote the code something like below. Please Look at that code.
<?php
class Document {
public $value = array();
function __construct($doc = array()) {
$this->value = $doc;
}
/** User defined functions here **/
}
class Collection extends Document {
//initialize database
function __construct() {
global $mongo;
$this->db = Collection::$DB_NAME;
}
//select collection in database
public function changeCollection($name) {
$this->collection = $this->db->selectCollection($name);
}
//user defined method
public function findOne($query = array(), $projection = array()) {
$doc = $this->collection->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function find($query = array(), $projection = array()) {
$result = array();
$cur = $this->collection->find($query, $projection);
foreach($cur as $doc) {
array_push($result, new Document($doc));
}
return $result;
}
/* Other user defined methods will go here */
}
/* Factory class for collection */
class CollectionFactory {
private static $engine;
private function __construct($name) {}
private function __destruct() {}
private function __clone() {}
public static function invokeMethod($collection, $name, $params) {
static $initialized = false;
if (!$initialized) {
self::$engine = new Collection($collection);
$initialized = true;
}
self::$engine->changeCollection($collection);
return call_user_func_array(array(self::$engine, $name), $params);
}
}
/* books collection */
class Books extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('books', $name, $params);
}
}
/* authors collection */
class Authors extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('authors', $name, $params);
}
}
/* How to use */
$books = Books::findOne(array('name' => 'Google'));
$authors = Authors::findOne(array('name' => 'John'));
Authors::update(array('name' => 'John'), array('name' => 'John White'));
Authors::remove(array('name' => 'John'));
?>
My questions are:-
Is this correct PHP implementation of Factory method?
Does this implementation have any issues?
Are there any better methodologies over this for this scenario?
Thanks all for the answers.
Hmm no, because with your piece of code you make ALL methods on the collection class available for a static call. That's not the purpose of the (abstract) factory pattern.
(Magic) methods like __callStatic or call_user_func_array are very tricky because a developer can use it to call every method.
What would you really like to do? Implement the factory pattern OR use static one-liner methods for your MongoDB implementation?!
If the implementation of the book and author collection has different methods(lets say getName() etc..) I recommend something like this:
class BookCollection extends Collection {
protected $collection = 'book';
public function getName() {
return 'Book!';
}
}
class AuthorCollection extends Collection {
protected $collection = 'author';
public function getName() {
return 'Author!';
}
}
class Collection {
private $adapter = null;
public function __construct() {
$this->getAdapter()->selectCollection($this->collection);
}
public function findOne($query = array(), $projection = array()) {
$doc = $this->getAdapter()->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function getAdapter() {
// some get/set dep.injection for mongo
if(isset($this->adapter)) {
return $this->adapter;
}
return new Mongo();
}
}
class CollectionFactory {
public static function build($collection)
{
switch($collection) {
case 'book':
return new BookCollection();
break;
case 'author':
return new AuthorCollection();
break;
}
// or use reflection magic
}
}
$bookCollection = CollectionFactory::build('book');
$bookCollection->findOne(array('name' => 'Google'));
print $bookCollection->getName(); // Book!
Edit: An example with static one-liner methods
class BookCollection extends Collection {
protected static $name = 'book';
}
class AuthorCollection extends Collection {
protected static $name = 'author';
}
class Collection {
private static $adapter;
public static function setAdapter($adapter) {
self::$adapter = $adapter;
}
public static function getCollectionName() {
$self = new static();
return $self::$name;
}
public function findOne($query = array(), $projection = array()) {
self::$adapter->selectCollection(self::getCollectionName());
$doc = self::$adapter->findOne($query, $projection);
return $doc;
}
}
Collection::setAdapter(new Mongo()); //initiate mongo adapter (once)
BookCollection::findOne(array('name' => 'Google'));
AuthorCollection::findOne(array('name' => 'John'));
Does it make sense for Collection to extend Document? It seems to me like a Collection could have Document(s), but not be a Document... So I would say this code looks a bit tangled.
Also, with the factory method, you really want to use that to instantiate a different concrete subclass of either Document or Collection. Let's suppose you've only ever got one type of Collection for ease of conversation; then your factory class needs only focus on the different Document subclasses.
So you might have a Document class that expects a raw array representing a single document.
class Document
{
private $_aRawDoc;
public function __construct(array $aRawDoc)
{
$this->_aRawDoc = $aRawDoc;
}
// Common Document methods here..
}
Then specialized subclasses for given Document types
class Book extends Document
{
// Specialized Book functions ...
}
For the factory class you'll need something that will then wrap your raw results as they are read off the cursor. PDO let's you do this out of the box (see the $className parameter of PDOStatement::fetchObject for example), but we'll need to use a decorator since PHP doesn't let us get as fancy with the Mongo extension.
class MongoCursorDecorator implements MongoCursorInterface, Iterator
{
private $_sDocClass; // Document class to be used
private $_oCursor; // Underlying MongoCursor instance
private $_aDataObjects = []; // Concrete Document instances
// Decorate the MongoCursor, so we can wrap the results
public function __construct(MongoCursor $oCursor, $sDocClass)
{
$this->_oCursor = $oCursor;
$this->_sDocClass = $sDocClass;
}
// Delegate to most of the stock MongoCursor methods
public function __call($sMethod, array $aParams)
{
return call_user_func_array([$this->_oCursor, $sMethod], $aParams);
}
// Wrap the raw results by our Document classes
public function current()
{
$key = $this->key();
if(!isset($this->_aDataObjects[$key]))
$this->_aDataObjects[$key] =
new $this->sDocClass(parent::current());
return $this->_aDataObjects[$key];
}
}
Now a sample of how you would query mongo for books by a given author
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
// Wrap the native cursor by our Decorator
$cursor = new MongoCursorDecorator($cursor, 'Book');
foreach ($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}
You could tighten it up a bit with a MongoCollection subclass and you may as well have it anyway, since you'll want the findOne method decorating those raw results too.
class MongoDocCollection extends MongoCollection
{
public function find(array $query=[], array $fields=[])
{
// The Document class name is based on the collection name
$sDocClass = ucfirst($this->getName());
$cursor = parent::find($query, $fields);
$cursor = new MongoCursorDecorator($cursor, $sDocClass);
return $cursor;
}
public function findOne(
array $query=[], array $fields=[], array $options=[]
) {
$sDocClass = ucfirst($this->getName());
return new $sDocClass(parent::findOne($query, $fields, $options));
}
}
Then our sample usage becomes
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoDocCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
foreach($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}

My mixin class is causing my classes to become static - PHP

So this is my mixin class:
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$object_name = get_class($object);
$this->_class_objects[$object->name] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object_name => $class_object){
$this->_methods[$class_object_name] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $class_name=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->isParam($class_name, $method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function isParam($class_name, $method, $param){
if($param != null){
call_user_func(array($class_name, $method), $param);
}else{
call_user_func(array($class_name, $method));
}
}
}
Pretty simple stuff, load a set of classes, allow you to call their functions and so on, but we have a new issue. It seems that classes passed into this are instantiated as static, thus their methods cannot use $this-> they are resorted to using self:: which is wrong.
Lets see an example of how this all works:
class BaseBridge extends AisisCore_Loader_Mixins{
public function __construct(){
parent::construct();
$this->setup(array('ClassB' => array()));
}
}
Lets Define ClassB
class ClassB{
public function __construct(){}
public function some_method(){
$this->_some_private_method();
}
private function _some_private_method(){}
}
Pretty basic stuff, so lets hook it all up in ClassA
class ClassA extends BaseBridge{
public function __construct(){
parent::__construct();
$this->some_method();
}
}
Quick Review: We have a core class, ClassA which extends BaseBridge which is our bridge class between one or more (meant to be used with more) classes that ClassA extends from. In this case were only extending from ClassB for simplicity.
Whats the issue? See, how in ClassB, were doing: $this->_some_private_method(); ya that's going to epically and catastrophically fail. Why? because I get the error: Using $this when not in object context which makes me so confused, so I change it to: self::$_some_private_method(); and it works like a charm.
Why? and what do I have to change or fix to make it so that $this can be used in a class being instantiated through the mixin class?
So with some slight modifications, I have managed to make this work. How ever I do not believe that a function with multiple arguments will work - Feed back appreciated.
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$this->_class_objects[$object->name] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object_name => $class_object){
$this->_methods[$class_object_name] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $class_name=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->_is_param($class_name, $method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function _is_param($class_name, $method, $param){
if($param != null){
$this->_param_is_array($class_name, $method, $param);
}else{
call_user_func(array($this->_class_objects[$class_name], $method));
}
}
private function _param_is_array($class_name, $method, $param){
if(is_array($param)){
call_user_func_array(array($this->_class_objects[$class_name], $method), $param);
}else{
call_user_func(array($this->_class_objects[$class_name], $method, $param));
}
}
}
Now functions inside of classes that are registered by this class can use $this->.
The issue is that I am not sure if multiple param based functions will actually work.

Inheritance in PHP - Creating child instance and calling parent method

I have something like this:
class MyParent {
protected static $object;
protected static $db_fields;
public function delete() {
// delete stuff
}
public static function find_by_id($id = 0) {
global $database;
$result_array = self::find_by_sql("SELECT * FROM " . static::$table_name . " WHERE id=" . $database -> escape_value($id) . " LIMIT 1");
return !empty($result_array) ? array_shift($result_array) : false;
}
public static function find_by_sql($sql = "") {
global $database;
// Do Query
$result_set = $database -> query($sql);
// Get Results
$object_array = array();
while ($row = $database -> fetch_array($result_set)) {
$object_array[] = self::instantiate($row);
}
return $object_array;
}
private static function instantiate($record) {
$object = self::$object;
foreach ($record as $attribute => $value) {
if (self::has_attribute($attribute)) {
$object -> $attribute = $value;
}
}
return $object;
}
}
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = new TheChild;
}
}
$child= TheChild::find_by_id($_GET['id']);
$child->delete();
I get this: Call to undefined method stdClass::delete() referring to the last line above. What step am I missing for proper inheritance?
You never actually instanciate the TheChild class, which should be done by
$var = new TheChild();
except in TheChild constructor itself.
So, the static $object field is never affected (at least in your example), so affecting a field to it (the line $object -> $attribute = $value; ) causes the creation of an stdClass object, as demonstrated in this interactive PHP shell session:
php > class Z { public static $object; }
php > Z::$object->toto = 5;
PHP Warning: Creating default object from empty value in php shell code on line 1
php > var_dump(Z::$object);
object(stdClass)#1 (1) {
["toto"]=>
int(5)
}
This object does not have a delete method.
And as said before, actually creating a TheChild instance will result in an infinite recursion.
What you want to do is this, probably:
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = $this;
}
}
Edit: Your updated code shows a COMPLETE different Example:
class MyParent {
protected static $object;
public function delete() {
// delete stuff
}
}
class TheChild extends MyParent {
function __construct() {
self::$object = new TheChild;
}
}
$child = new TheChild;
$child->delete();
Calling "Child's" Constructor from within "Child's" Constructor will result in an infinite loop:
function __construct() {
self::$object = new TheChild; // will trigger __construct on the child, which in turn will create a new child, and so on.
}
Maybe - i dont know what you try to achieve - you are looking for:
function __construct() {
self::$object = new MyParent;
}
ALSO note, that the :: Notation is not just a different Version for -> - it is completely different. One is a Static access, the other is a access on an actual object instance!

How do i deal with multiple contructor arguments or class variables?

How do I know what to load in a constructor and what to set using the set methods later on?
For example, I have a question class which most of the time will call the following vars:
protected $question;
protected $content;
protected $creator;
protected $date_added;
protected $id;
protected $category;
At the moment I have it so only the bare essentials $id, $question, and $content are set in the constructor so I don't start building up a huge list of constructor arguments. This however, means that when I make a new question object elsewhere, I have to set the other properties of that object straight after meaning 'setter code' getting duplicated all over the place.
Should I just pass them all into the constructor right away, do it the way I'm doing it already, or is there a better solution that I'm missing? Thanks.
Depending on the language you can have multiple constructors for any one class.
You could use an array as the parameter to the constructor or setter method.
Just example:
public function __construct($attributes = array()) {
$this->setAttributes($attributes);
}
public function setAttributes($attributes = array()) {
foreach ($attributes as $key => $value) {
$this->{$key} = $value;
}
}
PHP doesn't support traditional constructor overloading (as other OO languages do). An option is to pass an array of arguments into the constructor:
public function __construct($params)
{
}
// calling it
$arr = array(
'question' => 'some question',
'content' => ' some content'
...
);
$q = new Question($arr);
Using that, you're free to pass a variable number of arguments and there is no dependency on the order of arguments. Also within the constructor, you can set defaults so if a variable is not present, use the default instead.
I would pass an array to the constructor with the values I want to set.
public function __construct(array $values = null)
{
if (is_array($values)) {
$this->setValues($values);
}
}
Then you need a method setValues to dynamicly set the values.
public function setValues(array $values)
{
$methods = get_class_methods($this);
foreach ($values as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
For this to work you need setter methods for your properties like setQuestion($value) etc.
A fluent interface is another solution.
class Foo {
protected $question;
protected $content;
protected $creator;
...
public function setQuestion($value) {
$this->question = $value;
return $this;
}
public function setContent($value) {
$this->content = $value;
return $this;
}
public function setCreator($value) {
$this->creator = $value;
return $this;
}
...
}
$bar = new Foo();
$bar
->setQuestion('something')
->setContent('something else')
->setCreator('someone');
Or use inheritance...
class Foo {
protected $stuff;
public function __construct($stuff) {
$this->stuff = $stuff;
}
...
}
class bar extends Foo {
protected $moreStuff;
public function __construct($stuff, $moreStuff) {
parent::__construct($stuff);
$this->moreStuff = $moreStuff;
}
...
}
Or use optional parameters...
class Foo {
protected $stuff;
protected $moreStuff;
public function __construct($stuff, $moreStuff = null) {
$this->stuff = $stuff;
$this->moreStuff = $moreStuff;
}
...
}
In any case, there are many good solutions. Please dont use a single array as params or func_get_args or _get/_set/__call magic, unless you have a really good reason to do so, and have exhausted all other options.

workaround for multiple inheritances in PHP?

In a lot of my PHP classes, I have this code:
private $strError = "";
private $intErrorCode = NULL;
private $blnError = FALSE;
public function isError() {
return $this->blnError;
}
public function getErrorCode() {
return $this->intErrorCode;
}
private function setError( $strError, $intErrorCode = NULL ) {
$this->blnError = TRUE;
$this->intErrorCode = $intErrorCode;
$this->strError = $strError;
}
The point is so that outside code can know if an object has an error state, what the string of the error is, etc. But to have this exact code in a bunch of different classes is repetitious!
I'd love to have a dual-extension where I could do
class childClass extends parentClass, error {
...
}
And have those properties and methods inborn, But PHP doesn't support multiple inheritances. What I'm thinking about doing is creating an error class that exists inside each class. If I make it public, I can call it directly through the object
if ( $myObject->error->isError() ) {...}
but wouldn't that also make its error status settable from outside the containing class,
$myObject->error->setError("I shouldn't be doing this here");
which I would rather avoid?
Or I could write 'gateway' functions in the containing class, which do the appropriate calls on the error object, and prevent setting the error status from outside,
class childClass extends parentClass {
private $error;
public function __construct(...) {
...
$error = & new error();
...
}
public function isError() {...}
public function getError() {...}
public function getErrorCode() {...}
private function setError() {...}
...
}
but that leads to (some of) the code duplication that I'm trying to avoid.
What's the optimal solution here? I'm trying to have functionality for error statuses for a number of objects, so that the outside world can see their error state, with minimal repetition.
Use composition instead of inheritance.
class Errors {
private $strError = "";
private $intErrorCode = NULL;
private $blnError = FALSE;
public function isError() {
return $this->blnError;
}
public function getErrorCode() {
return $this->intErrorCode;
}
private function setError( $strError, $intErrorCode = NULL ) {
$this->blnError = TRUE;
$this->intErrorCode = $intErrorCode;
$this->strError = $strError;
}
}
And now use a private instance variable to refer to it:
class childClass extends parentClass {
private $errors = new Errors();
...
}
The private visibility prevents you from referencing $errors outside of the class.
There's also no need to create isError(), getError(), etc. inside childClass (and therefore no need to worry about code duplication). Simply call $this->errors->isError(), $this->errors->getError(), etc. If you still wanted to require those methods to be implemented though, as suggested below, you could specify an interface.
You could also abuse the __call magic method to do the same thing:
public function __call($name, array $arguments) {
$name = strtolower($name);
if (isset($this->methods[$name])) {
array_unshift($arguments, $this);
return call_user_func_array($this->methods[$name], $arguments);
}
throw new BadMethodCallException('Method does not exist');
}
Note that I said abuse... Ideally, I'd think of a different architecture rather than having all these "common methods" everywhere. Why not use an exception instead of checking $foo->isError? If that's not appropriate, why not decorate a class?
class Errors
protected $object = null;
public function __construct($object) {
$this->object = $object;
}
public function __call($method, array $arguments) {
$callback = array($this->object, $method);
if (is_callable($callback)) {
return call_user_func_array($callback, $arguments);
}
throw new BadMethodCallException('Method does not exist');
}
public function __get($name) { return $this->object->$name; }
public function __set($name, $value) { $this->object->$name = $value; }
// Your methods here
public function isInstance($name) { return $this->object instanceof $name; }
}
Then just "wrap" your existing object in that class:
$obj = new Errors($obj);
$obj->foo();
As of PHP 5.4, you can use Traits.
For example you could make Trait called ErrorTrait like this:
trait ErrorTrait {
private $strError = "";
private $intErrorCode = NULL;
private $blnError = FALSE;
public function isError() {
return $this->blnError;
}
public function getErrorCode() {
return $this->intErrorCode;
}
private function setError( $strError, $intErrorCode = NULL ) {
$this->blnError = TRUE;
$this->intErrorCode = $intErrorCode;
$this->strError = $strError;
}
}
Then you would define your child class like this:
class childClass extends parentClass {
use ErrorTrait;
...
}
Traits work basically like copy/paste so all of the code in the trait would be available within the class (without the code duplication).

Categories