Correct way to deal with application-wide data nee

2019-06-07 18:59发布

问题:

I am currently involved in the development of a larger webapplication written in PHP and based upon a MVC-framework sharing a wide range of similarities with the Zend Framework in terms of architecture.

When the user has logged in I have a place that is supposed to display the balance of the current users virtual points. This display needs to be on every page across every single controller.

Where do you put code for fetching sidewide modeldata, that isn't controller specific but needs to go in the sitewide layout on every pageview, independently of the current controller? How would the MVC or ZF-heads do this? And how about the rest of you?

I thought about loading the balance when the user logs in and storing it in the session, but as the balance is frequently altered this doesn't seem right - it needs to be checked and updated pretty much on every page load. I also thought about doing it by adding the fetching routine to every controller, but that didn't seem right either as it would result in code-duplication.

回答1:

Well, you're right, having routines to every controller would be a code-duplication and wouldn't make your code reusable.

Unlike suggested in your question comments, I wouldn't go for a a base controller, since base controllers aren't a good practice (in most cases) and Zend Framework implements Action Helpers in order to to avoid them.

If your partial view is site-wide, why don't you just write your own custom View Helper and fetch the data in your model from your view helper? Then you could call this view helper directly from your layout. In my opinion, fetching data through a model from the view doesn't break the MVC design pattern at all, as long as you don't update/edit these data.

You can add your view helpers in /view/helpers/ or in your library (then you would have to register your view helper path too):

class Zend_View_Helper_Balance extends Zend_View_Helper_Abstract
{

    public function balance()
    {
        $html = '';
        if (Zend_Auth::getInstance()->hasIdentity()) {
            // pull data from your model
            $html .= ...;
        }
        return $html;
    }
}

Note that you view helper could also call a partial view (render(), partial(), partialLoop()) if you need to format your code in a specific way.

This is a pretty simple example, but to me it's enough is your case. If you want to have more control on these data and be able to modify it (or not) depending on a particular view (or controller), then I recommend you to take a look on Placeholders. Zend has a really good example about them here on the online documentation.

More information about custom view helpers here.

When you perform such a task, consider using the Zend_Cache component too, so you won't have to query the database after each request but let's say, every minute (depending on your needs).



回答2:

What you are looking for is Zend_Registry. This is the component you should use when you think you need some form of global variable. If you need this on EVERY page, then you are best adding it to your bootstrap, if you only need it in certain places add it in init method of relavent controllers.

application/Bootstrap.php

public _initUserBalance() 
{
    $userId = Zend_Auth::getInstance()->getIdentity()->userId;
    $user = UserService::getUser($userId);
    Zend_Registry::set('balance', $user->getBalance());
}

application/layouts/default.phtml

echo 'Balance = ' . Zend_Registry::get('balance');

That wee snippet should give you the right idea!



回答3:

In this case, I usually go with a front controller plugin with a dispatchLoopShutdown() hook that performs the required data access and adds the data to the view/layout. The layout script then renders that data.

More details available on request.

[UPDATE]

Suppose you wanted to display inside your layout the last X news items from your db (or web service or an RSS feed), independent of which controller was requested.

Your front-controller plugin could look something like this in application/plugins/SidebarNews.php:

class My_Plugin_SidebarNews
{
    public function dispatchLoopShutdown()
    {
        $front = Zend_Controller_Front::getInstance();
        $view = $front->getParam('bootstrap')->getResource('view');
        $view->sidebarNews = $this->getNewsItems();
    }

    protected function getNewsItems()
    {
        // Access your datasource (db, web service, RSS feed, etc)
        // and return an iterable collection of news items
    }
}

Make sure you register your plugin with the front controller, typically in application/configs/application.ini:

resource.frontController.plugins.sidebarNews = "My_Plugin_SidebarNews"

Then in your layout, just render as usual, perhaps in application/layouts/scripts/layout.phtml:

<?php if (isset($this->sidebarNews) && is_array($this->sidebarNews) && count($this->sidebarNews) > 0): ?>
<div id="sidebarNews">
<?php foreach ($this->sidebarNews as $newsItem): ?>
<div class="sidebarNewsItem">
   <h3><?= $this->escape($newsItem['headline']) ?></h3>
   <p><?= $this->escape($newsItem['blurb']) ?></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>

See what I mean?