19

To restore the state of an object which has been persisted, I'd like to create an empty instance of the class, without calling its constructor, to later set the properties with Reflection.

The only way I found, which is the way Doctrine does, is to create a fake serialization of the object, and to unserialize() it:

function prototype($class)
{
    $serialized = sprintf('O:%u:"%s":0:{}', strlen($class), $class);
    return unserialize($serialized);
}

Is there another, less hacky way, to do that?

I was expecting to find such a way in Reflection, but I did not.

hakre
  • 193,403
  • 52
  • 435
  • 836
BenMorel
  • 34,448
  • 50
  • 182
  • 322
  • possible duplicate of [How does Doctrine 2 retrieve entities without calling the entity's constructor?](http://stackoverflow.com/questions/6555237/how-does-doctrine-2-retrieve-entities-without-calling-the-entitys-constructor) – hakre Aug 04 '11 at 16:24
  • 1
    @hakre: no, that question only raises the question of serialization, not the alternatives. – BenMorel Aug 04 '11 at 16:27
  • Which alternatives? And what is "less hacky"? - Btw: StdClass objects don't have a constructor, so theirs is not called anyway. – hakre Aug 04 '11 at 16:30
  • Which alternatives: that is the question I'm asking: what *are* the alternatives, if any? See my personal definition for "less hacky" in the comments below your answer. – BenMorel Aug 04 '11 at 16:55

5 Answers5

18

Update: ReflectionClass::newInstanceWithoutConstructor is available since PHP 5.4!

BenMorel
  • 34,448
  • 50
  • 182
  • 322
  • @Petah: do you have any source for this? AFAIK, PHP 5.4.0 is not a RC, therefore it is considered "production ready" by the PHP team. That said, it looks like some distributions such as Debian do not consider it "production ready" yet for some reason. – BenMorel Mar 12 '12 at 09:13
  • yes it might be theoretically production ready, but I say its not because no major distros, or hosting providers offer it. Also there will no doubt be a lot of bugs that surface after the initial release. Hence why I think 5.4.3 will be the golden version where major websites and providers adopt it. – Petah Mar 12 '12 at 09:33
9

Another way will be to create a child of that class with and empty constructor

class Parent {
  protected $property;
  public function __construct($arg) {
   $this->property = $arg;
  }
}

class Child extends Parent {

  public function __construct() {
    //no parent::__construct($arg) call here
  }
}

and then to use the Child type:

$child = new Child();
//set properties with reflection for child and use it as a Parent type
catalin.costache
  • 3,123
  • 1
  • 25
  • 15
  • What do you do if the child class needs actually parameters in it's constructor and the constructor to be called? – hakre Aug 04 '11 at 16:40
  • @hakre you can provide as many actual parameters as you want. But that is not the use case that Benjamin was talking about. He wants to make a prototype of the Parent class without setting any properties when instantiating the class - the properties will be latter set by Reflection. The doctrine2 for example needs that because it has no control of how a Entity __constructor will look like - it's implementation is in the user hands - but it knows how the properties look like because they are mapped with metadata (annotations, xml, yml, etc) – catalin.costache Aug 04 '11 at 16:53
  • @catalin.costache: while not fulfilling the original requirement, your solution is interesting and reminds me of how Doctrine creates proxies for related user class instances. It's quite strange that they didn't choose that solution for the retrieved base instances, actually! Looks like it would've been more consistent to me - that's just an opinion without yet a great deal of thinking coming with it, that said :-) – BenMorel Aug 04 '11 at 17:02
  • I'm not entirely sure if the OP really has the option to override the constructor by object refinement/inheritance. – hakre Aug 04 '11 at 17:09
  • 1
    hakre: what do you mean? The [doc](http://php.net/manual/en/language.oop5.decon.php) explicitly states that "unlike with other methods, PHP will not generate an `E_STRICT` level error message when `__construct()` is overridden with different parameters than the parent `__construct()` method has." – BenMorel Aug 05 '11 at 10:03
  • @catalin.costache: while not exactly what I was looking for, +1 for providing an interesting alternative! – BenMorel Aug 05 '11 at 11:26
  • @Benjamin: I just meant that using a refined object instead of the original object might not always be an option, e.g. if you need to unfreeze a specific class (and not a child of a specific class instead). That's all. – hakre Aug 05 '11 at 11:29
  • @hakre: gotcha, I misunderstood your comment. I can't really see any drawback to that approach for now, though I'd obviously feel more confident with generating the actual class. – BenMorel Aug 05 '11 at 11:32
3

By definition, instantiating an object includes callings its constructor. Are you sure you wouldn't rather write more lightweight constructors? (And no, I don't think there's another way.)

aib
  • 45,516
  • 10
  • 73
  • 79
  • I could of course make other design choices, but they all have their side effects; that's why I'm looking for a generic way. – BenMorel Aug 04 '11 at 16:45
1

Doctrine has a package for this which you can use: https://github.com/doctrine/instantiator

Kars Barendrecht
  • 549
  • 10
  • 23
1

Is there another [...] way to do that?

No. At least not w/o redefining a class'es codebase by using extensions like runkit.

There is no function in PHP Reflection that would allow you to instantiate a new object w/o calling the constructor.

less hacky way

That is most certainly not possible for two reasons:

  1. Objects are either serializeable or not and
  2. serialize/unserialize is PHP's most native implementation of object persistence around

Everything else will mean more work, more implementation and more code - so most probably more hacky in your words.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Do you mean temporarily renaming the constructor? – BenMorel Aug 04 '11 at 16:31
  • What I consider "hacky" is to hand-write a serialized string. If PHP's serialization method changes, the code is broken. What surprises me with Reflection, is that it provides ways to *instantiate* classes *with* constructor, to dynamically *set* properties, but not to instantiate *without* constructor. They look like 2 differents things to me: either you instantiate *with* the constructor, in which case you don't need Reflection, and you do the things the "normal" way, without it; or you use Reflection, to do some "magic" on the instance; why not creating an empty instance to work with then? – BenMorel Aug 04 '11 at 16:38
  • To answer your last question, I'm working with user defined classes, but wouldn't like to implement Serializable, as it makes keeping references between objects a nightmare... – BenMorel Aug 04 '11 at 16:41
  • 1
    The constructor has been "made for" the purpose of initializing a *new* object, without this persistence requirement in mind: retrieve an *existing* object from a persistent storage (with the exception of the Serialized interface, which as I pointed out is, at least from my experience and knowledge, a nightmare to work with when cross-object references are to be kept). About the Serialized PHP Library, I came across this project while browsing for solutions; I'm quite reluctant to create dependencies on such a big project for such a small need. – BenMorel Aug 04 '11 at 16:53
  • "*If PHP's serialization method changes, the code is broken.*" Yeah, I tell you what: if PHP syntax changes, even your whole code get's broken. So come back down to earth ;). It's mostly forward compatible even. And about Reflection: Just because it does not have the feature you personally need, it does not mean it's generally useless. I can understand your disappointment, but Reflection is far more than the small fragment you want to make use of it. – hakre Aug 04 '11 at 17:39
  • I don't remember saying that Reflection was useless; I'm just surprised that giving the ability to modify the private properties of an object does not come with a finer-grain control of its instantiation. About the stability of the serialization, I can agree to a certain extent. Still, I was hoping that something more elegant would exist. Please, don't ask me to define *elegant*! – BenMorel Aug 05 '11 at 09:59
  • @Benjamin: It's just that I don't have a better alternative to offer :) I guess that the feature you're looking for in reflection is not available (instantiation with overriding the constructor function) because it would undermine how objects work. You can think about the same with the accessibility setter it offers so I can understand you're looking for it, however, I think runkit does here what you're looking for. – hakre Aug 05 '11 at 10:17
  • I never had the opportunity to have a look at Runkit, which is definitely an interesting extension. Its major drawback however is that it's not shipped with PHP by default, which means I'll probably have to stay with the unserialization option! – BenMorel Aug 05 '11 at 10:30
  • I did assume that from the very beginning. I had looked around in the past already much and with your question I did review some news as well, but I can't see anything else that comes close (keeping extensions like runkit out). Maybe you can reduce the burden by writing yourself some little helper functions dealing with formatting the serialized strings. It's not that hard to accomplish. The Serialized PHP library still does not have an easy to use interface to actually create serialized objects with an object interface itself, but it contains some useful information at least. – hakre Aug 05 '11 at 10:34