2

Is it possible to write series of statements that repeatedly refer to a single object without having to write the object every time?

I came to this because I used to do this in Visual Basic:

With person
    .setFullName(.firstName+" "+.lastName)
    .addParent(parent)
    .save()
End With

This is a shorthand for

person.setFullName(person.firstName+" "+person.lastName)
person.addParent(parent)
person.save()

Is it possible to achieve this in PHP? To rewrite the following code without having to write $person 5 times?

$person->setFullName($person->firstName.' '.$person->lastName);
$person->addParent($parent);
$person->save();

Note: I'm not referring to methods chaining for 2 reasons:

1) I want to use public members as well

2) I don't work with classes I wrote, so I cannot add return $this; to all the methods

Thanks

ilyes kooli
  • 11,959
  • 14
  • 50
  • 79
  • 1
    Adding `return $this` to every methods to concat calls would have been a very ugly workaround anyway imo. I'd rather stick to the usual x3 $person-> – Clément Malet Jul 18 '14 at 08:03
  • In your case no, you can't. However if you have long expessions like `$one->two->three->prop` you can shorten them by defining alias/reference of nested object `$tree = $one->two->three, $tree->prop`. – hindmost Jul 18 '14 at 08:09
  • Could the person who down-voted the question tell us why? I am extremely curios! – ilyes kooli Jul 18 '14 at 08:18
  • What you're describing falls into method chaining, and you don't have to write the same sentence 5 times and you can use public members, like I did in my example... check my answer – Julio María Meca Hansen Jul 18 '14 at 09:05

4 Answers4

2

There is exists PHP lib that allow to do it: https://github.com/lisachenko/go-aop-php

Example of implementation: http://go.aopphp.com/blog/2013/03/19/implementing-fluent-interface-pattern-in-php/

Create your aspect

<?php
use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Around;

class FluentInterfaceAspect implements Aspect
{
    /**
     * Fluent interface advice
     *
     * @Around("within(FluentInterface+) && execution(public **->set*(*))")
     *
     * @param MethodInvocation $invocation
     * @return mixed|null|object
     */
    protected function aroundMethodExecution(MethodInvocation $invocation)
    {
        $result = $invocation->proceed();
        return $result!==null ? $result : $invocation->getThis();
    }
}

Add interface for matching

interface FluentInterface
{

}


class User implements FluentInterface
{
    protected $name;
    protected $surname;
    protected $password;

    public function setName($name)
    {
        $this->name = $name;
    }

    public function setSurname($surname)
    {
        $this->surname = $surname;
    }

    public function setPassword($password)
    {
        if (!$password) {
            throw new InvalidArgumentException("Password shouldn't be empty");
        }
        $this->password = $password;
    }
}

Usage

$user = new User;
$user->setName('John')->setSurname('Doe')->setPassword('root');

But you could write your matching rule without adding new interface.

P.S. It's not an proper answer to the question because required other syntax sugar. PHP do not support such syntax.

sectus
  • 15,605
  • 5
  • 55
  • 97
  • 1
    I am intrigued, and I admit I'm not getting how it works yet. The example implementation seems to scream *black magic* and a quick perusal of the readme didn't clear it up. Mind explaining in a few sentences how that library does what it does with virtually no code changes? – deceze Jul 18 '14 at 08:28
  • @deceze, after some looking in: tokenizer, streams with caching results. – sectus Jul 18 '14 at 08:42
0

You can't do it in PHP. The syntax of the language does not permit it

fluminis
  • 3,575
  • 4
  • 34
  • 47
0

In PHP you can do this, but of course you can create shorter variable

$p = &$person;

$p->setFullName($p->firstName.' '.$p->lastName);
$p->addParent($parent);
$p->save();

unset($p);

But in this case you still have -> operator and variable and without already mentioned libs you won't get more.

Marcin Nabiałek
  • 109,655
  • 42
  • 258
  • 291
0

Yes, you can do it. It's called method chaining (if I remember correctly). Let me put you a simple example:

class A {
    public $attributes;

    public function __construct() {
        return $this;
    }

    public function methodA($a) {
        if (!empty($a)) {
            $this->attributes["a"] = $a;
        }

        return $this;
    }

    public function methodB($b) {
        if (!empty($b)) {
            $this->attributes["b"] = $b;
        }

        return $this;
    }

    public function methodC($c) {
        if (!empty($c)) {
            $this->attributes["c"] = $c;
        }

        return $this;
    }
}

The key to what you're looking for is to return the object itself (hence, the reference to return $this;) and you get the same behaviour you had with VB. In PHP you would do it like this:

$a = new A();
$a->methodA(5)->methodB(50)->methodC(500);

Or even:

$a = (new A())->methodA(5)->methodB(50)->methodC(500);

Hope that helps :)

Julio María Meca Hansen
  • 1,303
  • 1
  • 17
  • 37