tags:

views:

101

answers:

6

I have two classes:

abstract Entity, abstract ClassA extends Entity, ClassB extends ClassA

Entity is an generic class for all kinds of entities, such as Cars or Students. There is an static class variable called $_entity_name, which contains the exact class name of the entity in the ER diagram.

The big problem: Entity can't set the value of this variable, but uses it! So I must define it in Entity, but I can't redefine it in ClassA.

ClassA is the auto-generated base class of an special ORM entity (lets say "Student"). The user is supposed to subclass this base class to add his own business logic.

ClassB is created by the user and extends ClassA. That's the place where he adds his business logic. He's not supposed to touch $_entity_name or set this value.

The only way I see is this:

ClassA defines an init() method which initializes these "system level" instance variables to reasonable values.

I want the user to not have to think at all about calling anything which is unusual. I don't know if it's usual in PHP to explicitely also call the constructor of the parent class, or not. But I guess it isn't.

Questions: A) What's the most convenient way to call init() after instantiation of the class? B) Is there a way to achieve that in such a way, that the user who creates ClassB must not think at all about calling init()? C) What other options do I have?

+1  A: 

Not exactly an answer to your question but maybe a solution for your problem:
Instead of having a property containing the actual class name (that's what you wanted, isn't it?) you can use get_called_class() (as of php 5.3.0) to get the name of the class for which the static function has been called.

 class Entity {
   public static function foo() {
     echo 'name=', get_called_class(), "\n";
   }
 }

 class ClassA extends Entity { }
 class ClassB extends ClassA { }

 Entity::foo();
 ClassA::foo();
 ClassB::foo();

prints

 name=Entity
 name=ClassA
 name=ClassB
VolkerK
Although it sounds good, unfortunately I didn't ment the PHP class, but the Entity name in the ER diagram. It's a little bit different.
openfrog
I.e. the developer _has to_ provide a meaningful value, something that cannot be deducted from the information your class can get when the property is used for the first time? Something more or less like `abstract public static $_entity_name;` (which isn't available in php)?
VolkerK
+1  A: 

It is actually fairly common to call the parent constructor using parent::__construct(). Would it be acceptable for you to do the following?

class B extends A { 
    function __construct($entity_name = '') {
        parent::__construct($entity_name);
    }
}

abstract class A extends Entity { 
    function __construct($entity_name = '') {
        parent::__construct($entity_name);
    }
}

abstract class Entity { 
    static $entity_name;
    function __construct($entity_name = '') {
        self::$entity_name = $entity_name;
    }
}

$b = new B('my_name');
cballou
Thanks, but ClassB is part of the business logic layer and created by the user, and thus not supposed to mess with the framework logic. Under any other circumstance this would be great, though!
openfrog
A: 

VolkerK's answer is the right one - but you could also look at the instanceof operator and the get_class() functions.

"unfortunately I didn't ment the PHP class, but the Entity name in the ER diagram. It's a little bit different" (openfrog)

That's a completely different problem, however (assuming you can't fix this defect in your entity naming system) mapping entries between 2 lists is a no-brainer.

Your solution of using a property at run-time is very messy and inflexible. It relies on an implicit mapping, the details of which are scattered all over your codebase and any non-linear heuristic will be very difficult to maintain/debug (e.g. if more than one class maps to the same entity in your ERD).

C.

symcbean
A: 

Underneath ClassB, I see two ways to ensure initialization of ClassA:

  1. instruct that ClassB's constructor either calls parent::__construct() or does not exist.

  2. create a hook init function that is called by ClassA and implemented by ClassB. This way, ClassA handles initialization of ClassA in __construct() and ClassB with $this->init()

Derek Illchuk
A: 

As far as I understand your question: you have ClassB that is a subclass of ClassA and when a ClassB object is instantiated, you want to have a method (e.g. init()) to be called on ClassA level without having your user write anything of that sort in ClassB code.

That is what constructors are for.

However, you hinted that since ClassB would be written by your user, you don't want to force him/her to call parent::__construct() from ClassB's constructor, so that's the reason why you thought of using init() method and also why you are looking for a way to have init() to be automagically called the way __construct() is, am I right? So, how about this:

  • add empty init() method in ClassA that the user has to overload when he/she writes ClassB
  • do the stuff you wanted to do in ClassA's __construct() (e.g. initialize $_entity_name) in ClassA's __construct() plus call $this->init().
  • set ClassA's __construct() as final so that the user can't overload the constructor.
  • tell your users that they have to overload the init() method to do custom initialization for subclasses of ClassA

pros: you can have the initialization stuff done by ClassA without having the user to write anything.

cons: your user cannot overload __construct() but can overload init() instead to do custom initialization in for ClassB

Lukman
A: 

This suggestion can't be overridden which would suit your needs.

Class A{
   final public function __construct() {
       // do your stuff here

       // optionally add something like this:
       $rc = new ReflectionClass($this);
       if(in_array('init', $rc->getMethods()) {
           $this->init();
       }
   }

If it doesn't do what you want (i.e. you'd like classes deriving from A to be able to use __construct()), you should think about encapsulating your class instead of inheriting from it.

chelmertz