On 2016-05-31 22:15, Jesse Schalken wrote:
Hi internals,

I often have code dealing with plain old PHP objects with properties and no
methods, either as a substitute for keyword arguments, or to represent a
JSON or YAML document for a web service, configuration file or schemaless
database.

At the moment, instantiating an object and setting public properties
requires a temporary variable for each object in the structure:

$obj1 = new Obj1();
$obj1->prop1 = ...;
$obj1->prop2 = ...;

$params = new FooParams();
$params->prop1 = ..;
$params->prop2 = ...;
$params->prop3 = $obj1;

$this->fooMethod($arg1, $arg2, $params);


For large structures, this gets verbose very quick. There is a good example
of this here
<https://github.com/jesseschalken/fail-whale/blob/72870b37c4c21d19f17324a966344ec476b432a7/src/FailWhale/Introspection.php#L22>
involving
18 unnecessarily variables.

I can remove the local variables by defining setters for all the properties:

$this->fooMethod(

     $arg1,
     $arg2,
     (new FooParams())

         ->setProp1(...)

         ->setProp2(...)

         ->setProp3((new Obj1())

         ->setProp1(...)

         ->setProp2(...))

);


But now for each property I have to spend an extra 3-5 lines of code
defining a setter, which is more code than it saved (unless the class is
used heavily enough).

I could define __construct() taking every property as a parameter, but then
each property has to be mentioned another three times (four times with a
doc comment), and the type twice:

class FooParams {

     public int $prop1;
     // ...


     public function __construct(
         int $prop1
         // ...
     ) {

         $this->prop1 = $prop1;
         // ...

     }

}


and where the object is constructed, it isn't immediately visible what the
meaning of each positional parameter is without some IDE assistance (eg
Ctrl+P in PhpStorm), and only specifying some parameters requires filling
preceding ones with defaults.

I could also define the __call() method to automatically expose setters,
but then IDEs and static analysis can't understand what's going on (it
can't see that those methods exist, what their parameters are and what they
return). @method doc comments on the class help, but that's another line
for every property which I have to manually keep in sync with the real
properties.

It would be great if there was a simple shorthand syntax for setting
properties on an object in-line, without needing to extract a variable:

$this->fooMethod(
     $arg1,
     $arg2,
     new FooParams() {
         prop1 = ...,
         prop2 = ...,
         prop3 = new Obj1() {
             prop1 = ...,
             prop2 = ...,
         },
     }
);



Is there anything wrong with casting an array to an object? The syntax is almost identical to what you want to achieve.

I can think of three ways to achieve, with the current PHP stable, what you seem to desire (forgive me if I misunderstood). Your code becomes:

   $this->fooMethod(
        $arg1,
        $arg2,
        (object)[
            'prop1' => ...,
            'prop2' => ...,
            'prop3' => (object)[
                'prop1' => ...,
                'prop2' => ...,
            },
        }
   );


Pretty concise and close to your proposal, right?
Another option is anonymous classes

   $this->fooMethod(
        $arg1,
        $arg2,
        new class {
            var $prop1 => ...,
            var $prop2' => ...,
            var $prop3' => new class {
                  var $prop1' => ...,
                  var $prop2' => ...,
            },
        }
   );


Or if you need a specific class name for validation or anything:

$this->fooMethod(
    $arg1,
    $arg2,
    new class extends FooClass {
        public $prop1 => ...,
               $prop2' => ...,
               $prop3' => new class extends FooBar {
                                  public $prop1' => ...,
                                         $prop2' => ...;
                          },
    }
);



Last option is a generic class with a __construct taking an array and creating a populated instance, which can be used as a base/trait to extend dumb classes that only require a name. But you've covered that already.


Cheers,


This way the structure can be written directly in the code as an expression
and FooParams and Obj1 remain simple containers for properties.

The grammar might look like (I haven't used bison/yacc before):

expr_without_variable:
         /* ... */
     |   expr '{' inline_set_properties '}'
;

inline_set_properties:
         /* empty */
     |   identifier '=' expr
     |   identifier '=' expr ',' inline_set_properties
;


(Although I think that would conflict with the alternative $var{8} syntax
for array/string offset.)

Has this been explored before? What problems can you foresee (or have been
foreseen) with such a feature?

Thanks

Reply via email to