问题
This is a question regarding best practice for storing and accessing an object from another class.
I'm using a simple homemade MVC paradigm in PHP, the class is called User and has its own methods and vars that essentially works as a database abstraction layer.
This class is instantiated by calling newUser($userID) which retrieves the data from a database given $userID or throws an exception if there is no user with that ID.
Every page has its own WebViewController class governing the content of the page, and in some instances the page needs to call $loggedInUser dependent functions such as WebViewController->displayUserFriends(), which might look like this:
<?php
class WebViewController extends WVCTemplate
{
// Class vars and methods
// ...
public function displayUserFriends()
{
foreach($loggedInUser->getFriends() as $friend) {
// Do something
// ...
}
}
}
?>
Is there a (best practice compliant) way to store LoggedInUser as a sort of global object, so it could be accessed inside of any class or WebViewController without instantiating it inside every class it's used?
回答1:
The most elegant solution to your problem is to use dependency injection. As you may need the current user in multiple controllers it would be best to create an AuthenticationService (or similar) that provides methods to check whether a user is logged in and to get the currently logged in user and encapsulates that common functionality. You can then inject the service instance in all the controllers where you need it.
There are several standalone PHP dependency injection libraries out there:
- PHP-DI
- Pimple
- Aura.DI
- Dice
回答2:
I think it's a matter of opinion, really. Nonetheless, here I'm going to present two viable options...
Option 1
...is to create a singleton that will hold current user:
class SessionHolder
{
private $user;
public static function getCurrentUser()
{
return self::$user;
}
public static function setCurrentUser(User $user)
{
self::$user = $user;
}
}
Usage:
//Authentication does following:
SessionHolder::setCurrentUser($user);
//web view controller does following:
SessionHolder::getCurrentUser();
The obvious advantage is that it's easy to incorporate this in about any framework. The disadvantage is that using singletons is usually a bad idea, since they make it very difficult to test your code (you cannot mock them). It's also difficult to track your dependencies. To answer a question "is my controller dependent on active user?", you need to manually search your code.
I think the testing part is really important, since sooner or later you'll stumble upon TDD in your work (if you haven't already), and as such, it's good to know how to deal with its idiosyncrasies.
Option 2
Use service layer. I think it's one of the nicest ways to structure your project. For example, I often use following structure in my projects:
- Controllers - act as proxies to services, collecting user input from
_GET,_POSTand whatnot. - Services - contain business logic, for example - which items should be put into customer's order.
- Models/DAO layer - contain database logic, like how to fetch data.
With architecture like this, you can structure your code in a following manner:
AuthService::getCurrentUser()- returns user currently logged in.UserService::getUserFriends(User $user)- returns friends for arbitrary user.UserService::getUserFriendsForCurrentUser()- returns friends for active user.UserService::getUserFriendsForCurrentUser()usesAuthService::getCurrentUser().UserServicenow depends onAuthService.- Finally, your controller can just call
$this->userService->getUserFriendsForCurrentUser()to fetch them, and optionally decorate them before sending them to view layer.
As such, your code can look like this:
class UserService
{
private $authService;
public function __construct(AuthService $authService)
{
$this->authService = $authService;
}
public function getUserFriends(User $user)
{
//...use DAO / models here...
}
public function getUserFriendsForCurrentUser()
{
$user = $this->authService->getCurrentUser();
if (!$user)
throw new DomainException('Must be logged in to do that.');
return $this->getUserFriends($user);
}
}
Advantages: your code is testable and the code looks clean, too. Dependencies are obvious just from glancing over constructor. Business logic is nicely separated from controllers, which acts just as proxies.
Disadvantages: you need to produce more boilerplate code. It's not really MVC either. However, since you said yourself that you use homemade MVC pattern, I think this approach is worth a shot, too.
As a side node - as you see, we used constructor to inject AuthService into UserService. I think it's worth some commenting:
we haven't used singleton
AuthServicewith static methods because we want to test ourUserServiceclass. To test it, we may or may not want to use mocks. In this particular case, we'd like to mock AuthService. Doing so is easy, just implement your variant ofAuthServicein test and createUserServicewith this stub, instead of realAuthService. If you wish to know more about good practices regarding making your code testable, see more about TDD, since I don't believe this is in the scope of this question.it's important to know that you're not supposed to construct your
UserServicemanually with all its dependencies each time you need to use it. This is where dependency injection kicks in - you just declare what your UserService needs, and then you use an object factory to retrieve its instance. (Ideally, you need calling object factory just once in the whole project, and it will construct whole dependency tree on a runtime basis.) For further reading, take a look at PHP-DI. It's on Github, too.
来源:https://stackoverflow.com/questions/26480939/best-practices-accessing-a-logged-in-user-object-from-other-classes