4
\$\begingroup\$

i'm using an external API that posts products and orders into a marketplace, and it provides a class that in each request i have to specify an endpoint, user and password (token), it's something like this:

if($env == 'testing') {
    $endpoint = 'testing';
    $user = 'testing';
    $pass = 'testing';
} else {
    $endpoint = 'production';
    $user = 'production';
    $pass = 'production';
}


$itemSender = new ItemSender($endpoint, $user, $pass);
$orderSender = new OrderSender($endpoint, $user, $pass);

my first thought was to use the strategy pattern so i can have both testing and production environment, or even others environments, implementing an interface, i built something lie this:

interface EnviromentInterface () {

    function getEndpoint();
    function getUser();
    function getPassword();
}

class TestingEnvironment implements EnviromentInterface {

    private $endpoint;
    private $user;
    private $password;

    function getEndpoint() {
        return $this->endpoint;
        // return getFromConfigFile('API_ENDPOINT_TESTING');
    }

    function getUser() {
        return $this->user;
    }

    function getPassword() {
        return $this->password;
    }

}

class ProductionEnviroment implements EnviromentInterface {

    private $endpoint;
    private $user;
    private $password;

    function getEndpoint() {
        return $this->endpoint;
        // return getFromConfigFile('API_ENDPOINT_PRODUCTION');
    }

    function getUser() {
        return $this->user;
    }

    function getPassword() {
        return $this->password;
    }

}

and i would overwrite the parent's class construct to use the environment object, something like this:

class CustomItemSender extends ItemSender
{

    function __construct(EnviromentInterface $env)
    {
        parent::__construct($env->getEndpoint(), $env->getUser(), $env->getPassword());
    }
}

the problem is: how can i switch between these environment objects? i thought about using a switch/case, but if i need to implement more environments i have to add another if to the switch/case statment.

what would be a good aproach in this case?

important: all info/credentials about the testing and production environments are inside a config file, that in this case is an .env file

\$\endgroup\$
2
\$\begingroup\$

You are probably looking for factory method pattern, instead of strategy pattern.

The CustomItemSender class is a good indicator of this. Whenever you extend a class to only override the constructor, you probably need a factory instead. It means you need somewhat more complex way of constructing the object and that implies creational pattern. Strategy is a behavioral pattern (and in fact, your code does not implement this pattern).

The EnvironmentInterface is basicaly a data structure, it is immutable which is good, but it has no behaviour, which is not so good. It is just a tuple. Maybe you don't really need this structure and stay with tripplet of arguments.

Polymorphism only makes sense to abstract varying behaviour, not data. Anyway polymorphism won't help you avoid ifs/switches entirely. It will help you reduce the amount of them to minimum. You might still need some ifs in the creational part, but once a polymorhic object is created, it no longer repeats those ifs because it encapsulates the beforehand chosen branch.

Sometimes ifs/switches can be replaced by key-value maps though (such a map could be owned by a factory...).

Getting config values from a file using global function like yours getFromConfigFile(string $key) is not very wise btw. Because it either loads the file again on every request for a key. Or it loads it only once but stores the loaded data to a global variable thus having side effects on globle state which is a bad practice. You might want to load all config from the file to lets say array (possibly encapsulated in an objet) and serve the individual config values from it. And btw the function knows the location of the config file globally/magically and that is also not very good. You might need a factory for the config object, which will load the config from a given a file.

function loadConfigFromJsonFile(string $filename): array
{
  // add error handling
  return json_decode(file_get_contents($filename), true);
}

class SenderFactory
{

  private string $endpoint;
  private string $user;
  private string $password;

  public function __construct(string $endpoint, string $user, string $password)
  {
    $this->endpoint = $endpoint;
    $this->user = $user;
    $this->password = $password;
  }

  public function createOrderSender(): OrderSender
  {
     return new OrderSender($this->endpoint, $this->user, $this->password);
  }

  public function createItemSender(): ItemSender
  {
     return new ItemSender($this->endpoint, $this->user, $this->password);
  }
}

function createSenderFactory(string $env, array $config): SenderFactory
{
  // the if has to go somewhere anyway
  // add some error handling too (missing config keys, unknown $env, etc.)
  if ($env === 'testing') {
    return new SenderFactory($config['API_ENDPOINT_TESTING'], 'testing', 'testing');
  } else {
    return new SenderFactory($config['API_ENDPOINT_PRODUCTION'], 'production', 'production');  
  }
}


// ...


$config = loadConfigFromJsonFile($configPath);
$factory = createSenderFactory($env, $config);
$orderSender = $factory->createOrderSender();
$itemSender = $factory->createItemSender();
```
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ i see, i'm going to read more about the factory pattern, but could you make an example using code? so i can have a better idea, and what would make this a strategy pattern? \$\endgroup\$ – Thiago Dias Dec 28 '19 at 23:48
  • 1
    \$\begingroup\$ @NBAYoungCode Yep, I have added the factories examples.Anyway, I don't think you should try to turn it to strategy pattern. Characteristic of strategy object is that it is held by the consumer for its entire lifetime (because the consumer's methods require the strategy's methods), not just used in constructor and then discarded. \$\endgroup\$ – slepic Dec 29 '19 at 0:00
  • \$\begingroup\$ i think i got it, also gonna take a deep look about the strategy pattern. but, hypothetically, so if i had more methods in my class using the methods of the strategy object, it would be a strategy pattern? this idea of "controlling the lifetime" means using features that the object provides, right? \$\endgroup\$ – Thiago Dias Dec 29 '19 at 0:06
  • 1
    \$\begingroup\$ @NBAYoungCode Yes. If you needed to override behaviour of (some) method(s) of lets say ItemSender, and this override can itself be different under different circumstances, then yes - a strategy may drive those differences. You gotta remember that patterns may cross. For example factory is creational pattern. But its ability to create something is a behviour and so for example a factory may own a strategy that tells it how the creation of objects should be done. Which is actualy not very far from another creational pattern - the builder... \$\endgroup\$ – slepic Dec 29 '19 at 0:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.