How To Alias Classes For Service Location?

I try to avoid using the "new" operator wherever possible. One easy method is to invoke Yii::createObject() as a sort of factory, eg:




// This...

$provider = Yii::createObject("PostProvider");


// ... not this

$provider = new app\providers\PostProvider();



I’m wondering how to handle this in Yii2. It seems like modifying Yii::$classMap is the way to go, but this leads to namespace problems.

Any advice?

Use the class with complete namespace like below:




$provider = Yii::createObject("\\app\\providers\\PostProvider");



Or you can use:




use app\providers\PostProvider;

$provider = Yii::createObject(PostProvider::className());



Why do you want to avoid using the "new" operator? Is this considered best practice?

It is not complete picture. Just avoiding new and replacing it with a string + method call that does exactly the same is pointless. The thing is that you can do the following. Let’s assume we have hardcoded class name:




class Rocket

{

  public function fly()

  {

    $engine = new Engine();

    return $engine->work();

  }

}

$rocket = new Rocket();

$rocket->fly();



You can do it better:




class Rocket

{

  public $engine = '\app\rocket\lightspeed\Engine';


  public function fly()

  {

    $engine = Yii::createObject($this->engine);

    return $engine->work();

  }

}

$rocket = new Rocket();

$rocket->engine = [

  'class' => 'app\rocket\gasoline\Engine',

  'fuel' => 'Gas',

];

if (!$rocket->fly()) {

  echo 'Well, that was pretty old engine...';

}



In other words if I revisit your post query title:

If your concern is to just use a shorter alias for your classes




use app\providers\PostProvider as PP;

$provider = new PP;



Have you tried using $classMap for this? It seems to work exactly the way you want it to.




        /**

         * @var array class map used by the Yii autoloading mechanism.

	 * The array keys are the class names (without leading backslashes), and the array values

	 * are the corresponding class file paths (or path aliases). This property mainly affects

	 * how [[autoload()]] works.

	 * @see autoload()

	 */

        public static $classMap = [];



createObject allows app-wide object configuration

BaseYii::createObject() uses BaseYii::$objectConfig to configure your objects. This means that if you want to change an object’s configuration at runtime throughout your entire app, you can modify BaseYii::$objectConfig without having to modify the class itself or any instances where it’s constructed.

You don’t get that flexibility with new. It reminds me a bit of the widget factory in Yii1. (Probably still in Yii2? Haven’t checked yet.)

Clarification: Swapping classes at runtime

Here’s what I was getting at:




setAlias('@someAlias', 'Foo');

$foo = Yii::createObject('@someAlias'); // is a Foo


setAlias('@someAlias', 'Bar');

$bar = Yii::createObject('@someAlias'); // is a Bar



The name of the alias stays the same, but the returned object is different. But honestly after some thought I’m not sure it’s necessary:

  • Direct dependency injection is clearer and generally better

  • You can easily implement this using an application component

I get it’s just an example, but is this a common pattern for injecting dependencies in Yii? (Dependency configurations as class properties.)

I’m used to explicitly declaring dependencies in constructors and/or setters:




class Rocket

{

    private $engine;


    public function __construct(EngineInterface $engine)

    {

        $this->engine = $engine;

    }


    public function fly()

    {

        $this->engine->work();

        echo "Wheeeeeeeeee";

    }

}



I’ve always wondered how to Yii-ify it, or make it more Yii-like in general. You don’t really see code like this in the docs. (I think? Maybe I’m just forgetting examples.)

You have to explicitly specify the namespace when using the class map, so it’s not quite the same as aliasing.

danschmidt5189

BaseYii::createObject() is indeed grew up from 1.1 widgetfactory. Widgetfactory itself was removed since now you can configure any object class defaults including widgets.

Using aliases as you’ve mentioned isn’t a good idea.

There are three types of dependency injection: setter, propery, constructor. I’ve just used one type as an example. Configuration as a property and lazy initialization is common in Yii, yes.

Either setter or public property will work with Yii’s createObject. You won’t be able to hint for an interface since passing an array will be valid as well.

Property injection in Yii2 is golden. Nice job with all that!

Constructor injection works as well, since additional arguments are passed to the constructor:




class Rocket

{

    public function __construct(EngineInterface $engine, $config = []);

}

$rocket = Yii::createObject([

    'class' => 'Rocket',

    // Passed as $config array

    'param1' => 'val1',

    'param2' => 'val2',

], new vendor\engines\GasEngine());



As for using aliases, that’s just a bad way to implement (one small piece of) a service locator. But there’s nothing necessarily wrong with using a simple name to refer to a specific class or object. Every call to Yii::$app->db is basically doing the same thing, except you’re reasonably certain that what you get back implements the interface you want.