0

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?

Reece Como
  • 75
  • 9
  • 2
    Have you looked at [dependency injection](http://php-di.org/)? That is most probably what you are looking for. But this will only work, if you actually have controller instances, so all controller methods need to be non-static (e.g. displayUserFriends). Why are they static anyway? – Fabian Kleiser Oct 21 '14 at 07:21
  • Oops sorry. In the class files they're not actually static, I've fixed the code in the original question to reflect that. – Reece Como Oct 21 '14 at 07:29
  • @FaKeller Would it be bad practice to store the current logged in user object as a superglobal `$GLOBALS['CurrentUser']`? – Reece Como Oct 21 '14 at 07:31
  • Yes, i think global variables are bad practice and [many others do, too](http://stackoverflow.com/a/5166527/1262901). They are bad practice, because they introduce side effects. – Fabian Kleiser Oct 21 '14 at 07:37

2 Answers2

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:

Community
  • 1
  • 1
Fabian Kleiser
  • 2,988
  • 3
  • 27
  • 44
0

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, _POST and 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() uses AuthService::getCurrentUser().
  • UserService now depends on AuthService.
  • 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 AuthService with static methods because we want to test our UserService class. 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 of AuthService in test and create UserService with this stub, instead of real AuthService. 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 UserService manually 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.

rr-
  • 14,303
  • 6
  • 45
  • 67