Zend Framework 2 passing variable to models

2019-06-21 20:27发布

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

2条回答
何必那么认真
2楼-- · 2019-06-21 20:52

I would imagine that you could first register your session model in the service manager in the Module.php of whatever module you desire so that it is globally available, and use this service any time you need to access the session.

public function getServiceConfig()
{        
    return array(
        'factories' => array(
            'AppSession' => function($sm) {
                  $session = new \Path\To\Session();
                  $session->doSomething();
                  return $session;
            }
        )
    );
}

When you register your various models throughout your application in the service manager, inject your session service then.

public function getServiceConfig()
{        
    return array(
        'factories' => array(
            'Application\Model\SomeThing' => function($sm) {
                $c = new SomeThing();
                // You will have to create the set and get functions.
                $c->setDbAdapterService($sm->get('Zend\Db\Adapter\Adapter'));
                $c->setSessionService($sm->get('AppSession'));
                return $c;
            }
        )
    );
}

And on your controller too, it might make more sense to inject the session from the start so that the session is ready and waiting for you.

In your module.config.php...

return array(
    'controllers' => array(
        'factories' => array(
            'Application\Controller\Something' => function ($sm) {
                $locator = $sm->getServiceLocator();
                $c = new Application\Controller\SomethingController();
                $c->setSessionService($locator->get('AppSession'));
                return $c;
            },
        ),
    ),
    'router' => array(
        'routes' => array(
            'myroute' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route' => '[/:action]',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Something',
                        'action'     => 'index',
                    ),
                ),
            ),
        ),
    ),
);
查看更多
手持菜刀,她持情操
3楼-- · 2019-06-21 21:06

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.

查看更多
登录 后发表回答