skip to Main Content

I have a usecase where I have a fluent API and I would like to be able to make some methods ineffective:

class Car {

    public function accelerate() {
        ...
        return $this;
    }

    public function turnLeft() {
        ...
        return $this;
    }


    public function turnRight() {
        ...
        return $this;
    }

    public function stop() {
        ...
        return $this;
    }
}

$car = new Car();

$car
->accelerate()
->turnLeft()
->accelerate()
->turnRight()
->stop();

Now I would like to disable "accelerate". I.e. any subsequent calls will just return $this without doing anything else.

The easy way would be to add properties for each method to flag it as disabled and then check at the beginning of the call. However I would like to have it more flexible with less boilerplate code.

I could just add a disabled methods property instead and a magic call method:

public function disableMethod(string $method) {
    $this->disabledMethods[] = $method;
}

    public function __call($method, array $parameters)
    {
        if(in_array($method, $this->disabledMethods)) {
            return $this;
        }
        return parent::__call($method, $parameters);
    }

This takes care of the disabling part, but I would also like to have autocompletion in the IDE for the method names that I pass into the disableMethod function.

This obviously will not work, but is there some way to do something similar?:

$car->disableMethod($car->accelerate);

2

Answers


  1. Chosen as BEST ANSWER

    ok, I managed to come up with a hacky solution using mockery:

    public function ignore(Closure $callback): static
    {
        $spy = Mockery::spy(static::class);
    
        $callback($spy);
    
        $reflection = new ReflectionClass(get_class($spy));
        $receivedMethodCalls = $reflection->getProperty('_mockery_receivedMethodCalls')->getValue($spy);
        $reflection = new ReflectionClass(get_class($receivedMethodCalls));
        $methodCalls = $reflection->getProperty('methodCalls')->getValue($receivedMethodCalls);
    
        /** @var MockeryMethodCall $methodCall */
        foreach ($methodCalls as $methodCall) {
            $this->ignoredMethods[] = $methodCall->getMethod();
        }
    
        return $this;
    }
    

    it can be used like this:

    (new Car())
    ->ignore(fn(Car $car) => $car
        ->accelerate()
        ->turnLeft();
    )
    ->accelerate()
    ->turnLeft()
    ->accelerate()
    ->turnRight()
    ->stop();
               
    

  2. How about passing an extra argument to return its method name? It is a bit more work, but will fulfill your usecase and the IDE is still able to autocomplete the method name.

    class Car
    {
        private array $disabledMethods = [];
    
        public function accelerate(bool $returnName = false): string|self
        {
            if ($returnName) {
                return __METHOD__;
            }
    
            if (isset($this->disabledMethods[__METHOD__])) {
                return $this;
            }
    
            echo "Accelerating";
    
            return $this;
        }
    
        public function stop(bool $returnName = false): string|self
        {
            if ($returnName) {
                return __METHOD__;
            }
    
            if (isset($this->disabledMethods[__METHOD__])) {
                return $this;
            }
    
            echo "Stopping";
    
            return $this;
        }
    
        public function disableMethod(string $method): self
        {
            if (isset($this->disabledMethods[$method])) {
                return $this;
            }
    
            $this->disabledMethods[$method] = true;
    
            return $this;
        }
    }
    
    $car = new Car();
    $car->
        disableMethod($car->accelerate(returnName: true))
        ->accelerate()
        ->stop();
    

    Just echoes

    Stopping
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search