2

I have a PHP/Laravel best practice question.

Here is the scenario: I have an object with a "type" property in it. The type property will be set to an integer of some kind.

Depending on the "type" integer in the object, different actions will have to happen.

What is the best way to process this object? I am trying desperately to think of a way that avoids using a whole bunch of if/else statements as that feels very like a very wrong and ugly way to approach this.

i.e. I don't want this:

if($obj->type == 1) {
    //process actions for type 1
}
else if($obj->type == 2){
    //process actions for type 2
}

Would appreciate any advice.

Thanks!

ackerchez
  • 1,684
  • 7
  • 28
  • 49
  • i think we need more details on what you want to do to try to think about a better solution – ThomasP1988 Jul 05 '15 at 19:29
  • 1
    [Strategy Design Pattern - Define a family of algorithms, encapsulate each one, and make them interchangeable.](https://sourcemaking.com/design_patterns/strategy)? – Ryan Vincent Jul 05 '15 at 20:17

2 Answers2

4

Thanks to @Ryan Vincent I found this resource (https://sourcemaking.com/design_patterns/strategy/php) and changed the Strategy design pattern a bit. For avoiding hard-coded type values check how the dynamic class loading is done in StrategyContext::__construct method. New class instance is initiated by the $type variable name. Class names should be strings in PHP so this way forces types to be strings not only numbers.

Different than the original example in the article, I attached StrategyContext to the book object and wrap the get methods with the strategy to have better use of book object. Unfortunately if the business logic will be in your code somehow you need to hardcode it. With this method you don't hardcode for each type but you need to create a strategy class for each type. In the example we have StrategyCaps , StrategyStars and StrategyExclaim strategies. So our types are limited to Caps, Stars and Exclaim.

I didn't try this piece of code in production environment but you can have a starting point via the example.

Also for dynamic loading, you can benefit from this question too.instantiate a class from a variable in PHP?

Hope it helps,

<?php
interface StrategyInterface {
    public function showTitle($title);
    public function showAuthor($author);
}

class StrategyContext  implements StrategyInterface {
    private $strategy = NULL; 

    public function __construct($type) {

        //Dynamic class loading per type
        $classname="Strategy{$type}";
        if(class_exists($classname)) {
          $this->strategy = new $classname();
        } else {
          throw new Exception("Strategy not found", 1);
        }

    }
    public function showTitle($title) {
      return $this->strategy->showTitle($title);
    }

    public function showAuthor($author) {
      return $this->strategy->showAuthor($author);
    }
}


class StrategyCaps implements StrategyInterface {
    public function showTitle($title) {
        return strtoupper ($title);
    }

    public function showAuthor($author) {
        return strtoupper ($author);
    }
}

class StrategyExclaim implements StrategyInterface {
    public function showTitle($title) {
        return Str_replace(' ','!',$title);
    }
    public function showAuthor($author) {
        return Str_replace(' ','!',$author);
    }
}

class StrategyStars implements StrategyInterface {
    public function showTitle($title) {
        return Str_replace(' ','*',$title);
    }    

    public function showAuthor($author) {
        return Str_replace(' ','*',$author);
    }

}

class Book {
    private $author;
    private $title;
    private $strategy;

    function __construct($strategy, $title_in, $author_in) {
        $this->strategy = new StrategyContext($strategy);
        $this->author = $author_in;
        $this->title  = $title_in;
    }
    function getAuthor() {
        return $this->strategy->showAuthor($this->author);
    }
    function getTitle() {
        return $this->strategy->showTitle($this->title);
    }
    function getAuthorAndTitle() {
        return $this->getTitle() . ' by ' . $this->getAuthor();
    }
}

writeln('BEGIN TESTING STRATEGY PATTERN');
writeln('');

$type = 'Caps';
$book = new Book($type, 'PHP for Cats','Zeev Suraski');
writeln($book->getAuthorAndTitle());


$type = 'Exclaim';
$book = new Book($type, 'PHP for Unicorns','Rasmus Lerdorf');
writeln($book->getAuthorAndTitle());


$type = 'Stars';
$book = new Book($type, 'PHP for Ponys','Andi Gutmans');
writeln($book->getAuthorAndTitle());


function writeln($line_in) {
  echo $line_in.PHP_EOL;
}

Update:

So if you are using an ORM we can assume that Book is your model class. I don't have any knowledge about Eloquent and how it handles data binding etc. so I'll make it as simple as I can. So I assume you can use a constructor with the binded data from database.

Keep your StrategyContext and the actual strategy classes -where your biz logic will be coded- as a service and use dependency injection while finding out which strategy you will use. This way you can bind all your strategies depending on your type variable, into your Model object.

Updated version of Book class,

class Book {
    private $author = "Zeev Suraski";
    private $title = "PHP for Cats";
    private $strategy;
    private $type = 'Caps';

    function __construct() {
        $this->strategy = new StrategyContext($this->type); //Dependency injection here
    }

    function getAuthor() {
        return $this->strategy->showAuthor($this->author);
    }

    function getTitle() {
        return $this->strategy->showTitle($this->title);
    }

    function getAuthorAndTitle() {
        return $this->getTitle() . ' by ' . $this->getAuthor();
    }

    function setType($type) {
      $this->type = $type;
    }

    function setStrategy($type=null) {
      if($type==null) {
        $this->strategy = new StrategyContext($this->type); //Dependency injection here
      } else {
        $this->strategy = new StrategyContext($type); //Dependency injection here
        $this->setType($type);
      }
    }

}


writeln('BEGIN TESTING STRATEGY PATTERN');
writeln('');

$book = new Book();
writeln($book->getAuthorAndTitle());


$type = 'Exclaim';
$book->setType($type);
$book->setStrategy();
writeln($book->getAuthorAndTitle());


$type = 'Stars';
$book->setStrategy($type);
writeln($book->getAuthorAndTitle());

function writeln($line_in) {
  echo $line_in.PHP_EOL;
}
Community
  • 1
  • 1
Ugur
  • 1,679
  • 14
  • 20
  • That is a good idea and I was thinking in this direction with interfaces. Here is the twist, I am doing this in Laravel and working with the ORM so having different classes for each "obj" doesn't work exactly because they are all going to be instances of the same model object....thoughts? – ackerchez Jul 06 '15 at 05:07
  • I updated the answer with some assumptions, I guess it looks like a better implementation. @ackerchez what's your opinion? – Ugur Jul 06 '15 at 09:25
-1

I would use PHP's switch statement for this. For example,

switch($obj->type) {
    case 1:
        // do something
        break;
    case 2:
        // do something else
        break;
    default:
        // default actions
        break;
}

Here break is used to stop execution of the switch statement as a whole. Without the break, code execution could fall through to the next case. This is sometimes desirable behavior though. If you wanted cases 1 and 2 to have the same code run, you could leave case 1 empty and write all your code in case 2. That way when the case 1 condition is met, no code related to case 1 would be run, but without a break statement, code execution would continue into case 2 before reaching the break statement in case 2.

user2027202827
  • 1,234
  • 11
  • 29
  • Thanks for your answer. I was wondering if anyone had a more dynamic that didn't require having to deal with a hard coded type...like maybe working of a constant dictionary or something.. – ackerchez Jul 05 '15 at 19:17
  • Couldn't you use your actual conditions in place of using the type attribute? – user2027202827 Jul 05 '15 at 19:22