aufziehvogel
aufziehvogel

Reputation: 7287

Avoiding cyclic dependencies between entities in two modules

I’m currently wondering what’s a good way to keep ZF2 modules copyable from one project to another, if I have doctrine2 entities that reference each other.

My current situation is something like this: I have an entity User from which I want to be able to access all languages this user speaks.

Of course, Language is not a component of the Authentication module, because I might want to use it for other purposes, too.

namespace Authentication\Entity;

class User {
    public function getSpokenLanguages();
}

And:

namespace Application\Entity;

class Language {
    public function getUsersWhoSpeakThisLanguage();
}

The problem is, I want my Authentication module to be totally independent from the project-specific module Application.

Is there a good way to keep these relations out of my entities or possibly inject them from the Application module? Maybe also a UserService (in Application module) giving me the languages Language[] for a specific User would be a good idea? I could call it like this:

$userService->getUsersLanguages($user);

I think, especially injection might be a ZF2-solution, but I have no idea, how one could extend Doctrine2 entities like that from another module.

Upvotes: 1

Views: 376

Answers (1)

Saeven
Saeven

Reputation: 2300

I think you're speaking to more of semantic issue than one specific to ZF2. Reading your question, I think your language becomes more of a managed layer that you can easily facilitate with factories and DI - luckily ZF2 has all the right tools. Consider something like this as a potential draft for a solution:

Create a LanguageAbstractFactory:

namespace Your\Namespace;

use Zend\ServiceManager\AbstractFactoryInterface,
    Zend\ServiceManager\ServiceLocatorInterface;

class LanguageAbstractFactory implements AbstractFactoryInterface
{
    /**
     * Determine if we can create a service with name
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param                         $name
     * @param                         $requestedName
     * @return bool
     */
    public function canCreateServiceWithName( ServiceLocatorInterface $serviceLocator, $name, $requestedName )
    {
        return stristr( $requestedName, 'Namespace\Language' ) !== false;
    }


    public function createServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
    {
        $filter = new $requestedName();
        $filter->setServiceLocator( $locator );
        return $filter;
    }

}

Then, create your languages in that same namespace, as subclasses of Language, that implement ServiceLocatorAwareInterface (to give you database access down the road and such). The code in the factory above injects the service locator (and that's where you tweak it to inject other goodness to satisfy your language architecture):

namespace Your\Namespace;

use Zend\ServiceManager\ServiceLocatorAwareInterface,
    Zend\ServiceManager\ServiceLocatorInterface;


class Language implements ServiceLocatorAwareInterface
{

    protected $serviceLocator;


    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }

    // ... other things your factory knows, that this class may not go here
}

A Language implementation might then look like:

namespace Your\Namespace\Language;

class English extends \Your\Namespace\Language
{

    public function getUsersWhoSpeakThisLanguage()
    {
        $sm = $this->getServiceManager();
        // get your entities or w/e using the SM
    }
}

Connect the factory by tweaking your module's Module.php at getServiceConfig:

public function getServiceConfig() { return array(

        'abstract_factories' => array(
            // this one generates all of the mass email filters
            'Your\Namespace\LanguageAbstractFactory',
        ),

    );
}

This gives you the ability to use the service manager to get a service-aware language very easily. e.g., from a service-aware class:

$sm->getServiceManager()->get( '\Your\Namespace\Language\English' );

Because of the config, and that the Factory can meet the request, your factory will auto-configure the English instance with whatever logic you build into it in a very portable fashion.

Where this is kind of a primer on Factories - if you rig an interface class that the service can use to speak to your user classes, you can invert control to the Language service from the User. Making your User implement LanguageAware (for example) which contains classes that the Language service can use should be a few steps away.

Hope this helps. There's probably 15 ways to skin this cat; this approach is one I have used to solve similar problems, e.g., that of "Filtering" data. A filter, can filter information, and information can be filtered by a filter.

Good Luck!

Upvotes: 1

Related Questions