3

This is not about instructions (the docs are sufficient), but a question of how things work.

Symfony 4's autowiring system allows us to auto-inject services by simply typehinting

use App\Util\Rot13Transformer;

class TwitterClient
{
  public function __construct(Rot13Transformer $transformer)
  {
    $this->transformer = $transformer;
  }
}

To gain a deeper understanding of PHP, I have looked around in the symfony-bundle source code but can't find the spot where the "magic" happens.

How can Symfony prevent PHP from protesting that not enough arguments were fed to the constructor (or any function that uses autowiring)?

fridde
  • 472
  • 1
  • 5
  • 17
  • with reflection, obviously http://php.net/manual/en/intro.reflection.php – ArtisticPhoenix Dec 03 '17 at 14:40
  • Some examples: http://php.net/manual/en/reflection.examples.php – ArtisticPhoenix Dec 03 '17 at 14:42
  • I think you are really asking how the container works as opposed to just auto wiring. Take a look at the compiled container under the var directory to see how a generated factory method is used to create each instance. – Cerad Dec 03 '17 at 14:51
  • @Cerad: it's a bit of both. I have (obviously) only been using Reflection for simple stuff and had not been aware of its power. I knew there was something I had been missing. – fridde Dec 03 '17 at 17:33

2 Answers2

6

They use Refection

How can Symfony prevent PHP from protesting that not enough arguments were fed to the constructor

Reflection allows you to inspect the definition of other "things" in PHP. Among them are Classes their methods, and the arguments for those methods.

<?php
class bar
{
    //just a placeholder class
};

$bar = new bar(); //instance of bar

//class to inspect
class foo
{        
    public function __construct( bar $bar)
    {
        //do something fancy with $bar
    }        
}

//get the Reflection of the constructor from foo
$Method = new ReflectionMethod('foo', '__construct');

//get the parameters ( I call them arguments)
$Args = $Method->getParameters();

//get the first argument
$Arg = reset($Args);

//export the argument definition
$export = ReflectionParameter::export(
   array(
      $Arg->getDeclaringClass()->name, 
      $Arg->getDeclaringFunction()->name
   ), 
   $Arg->name, 
   true
);

//parse it for the typehint
$type = preg_replace('/.*?(\w+)\s+\$'.$Arg->name.'.*/', '\\1', $export);
echo "\nType: $type\n\n";

var_dump(is_a($bar, $type));

Outputs:

Type: bar

bool(true)

You can see it here

Then you just use is_a() or whatever to see if an "input" object has bar as one of it's ancestors. And as you can see in this simplified example if we had object $bar, we would know that it's perfectly good as an input to our constructor because it returns true.

I should note SO is probably not the right place to ask this, but i could use it in one of my many projects so I didn't mind figuring it out. Also I never used Symphony... Special thanks to this SO question for the last bit on parsing the type hint:

PHP Reflection - Get Method Parameter Type As String

That said I would have figured the Regx out in about 10 seconds, the export method no so much.

This is the extent of the documentation on it

http://php.net/manual/en/reflectionparameter.export.php

Literally

public static string ReflectionParameter::export ( string $function , string $parameter [, bool $return ] )
ArtisticPhoenix
  • 21,464
  • 2
  • 24
  • 38
  • 1
    You not only answered the question, but also gave an introduction to Reflection, an obviously blind spot in my knowledge. You truly followed this concept: https://xkcd.com/1053/ – fridde Dec 03 '17 at 17:37
  • I use it all the time for similar things, typically not to the level they use it. I've never used Symphony but I am a fan of what I have seen of it. My new github project needs something similar, it's called `eJinn` it's a way of building a config that will auto create exception classes for any project. So I need to inspect the constructor of the class supplied to the extends property of the config. Basically someone could use a base exception class with custom params in it. And I need to try to account for it. you can find the link to it below ( it's not working yet, close ) – ArtisticPhoenix Dec 04 '17 at 03:04
  • That's why I didn't mind doing this, my end goal is a plugin-able framework. the base of that is in the Evo, folder on my github, so now I am splliting off parts to their own projects. https://github.com/ArtisticPhoenix/eJinn – ArtisticPhoenix Dec 04 '17 at 03:05
2

As others mentioned, they use Reflection. If you want to see how exactly Symfony is doing this, start with autowire() method here

lchachurski
  • 1,770
  • 16
  • 21