5

I want to implement a hook-system in my simple ORM, in PHP:

class Record {
  public function save() {
    if (method_exists($this,"before_save")) {
      $this->before_save();
    }
    //...Storing record etc.
  }
}

class Payment extends Record {
  private function before_save() {
    $this->payed_at = time();
  }
}

$payment = new Payment();
$payment->save();

This results in a Fatal Error:

Fatal error: Call to private method Payment::before_save() from context 'Record' in

Makes sense.

I could change the scope to public, but that seems ugly: no-one but Payment has anything to do with before_save(). It is best left private, IMHO.

How can I make Record call a private method on the class inheriting from the Record?

James Jones
  • 3,850
  • 5
  • 25
  • 44
berkes
  • 26,996
  • 27
  • 115
  • 206
  • 2
    can someone tell me why this question got a negative vote ??? – Baba Sep 26 '12 at 14:18
  • Thanks @hakra. I missed one part in the documentation "Members declared protected can be accessed only within the class itself and by inherited **and parent** classes". And always assumed protected to be for children and self only. Apparently it scopes to parents too. Care to make this an answer I can accept? – berkes Sep 26 '12 at 14:22
  • 1
    @hakra: Could you please be a little more polite? If you really are that annoyed by people not knowing something, you are probably at the wrong place on a Q&A forum. – berkes Sep 26 '12 at 14:23
  • @berkes: I added an anser. What I mean is that next to the technical problem of choosing the wrong visibility specifier, `extends` means *"is-a"* but Payment is not a Record, or is it? An no, I'm not annoyed by your question and it's perfectly fair to not know everything. I just wanted to put it away from the sheer technical problem. – hakre Sep 26 '12 at 14:24
  • Payment is a (Database)Record, @hakra. That is what I intended and how it is implemented. – berkes Sep 26 '12 at 14:25
  • 1
    @berekes: Then it's okay until it becomes something else in your application. Then you would need to introduce something else, e.g. a record object. But then the name would have been already taken. So probably choose some better saying names, like `PaymentRecord`. Just a hint. – hakre Sep 26 '12 at 14:27
  • @hakra: though you are correct, that is far beyond the question I posed here. And actually quite debatable; but for your ease-of-mind: I simplified the names to clarify my problem, my actual application uses namespaces and other clutter. – berkes Sep 26 '12 at 14:29
  • 1
    well others are already pointing you out, that the issue is more complex. But you are right, that was not what you have asked for, so I only left that in a comment (which turned out to upset you, so again sorry for that, please don't feel offended). – hakre Sep 26 '12 at 14:49

6 Answers6

8

Add a dummy before_save function to your Record class, set its accessibly to protected. Now all classes that inherit from Record will have this function, if they don't overwrite it it will do NOTHING. If they overwrite it, it can implement the desired functionality.

class Record {
  public function save() {
    $this->before_save();
    //...Storing record etc.
  }

  protected function before_save() {
     return;
  }
}

class Payment extends Record {
  protected function before_save() {
    $this->payed_at = time();
  }
}
clentfort
  • 2,454
  • 17
  • 19
  • 2
    +1 This is also my preference, the parent class should be aware of `before_save()`. – Ja͢ck Sep 26 '12 at 14:25
  • But before_save (and a few more, before_update, after_save, after_insert) are hooks. I could implement them trough a listerener. But I rather follow the ActiveRecord model "if a certain named function exists it will be called OnBeforeFoo and OnAfterFoo()". Should the parent contain all these functions then? – berkes Sep 26 '12 at 14:32
  • 1
    You could go with different interfaces and make `Record` an abstract class. But else yes, Record should have a stub of all the needed possible methods so one can see at what point they were defined etc. – clentfort Sep 26 '12 at 14:35
  • @berkes in that case you could adopt observer pattern instead. – Ja͢ck Sep 26 '12 at 14:37
  • @Jack: an observer adds quite a lot of clutter to the implementation: having to register listeners and all that. For these simple cases, they are more *callbacks* then actual hooks. – berkes Sep 26 '12 at 14:43
  • @berkes not That much clutter though, you should check out `SplObjectStorage` for pointers on how to easily build the registration part. – Ja͢ck Sep 26 '12 at 14:55
  • This doesn't answer the question. In my case I am dealing with extending 3rd party code, which needlessly uses private functions everywhere. I was expecting a way of doing this with a ReflectionMethod class. – Jed Lynch May 20 '20 at 19:12
8

The winning answer doesn't answer the question. The information about "public", "protected", "private", and "final" should be obtainable on any blog or book.

This question asks about how to use a "private" function from an inherit class. The use case here is you're forced to use 3rd party code that is poorly designed, indiscriminately uses private functions, and faced with having to either find a way to use a private function, or fork the entire repo.

Here is the answer to the question.

class foo {

    protected $text = "foo";

    private function bar($text)
    {
        return $text.'->'.$this->text;
    }
}

class fooChild extends foo{

    protected $text = "bar";

    public function barChild()
    {
        $r = new \ReflectionMethod(parent::class, 'bar');
        $r->setAccessible(true);
        //return $r->invokeArgs(new foo(), ['output']); // output->foo
        return $r->invokeArgs($this, ['output']);//output->bar
    }
}

echo (new fooChild())->barChild();

Using the ReflectionMethod class you can call a private method from the inherit class. You can see the difference from using $this and a new instance of the parent. The properties will not be set from the child from a new instance.

Jed Lynch
  • 1,998
  • 18
  • 14
4

Check the error message

Call to private method Payment::before_save() from context 'Record'

This means that you are trying to call a function defined in Payment while you are within Record. Class Record does not have a before_save method because it is further up in the inheritance chain than where the function is defined.

In other words since, the parent-child relation is Record (is parent of) Payment, Payment has access to Records functions (by virtue of inheriting from the parent) but not vice-versa (parent cannot "inherit" child class functions). You can make your function protected which will give it access up and down the inheritance chain, but you might want to rethink the architecture and decide if you want it so.. Ideally you should have the function defined in Record and have it overridden in Payment

Also (and I might be wrong with this), but checking explicitly for method_exists is usually not required unless you are creating a really dynamic system where run time classes can be overlapped and/or generated. If you are defining a class based system from ground-up and you know how you are stitching up the various pieces, usually you would not need to check during run-time if method_exists...just a thought..

raidenace
  • 12,789
  • 1
  • 32
  • 35
  • The reason I call function_exists, is that this is a hook-pattern, where a child implementing a method acc. to a pattern (before_save, before_update, before_insert and after_*) that method will be called. – berkes Sep 26 '12 at 14:34
  • While that is fine, you will either need to define the function in parent class and override it in child, or define the function in child as protected (or public, but that is not desirable)- in order to solve the issue you have – raidenace Sep 26 '12 at 14:37
3

change the scope to protected:

http://php.net/manual/en/language.oop5.visibility.php

RomanKonz
  • 1,027
  • 1
  • 8
  • 15
2

Visibility and the rules of inheritance in PHP:

Members declared protected can be accessed only within the class itself and by inherited and parent classes

hakre
  • 193,403
  • 52
  • 435
  • 836
  • It is a solution but a bad one. It is bad style, people reading your code will have problems figuring where/when/why your "before_save" function is defined, what parameter it expects and so on. Only because you can do it that way doesn't necessarily mean it is a good way. Also another thing: If you are working with an IDE the IDE won't know that all children of `Record` should/can have the function `before_save`! – clentfort Sep 26 '12 at 14:30
0
class test {

    private function pri($val){
        return $val;
    }

    function caller(){
       return $this->pri(5);
    }
}

$testobj = new test;
echo $testobj->caller();

You will get 5 as output.

By this way You can access the private function of a class.

EricSchaefer
  • 25,272
  • 21
  • 67
  • 103
AkiShankar
  • 332
  • 5
  • 16