Junior
Junior

Reputation: 11990

How to automatically call a method after the __constructor is done in PHP?

I wrote a small abstract class that is called Task. I like to have every task logic's class to extend it.

Within my abstract class "Task" I like to call a used defined method "execute" that is defined in every class.

I tried to use the magic method __call but it is not working.

If you notice in my method I am echoing a message which never prints on the screen.

Here is my abstract Task class

<?php

namespace App\Modules\Surveys\Tasks;

use App\Modules\Surveys\Tasks\Support\Traits\HtmlHelper;

abstract class Task
{
    /*
    |
    | This task base class provides a central location to place any logic that
    | is shared across all of your tasks. 
    |
    */

    use HtmlHelper;


    /**
     * checks wether a get method execute exists and calls it
     *
     * @param string $name
     * @param array $args optional
     * @return mixed
     */
    public function __call($name, $args = [])
    {

        echo 'Attempt to execute task';

        if (method_exists($this, 'execute')) {

            return call_user_func_array('execute', $args);

        } else {

            throw new \Exception('execute method does does not exists in your task! ' . get_class($this) );

        }
    }
}


?>

Here is a logical class

<?php 

namespace App\Modules\Surveys\Tasks\Interviews;

use App\Modules\Surveys\Tasks\Task;

use App\Modules\Surveys\Models\SurveyInterview;

use Exception;


class ResumeInterview extends Task
{

    protected $surveyId;

    protected $callId;

    protected $myInterview;

    /**
     * Create a new task instance.
     *
     * @return void
     */
    public function __construct($surveyId, $callId)
    {

        $this->surveyId = intval($surveyId);

        $this->callId = intval($callId);
    }


    /**
     * Resume existing interview if one exists using the giving $surveyId and $callId
     *
     * @return void
     */
    protected function execute()
    {
        //find the current interview if one exits
        $myInterview = SurveyInterview::surveyAndCall($this->surveyId, $this->callId)->first();

        $this->setInterview($myInterview);

        if( $this->wasResumed() ){
            //At this point existing interview was found

            if($myInterview->status != 'Pending'){
                //At this point the interview is completed and should not be conducted
                throw new Exception('This interview can not not be retaken. It\'s current status is "' . $myInterview->status . '"');
            }

        }


    }

    /**
     * Return the current interview
     * 
     * @return App\Models\Survey\SurveyInterview
     */
    public function getInterview()
    {
        return $this->myInterview;
    }

    /**
     * It checks whether ot the the interview was resumed
     * 
     * @return boolean
     */
    public function wasResumed()
    {
        return $this->getInterview() ? true : false;
    }

    /**
     * It sets the interview
     * 
     * @param Illuminate\Support\Collection $myInterview
     * @param  void
     */
    protected function setInterview($myInterview)
    {
        $this->myInterview = $myInterview;
    }
}

How would I automatically call the execute method if it exists, otherwise throw an exception?

Upvotes: 3

Views: 2582

Answers (2)

Alexander O&#39;Mara
Alexander O&#39;Mara

Reputation: 60507

To avoid having to call parent::__construct in the sub-classes, some frameworks will define a separate init method for the child to override, which will be called from the parent.

abstract class Task
{
    public function __construct($name, $args = [])
    {
        $this->init();
    }
    public function init()
    {
        // Throwing this exception is optional and can be removed to make init optional.
        throw new \Exception('init method must be overridden by child class.');
    }
}

class ResumeInterview extends Task
{
    public function init()
    {
        echo "My awesome init method that does not need to use parent::!";
    }
}

To do this you would have to not use __construct in your child class at all.

Upvotes: 0

Matteo Tassinari
Matteo Tassinari

Reputation: 18584

I would go like this:

abstract class Task {
    [...]

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

    protected function execute() {
        throw new Exception('NOT IMPLEMENTED');
    }

    [...]
}

class ResumeInterview extends Task {
    protected $surveyId;
    protected $callId;
    protected $myInterview;

    public function __construct($surveyId, $callId) {
        $this->surveyId = intval($surveyId);
        $this->callId = intval($callId);
        parent::__construct();
    }

    protected function execute() { [...] }
}

simply call execute() in the base class constructor.

EDIT: notice that the call to parent::__construct(); is only needed if the child class implements her own constructor, else it is not required.

Upvotes: 5

Related Questions