3

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?

1
  • Why not do this in your parent class constructor, then in the child do parent::__construct();? Commented Nov 26, 2015 at 17:12

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Beat me to it. I would go with this answer.
so why would I need this code if I can also just do $this->execute() in the extended class constructor? i was hoping to avoid writing $this->execute() in every task class
This way you could add some more logic in the base class constructor and call it all at once.
so there is no way to avoid using parent::_construct?
@Mike A validate using method-exists
|
0

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.

8 Comments

My awesome init method that does not need to use parent:: for anything! only if the parent class does not implement her own __construct() .. or else it must still call parent::__construct()
In the end, this solution is just about identical to my own.
@MatteoTassinari Umm, no because it has a different name.
LOL, true, but overall effect is the same.
@MatteoTassinari Pretty much. Personally I would use the usual parent:: solution from your answer, just adding an alternative approach.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.