7

In perl I'm used to doing

my $foo = new WhatEver( bar => 'baz' );

and now I'm trying to figure out if PHP objects can ever be constructed this way. I only see this:

my $foo = new WhatEver();
$foo->{bar} = 'baz';

is it possible to do it in one step?

AmbroseChapel
  • 11,957
  • 7
  • 46
  • 68
  • Being a perl guy myself I'm interested in this as well. Perl ftw. – Zach Leighton May 31 '13 at 00:00
  • Do you mean "PHP objects" as in an `stdClass`? because if it's a constructor you can do that in `__construct` and pass the variables on instantiation right? – elclanrs May 31 '13 at 00:02
  • So for the Perl-unfamiliar, you are asking if it is possible to arbitrarily set an object's properties by passing into the constructor? It isn't - you must define the `__construct()` to accept parameters, and manually set them or loop over an array passed to the constructor to set them dynamically. – Michael Berkowski May 31 '13 at 00:03
  • http://php.net/manual/en/language.oop5.php – georgealton May 31 '13 at 00:04

4 Answers4

3

You can lay out your constructor as follows:

 class MyClass {
     public function __construct($obj=null) {
         if ($obj && $obj instanceof Traversable || is_array($obj)) {
             foreach ($obj as $k => $v) {
                if (property_exists($this,$k)) {
                    $this->{$k} = $v;
                }
             }
         }
     }
 }

This has a serie of drawbacks:

  • This is inefficient
  • The variables you create will not show up on any doc software you use
  • This is the open door to all forms of slackery

However, it also presents the following benefits:

  • This can be extended pretty safely
  • It allows you to lazy-implement variables
  • It also allows you to set private variables, provided that you know their names. It is pretty good in that respect if not abused.
Sébastien Renauld
  • 19,203
  • 2
  • 46
  • 66
  • I forgot to mention - anything that can run into a `foreach` can be passed in as the first argument of this constructor. – Sébastien Renauld May 31 '13 at 00:04
  • Anything apart from a normal array, which would be the obvious choice here since we're looking for concise initialisation. – IMSoP May 31 '13 at 00:05
  • Thank you. I had totally forgotten about the __construct() method. and of course Perl objects generally have a new() method doing that too, it isn't magical. – AmbroseChapel May 31 '13 at 00:06
  • One extension to this would be to check `property_exists($k, $this)` so that only properties actually defined in the class were assigned. – IMSoP May 31 '13 at 00:19
  • @IMSoP: I kept the example to a minimum. Nevertheless, added... (Also, yours has reversed parameters. Trying to test me? ;-) ) – Sébastien Renauld May 31 '13 at 00:21
  • @SébastienRenauld Oh, bother, I always get those wrong - I even brought the manual page up and somehow ignored it! And yes, certainly optional, but then so is supporting iterable objects as well as arrays (and explicitly rejecting non-iterable ones...) – IMSoP May 31 '13 at 00:23
  • Actually, sorry to quibble, but shouldn't it be `instanceof Traversable` not `instanceof ArrayAccess`? `ArrayAccess` is for using `[]` element access. – IMSoP May 31 '13 at 00:26
  • @IMSoP: You're right on that, though usually, when people implement `Traversable`, they also implement `ArrayAccess`. Correcting anyway! – Sébastien Renauld May 31 '13 at 00:27
1

The parameters passed in the parentheses (which can be omitted, by the way, if there aren't any) go to the constructor method where you can do whatever you please with them. If a class is defined, for example, like this:

class WhatEver
{
    public $bar;

    public function __construct($bar)
    {
        $this -> bar = $bar;
    }
}

You can then give it whatever values you need.

$foo = new WhatEver('baz');
pilsetnieks
  • 10,330
  • 12
  • 48
  • 60
1

There are a few ways to accomplish this, but each has its own drawbacks.

If your setters return an instance of the object itself, you can chain your methods.

my $foo = new WhatEver();
$foo->setBar("value")->setBar2("value2");

class WhatEver
{
    public $bar;
    public $bar2;

    public function setBar($bar)
    {
        $this->bar = $bar;
        return $this;
    }

    public function setBar2($bar2)
    {
        $this->bar2 = $bar2;
        return $this;
    }
}

However, this doesn't reduce it to one step, merely condenses every step after instantiation.

See: PHP method chaining?

You could also declare your properties in your constructor, and just pass them to be set at creation.

my $foo = new WhatEver($bar1, $bar2, $bar3);

This however has the drawback of not being overtly extensible. After a handful of parameters, it becomes unmanageable.

A more concise but less efficient way would be to pass one argument that is an associative array, and iterate over it setting each property.

Community
  • 1
  • 1
radicalpi
  • 907
  • 1
  • 9
  • 29
  • So you can only do it with an indexed array and go by order? And you can pass in an associative array, but it won't automatically assign key values to variable names in your object, you'd have to iterate it? – AmbroseChapel May 31 '13 at 00:12
  • Yes, you'd need to iterate. But, you also have the ability to use magic methods in PHP5. http://www.php.net/manual/en/language.oop5.overloading.php#object.set – radicalpi May 31 '13 at 00:13
  • @ChrisHendry: If you can avoid magic methods, I strongly recommend you do so. http://www.garfieldtech.com/blog/benchmarking-magic has a bunch of good stuff on the matter. – Sébastien Renauld May 31 '13 at 00:22
  • @Sébastien: Interesting. I wouldn't have expected such a performance hit, but it does make sense. Thanks. – radicalpi May 31 '13 at 00:26
  • @ChrisHendry: magic methods are not just bad on performance. They also encourage malpractice (have you ever inherited an undocumented PHP class that assigned variables dynamically without declaration? Absolutely. Impossible. To maintain. Without reading the full class to know what's what) and quite a few forms of slackery (they're mostly used by people to suppress notices for undeclared variables). deceze and I had a little chat on another question a while back - there are only two magic methods that cannot be replaced by good coding standards (__wakeup, __invoke). – Sébastien Renauld May 31 '13 at 13:20
1

The implicit assumption here is that objects have meaningful, presumably public, properties which it is up to the calling code to provide values for. This is by no means a given - a key aspect of OOP is encapsulation, so that an object's primary access is via its methods.

The "correct" mechanism for initialising an object's state is its constructor, not a series of property assignments. What arguments that constructor takes is up to the class definition.

Now, a constructor might have a long series of named parameters, so that you could write $foo = new WhatEver(1, "hello", false, null) but if you want these to act like options, then it could take a single hash - in PHP terms, an Array - as its argument.

So, to answer the question, yes, if your constructor is of the form function __construct(Array $options) and then iterates over or checks into $options. But it's up to the constructor what to do with those options; for instance passing [ 'use_safe_options' => true ] might trigger a whole set of private variables to be set to documented "safe" values.

As of PHP 5.4 (which introduced [ ... ] as an alternative to array( ... )), it only takes a few more character strokes than the Perl version:

$foo = new WhatEver( ['bar' => 'baz'] );
IMSoP
  • 89,526
  • 13
  • 117
  • 169