Dependency injection into class props

  • This topic is empty.
Viewing 1 post (of 1 total)
  • Author
    Posts
  • #1631
    loopcake
    Participant

    I’ll start by saying that I’m using the “Tutorial” flair and I’m not sure if it’s correct for this type of content.

    If this is a mistake please let me know.

    ​

    In [my last post](https://www.reddit.com/r/PHP/comments/jansfa/a_standalone_cli_server_enhanced_by_the_jit/) I mentioned Autowiring and dependency injection regarding the discussed codebase.It’s actually a pretty simple implementation and it’s standalone, it doesn’t need support from any framework.

    Here’s the repository: [https://github.com/tncrazvan/php-autowire](https://github.com/tncrazvan/php-autowire), you can also get it with composer from [tncrazvan/autowire](https://packagist.org/packages/tncrazvan/autowire).

    ​

    The whole idea is based around Singletons, this is inspired by Java Spring Boot.

    I’ll walk through how to use it.

    ​

    I’ve prepeared this exmample [https://github.com/tncrazvan/catpaw-examples-autowire](https://github.com/tncrazvan/catpaw-examples-autowire)

    All you need to know with regards to the server I’m using is that the controller found in `src/api/http/AccountController.php` responds to the endpoint “`/account`” for GET and POST methods.

    Obviously this setup would be different depending on the framework you’re using.

    ​

    This is where this controller is getting instantiated (src/main.php)

    <?php

    use apihttpAccountController;
    use modelsAccount;

    return [
    “port” => 80,
    “webRoot” => “../public”,
    “sessionName” => “../_SESSION”,
    “asciiTable” => false,
    “events” => [
    “http”=>[
    “/account” => fn(?Account $body) => AccountController::singleton($body)
    ],
    “websocket”=>[]
    ]
    ];

    As you can se I’m not using the `new` keyword, instead I’m using the `::singleton(…)` static method, that’s because AccountController uses the `Singleton` trait, which provides the method:

    &#x200B;

    <?php
    namespace apihttp;

    use comgithubtncrazvancatpawhttpHttpEventHandler;
    use comgithubtncrazvancatpawhttpmethodsHttpMethodGet;
    use comgithubtncrazvancatpawhttpmethodsHttpMethodPost;
    use modelsAccount;
    use servicesAccountService;

    //Autowiring tools
    use iogithubtncrazvanautowireAutowired;
    use iogithubtncrazvanautowireSingleton;

    class AccountController extends HttpEventHandler implements HttpMethodGet,HttpMethodPost{
    use Singleton;
    use Autowired;

    public AccountService $service;

    public function post(Account $account):string{
    $this->service->save($account);
    return “Account created!”;
    }

    public function get():array{
    return $this->service->findAll();
    }
    }

    The `::singleton(…)` methods will trigger the `auto_inject()` method of `AccountController`, which is provided by the next trait: `Autowired`.

    `Autowired` is the trait that will actually inject your dependencies.

    This type of injection does not use the constructor as a means of injecting, it’ll instead inject your dependecies directly as props to your class (also inspired by spring boot).

    In order to inject your dependency you simply need to declare it as a ~~public~~ `public`, `protected` or `private` property and specify its `type`, so in this case: `public AccountService $service;`

    &#x200B;

    Taking a look into `AccountService`, you’ll notice that it also uses the `Singleton` trait, and that is required for the injection to happen:

    <?php
    namespace services;

    use modelsAccount;
    use iogithubtncrazvanautowireSingleton;

    class AccountService{
    use Singleton;

    private static array $users = [];

    public function save(Account $account):void{
    static::$users[$account->username] = $account;
    }

    public function findAll():array{
    return static::$users;
    }
    }

    Remember, if you want your class to be injectable, you must use the trait `Singleton`.

    This is pretty much all there is to it, run the server with `composer run start`

    NOTE: if the framework you’re using won’t allow you to define how your controllers are being created, you can always ommit the Singleton trait in your controller, and call `auto_inject()` manually in your constructor, like so:

    <?php
    namespace apihttp;

    use comgithubtncrazvancatpawhttpHttpEventHandler;
    use comgithubtncrazvancatpawhttpmethodsHttpMethodGet;
    use comgithubtncrazvancatpawhttpmethodsHttpMethodPost;
    use modelsAccount;
    use servicesAccountService;

    //Autowiring tools
    use iogithubtncrazvanautowireAutowired;
    use iogithubtncrazvanautowireSingleton;

    class AccountController extends HttpEventHandler implements HttpMethodGet,HttpMethodPost{
    //use Singleton;
    use Autowired;

    public function __construct(){
    $this->auto_inject(); // <=== invoke it here
    }
    public AccountService $service;

    public function post(Account $account):string{
    $this->service->save($account);
    return “Account created!”;
    }

    public function get():array{
    return $this->service->findAll();
    }
    }

    Obviously all of this perfmors better in non blocking cli servers since singletons have longer life spans in that type of environment and they perform even better if you’re using a jit.The autoinjection only happens the first time the `::singleton(…)` method is called if you’re using the `Singleton` trait.

    And even if you’re omitting the `Singleton` trait in your controller and you call `auto_inject()` manually, the autoinjection will still skip properties that are already initialized.

    NOTE 2: you might have noticed [I’m using the reflection api here](https://github.com/tncrazvan/php-autowire/blob/836b9627c06f627996e7fae0244f0ecc0bf899c5/src/io/github/tncrazvan/autowire/Autowired.php#L9) instead of making use of php variable class names, like `new $classname()` or even using `$this` directly, and that is because I’m actually waiting for php 8 to be officially released and implement all of this using attributes instead of traits, and the reflection api is the way to do it, even though it’s a little slower (which is only a concern for the first time it runs).

    &#x200B;

    Finally here’s how to make the requests through js.

    POST request:

    (async ()=>{
    const RESPONSE = await fetch(“/account”,{
    method:”POST”,
    headers:{
    “Content-Type”:”application/json”
    },
    body:JSON.stringify({
    username: “my-username”,
    password: “123”,
    otherDetails: “details”
    })
    });
    let text = await RESPONSE.text();
    console.log(text);
    })();

    this will add a new account.

    GET request:

    (async ()=>{
    const RESPONSE = await fetch(“/account”,{
    method:”GET”,
    headers:{
    “Accept”:”application/json”
    }
    });
    let json = await RESPONSE.json();
    console.log(json);
    })();

    this will return all accounts.

    These requests are obviously specific to the type of server and example I’m using.

    &#x200B;

    I hope you like it!

    I’ll post more stuff soon, next up are quarkus panache-like entities!

Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.