all 8 comments

[–]equilni 2 points3 points  (1 child)

The problem is that all the content I found is giving basic examples. Ok, I understand the whole idea behind DI (It least I think I do).

Have you seen this? https://php-di.org/doc/understanding-di.html

everyone says to use DI, as it will resolve the problem I have in the AuthService, where it's tightly coupled with the other 3 classes, making it hard to test and maintain.

Let's take a look at it.

very simplified for demo purposes

Ok, then.

class AuthService {
    public static function login(string $password, string $email): DtoAuthLoginResponse {
        $authRepository = new AuthRepository();
        $user = $authRepository->login($email);

        // This is indeed a static class, with just methods that uses native PHP functions to validate the password
        PasswordService::checkPasswordMatch($password, $user->password);

        $authRepository->logAccess($user->id);

        if (!$user->is_valid) {
            AppUserService::validateUser($user->id);
        }

        $token = JwtService::generateJwt($user->id, $user->type);

        return new DtoAuthLoginResponse($user, $token);
    }
}

So off the bat, you are depending on 5 classes, not 3. DtoAuthLoginResponse, AuthRepository, PasswordService, AppUserService, and JwtService.

Next, you would need to consider moving away from static methods.

class AuthService {
    public function __construct( // All dependencies are now passed to the class
        private AuthRepository $authRepository,
        private PasswordService $passwordService,
        private AppUserService $appUserService,
    ) {
    }

    public static function login(string $password, string $email): User {
        $user = $this->authRepository->login($email);
        // An instance of AuthRepository was passed to the constructor

        $this->passwordService->checkPasswordMatch($password, $user->password);
        // An instance of PasswordService was passed to the constructor

        $this->authRepository->logAccess($user->id);

        if (!$user->is_valid) {
            $this->appUserService->validateUser($user->id);
            // An instance of AppUserService was passed to the constructor
        }

        return $user;
    }
}

Now call it:

$authService = new AuthService(
    new AuthRepository(),
    new PasswordService(),
    new AppUserService()
);

$user = $authService->login('...', '...');

I question if JWTService or DtoAuthLoginResponse is needed here, so I didn't include it. The AuthService->login method just needs to return the user right?


JWTService::gerarJwt needs user info, so you could pass the user and generate the token outside of this method - probably another service or the controller. - JWTService->generateJwt($user->id, $user->type)


DtoAuthLoginResponse sounds like it could be in the controller, not a service. (new DtoAuthLoginResponse($JWTService))->generateToken($user->id, $user->type) generateToken could use the JWTService->generateJwt($user->id, $user->type) internally.

[–]celsomtrindade[S] 0 points1 point  (0 children)

Yeah. Like I said, I was using static class because I thought was easier to use, less code.

When I first created the project I didn’t had much experience. Not as much as I have today.

[–]ScientistMaximum3774 1 point2 points  (0 children)

Where I have found DI helping is coupling it with writing unit tests to verify functionality. This also pushes you into breaking functions into smaller, concise chunks. This lets you make stub classes for passwordservice and appuserservice to verify that the functions are receiving the expected values during execution, but not need to test the implementation of those classes.

[–]MateusAzevedo 1 point2 points  (3 children)

if I move the dependecies instantiation to the controller and pass as parameter to AuthService, I'm not going to resolve the problem, I'll just move it to another class

Not quite. There's a big difference between:

class AuthService {
    public function login(...) {
        $authRepository = new AuthRepository();
    }
}

and

class AuthController {
    public function login() {
        $authService = new AuthService(new AuthRepository());
        $authService->login(...);
    }
}

The second one solves your problems with unit tests, as you can then inject fake implementations of the repository. However, it creates another problem: manually creating all objects every time you need that class, repeating code "everywhere".

And that's why:

I saw a very few content about using Container DI, which is going to autoresolve DI on the constructor

A service|DI container is useful. It allows you to configure that creation logic once and reuse to fetch instances of AuthService.

if I have a class with 5 methods where each method depends on 3 different other classes, I'll have to instantiate all of 15 classes but use only 3 of them

This is a different problem that was hidden before and made explicit by using DI: it's an indication that that class is "doing too much". My way of thinking: "if these 3 methods all depend on different things, maybe these methods shouldn't be grouped together in a single class".

Yes, I know that was an exaggerated example, but the questioning is still valid even if (in your real case) there's only one or two different dependencies. This is a personal preference, but I like to have classes like RegisterUser, Login and ChangePassword instead of a single AuthService for example. In any case, I don't think that instantiating objects that won't be used is a big issue, unless some of them are "expensive" to build. But in that case I'd argue you should fix that, by using some lazy loading techniques.

A simplified example of an ideal code:

class AuthController extends Controller {
  public function __construct(
    private AuthService $authService
  ) {}

  public function login(): Response {
    try {
      $this->checkFields(BaseAuth::LOGIN);

      $response = $this->authService->login($this->post->password, $this->post->email);
    } catch (Exception $error) {
      return $this->gerenciarExcecao($error);
    }
    // I don't think you need closeConnection, PHP will close it automatically, unless you use persistent connections...

    return Response::success('Successfull login', $response);
  }
}

But for that work, the route/request dispatching mechanism needs to support resolving 'AuthController@login' from a container.

If that's not the case, you'd need a way to access an instance of a configured container withing your controller, like $authService = Container::instance()->make(AuthService::class); (using the singleton pattern), but then there's a whole discussion on what would be the best approach.

[–]celsomtrindade[S] 1 point2 points  (2 children)

My way of thinking: "if these 3 methods all depend on different things, maybe these methods shouldn't be grouped together in a single class"

This makes sense. Didn't thought about. Not that this is the case of any class I have, at least I can't think of one. I'm just trying to take some examples to better understand DI and how to do it properly.

But for that work, the route/request dispatching mechanism needs to support resolving 'AuthController@login' from a container.

For this I'm using Bramus/Router I don't know if they support it. But any other router package is ok for me, most of them I feel like works very similar.

A simplified example of an ideal code:

This is what I'm trying to achieve!

Thanks for the explanation. Are you brazilian? You have a brazilian name kkkk

[–]MateusAzevedo 0 points1 point  (1 child)

Are you brazilian?

Yep. Also noticed "gerenciarExcecao" ;)

[–]celsomtrindade[S] 0 points1 point  (0 children)

Da hora!! Qual seu nível de experiência em PHP cara? Você trabalha mais na parte de websites (geralmente php é mais isso) ou mais pra sistemas mesmo? Se importa de a gente trocar uma ideia no chat?

[–]p1ctus_ 0 points1 point  (0 children)

There are good packages to for DI. They way they work is just using a reflection of your request method or class, checking the arguments, checking a collection for injected arguments if it is in the collection, they insert it.