缓存的用户类vs单独的类,包含必要的缓存字段

I need to cache some info about a user who is logged in (such as security groups, name, username, etc.)

Currently I have a separate class to achieve just this, lets call it CurrentUserHelper. Given a user object, it will cache the appropriate data and save store info in the $_SESSION variable.

The issue is I'm finding a bunch of classes relying just on CurrentUserHelper because they only need a couple of common fields. In fact, most of the functions have the same name as my User class. There's a couple of functions in CurrentUserHelper, such as getSecurityGroupsNames(), that contains a cache of all security group names, but there is no reason this function name could not be in the user class also.

Instead, should I create a CachedUser class and pass this around? This class can extend User, but then I can override getName(), getSecurityGroups(), etc, and returned the cached data, and not preform db requests to get the data.

The downside of passing around a CachedUser object is that this kind of hides the fact the data isn't really up to date if a constructor/function is accepting a type User. I also need to find way to handle merging the entity with Doctrine 2, and making sure entities associating themselves with a CachedUser won't break. If I decide to cache some temporary data (such as # of page views since logged in), this property shouldn't be part of the User class, it's more about the current user's session.

If I continue using the CurrentUserHelper class, maybe I should create an interface and have both CurrentUserHelper and User for the common functionality the two classes would share?

Preface: Extension isn't the best way for these sorts of things.. I'm sure you've heard composition over inheritance shouted at you over and over again. In fact, you can even gain inheritance without using extends!

This sounds like a good use-case for the decorator pattern. Basically, you wrap your existing object with another one that implements the same interface, so it has the same methods as the inner object, but this object's method adds the extra stuff around the method call to the inner object, like caching, for example.

Imagine you have a UserRepository:

/**
 * Represents an object capable of providing a user from persistence
 */
interface UserProvider
{
    /**
     * @param int $id
     *
     * @return User
     */
    public function findUser($id);
}

/**
 * Our object that already does the stuff we want to do, without caching
 */
class UserRepository implements UserProvider
{
    /**
     * @var DbAbstraction
     */
    protected $db;

    /**
     * @var UserMapper
     */
    protected $mapper;

    /**
     * @param DbAbstraction $db
     * @param UserMapper    $mapper
     */
    public function __construct(DbAbstraction $db, UserMapper $mapper)
    {
        $this->db     = $db;
        $this->mapper = $mapper;
    }

    /**
     * {@inheritDoc}
     */
    public function findUser($id)
    {
        $data = $this->db->find(['id' => $id]);

        /** Data mapper pattern - map data to the User object **/
        $user = $this->mapper->map($data);

        return $user;
    }
}

The above is a really simple example. It'll retrieve the user data from it's persistence (a database, filesystem, whatever), map that data to an actual User object, then return it.

Great, so now we want to add caching. Where should we do this, within the UserRepository? No, because it's not the responsibility of the repository to perform caching, even if you Dependency Inject a caching object (or even a logger!).. this is where the decorator pattern would come in useful.

/**
 * Decorates the UserRepository to provide caching
 */
class CachedUserRepository implements UserProvider
{   
    /**
     * @var UserRepository
     */
    protected $repo;

    /**
     * @var CachingImpl
     */
    protected $cache;

    /**
     * @param UserRepository $repo
     */
    public function __construct(UserRepository $repo, CachingImpl $cache)
    {
        $this->repo  = $repo;
        $this->cache = $cache;
    }

    /**
     * {@inheritDoc}
     *
     * So, because this class also implements UserProvider, it has to
     * have the same method in the interface. We FORWARD the call to
     * the ACTUAL user provider, but put caching AROUND it...
     */
    public function findUser($id)
    {
        /** Has this been cached? **/
        if ($this->cache->hasKey($id))
        {
            /**
             * Returns your user object, or maps data or whatever
             */
            return $this->cache->get($id);
        }

        /** Hasn't been cached, forward the call to our user repository **/
        $user = $this->repo->findUser($id);

       /** Store the user in the cache for next time **/
       $this->cache->add($id, $user);

       return $user;
    }
}

Very simply, we've wrapped the original object and method call with some additional caching functionality. The cool thing about this is that, not only can you switch out this cached version for the non-cached version at any time (because they both rely on the same interface), but you can remove the caching completely as a result, just by changing how you instantiate this object (you could take a look at the factory pattern for that, and even decide which factory (abstract factory?) depending on a configuration variable).