Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zend Framework 2 passing variable to models

I'm currently developing a multilangual website. For the multilangual part i use translator / poedit. I store the selected language in session. It works fine.

Module.php:

public function onBootstrap(MvcEvent $e)
{
    // ...

    $session = new Container('base');

    if ($session->language !== NULL) {
        $e->getApplication()->getServiceManager()->get('translator')->setLocale($session->language);
    }
}

Action for setting language in a controller:

public function setLanguageAction()
{
    $language = $this->params()->fromRoute('language');

    if (isset($this->languages[$language])) {

        $session = new Container('base');

        if ($language == 'en') {
            $session->language = NULL;
        } else {
            $session->language = $language;
        }
    }

    return $this->redirect()->toRoute('home');
}

In module.config.php the default locale is set to en.

As i said everything works fine, except for one thing.

I store some language dependent data in DB too, so in my models i need to know what the current language is. The current language is needed for other purposes in the models too.

So i include the following code in every model's construct function:

$session = new Container('base');

if ($session->language !== NULL) {
    $this->language = $session->language;
} else {
    $this->language = 'default';
}

I think it's not the best solution. I have too much models to always include this code.

I would like to know if there is a solution to pass a $language variable automatically to all of my models for example from Module.php / getServiceConfig function:

public function getServiceConfig()
{        
    $session = new Container('base');

    return array(
        'factories' => array(
            'Application\Model\SomeThing' => function($sm) {
                $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                $c = new SomeThing($dbAdapter);
                $c->language = $session->language;
                return $c;
            }
        )
    );
}

Of course this is not working, but it would be great to be able to do something similar like this, or a more general solution, where it is not necessary to assign the value of the current language to every model's language variable in the factory array (for example a common bootstrap for models, where this assignment can be done for all models in one place).

Is there a solution for my problem?

Thank you for your help!

M

like image 495
Mpk1 Avatar asked Oct 05 '22 13:10

Mpk1


1 Answers

If you don't want to inject the language into all of your models, then I can think of at least one other way off the top of my head.

You could make use of an initializer. An initializer basically allows you to perform initialization tasks on services fetched through the service manager. Thus, to use this approach, you must fetch your models (at least the ones that need the language) through the service manager. This is as easy as adding them as invokables or perhaps factories if you need to inject any dependencies. For more information about initializers, you can read the article I wrote about the service manager (scroll down to the section about initializers).

So, what you can do is to have your models implement an interface, e.g. LanguageAwareInterface.

namespace User\Model;

interface LanguageAwareInterface {
    /**
    * Gets the language
    *
    * @return string
    */
    public function getLanguage();

    /**
    * Sets the language
    *
    * @param string $language
    */
    public function setLanguage($language);
}

Then you can do like the following in your model.

namespace User\Model;

class MyModel implements \User\Model\LanguageAwareInterface {
    /**
    * @var string $language The current language
    */
    protected $language;

    /**
    * Gets the language
    *
    * @return string
    */
    public function getLanguage() {
        return $this->language;
    }

    /**
    * Sets the language
    *
    * @param string $language
    */
    public function setLanguage($language) {
        $this->language = $language;
    }
}

If you wish, you could even make an abstract base model class that has the above code, or perhaps write a simple trait if you want to avoid extending a class. Then your models could each extend this base class, but that depends how many of your models actually need to work with the current language.

Now for the initializer. In my article I have an example of how to add it with method calls, but chances are that you will want to do it in your configuration file (perhaps in the Application module if you are using the Zend Skeleton Application). You can either return an array (or point to a config file) from the module's Module class in the getServiceConfig() method, or you can just add it to the YourModule/config/module.config.php file under the service_manager key. The official documentation does not give any example of this; in fact, initializers are the only thing missing. It should, however, work.

return array(
    /* Other configuration here */

    'service_manager' => array(
        'initializers' => array(
            'language' => function($service, $sm) {
                // $service is the service that is being fetched from the service manager
                // $sm is the service manager instance
                if ($service instanceof \User\Model\LanguageAwareInterface) {
                    $session = new \Zend\Session\Container('base');

                    if ($session->language === null) {
                        // Fetch default language from configuration
                        $config = $sm->get('Config');

                        $session->language = $config['translator']['locale'];
                    }

                    $service->setLanguage($session->language);
                }
            },
        ),
    ),
);

The above initializer checks every object that is fetched from the service manager to see if it implements the interface we created above. If it does, the language can be injected. In my example, I check to see if it is set in the session and if not, fetch it from the configuration. You may have to tweak the logic depending on your exact needs. Note that the initializer will be run every time you fetch an object from the service manager, and thus it does add a little bit of overhead. This is, however, not significant because we are only doing a simple type check when the service does not implement the interface. Also, services are shared by default, which means that a service will only be instantiated once; subsequent requests for the same service will then reuse the previously instantiated one. This helps to limit the overhead even further.

like image 82
ba0708 Avatar answered Oct 10 '22 03:10

ba0708