Zend Framework 2: Touching headLink() twice on layout template
Friday, January 17. 2014
This one was one of the tricky ones. My CSS-inclusion was doubled for a very strange reason. My layout-template has:
{$this->headLink()
->prependStylesheet('/css/style.css')
->prependStylesheet('/css/jQuery/jquery.mobile.css')}
{$this->headLink([
'rel' => 'shortcut icon',
'type' => 'image/vnd.microsoft.icon',
'href' => '/images/favicon.ico'
])}
That would be pretty standard for any web application. Link a couple of CSS-definition files and declare the URL for favorite icon of the website. However, on ZF2 doing things like my above code does, makes things go bad. Rather surprisingly, the HTML gets rendered as:
<link href="/css/jQuery/jquery.mobile.css" media="screen" rel="stylesheet" type="text/css">
<link href="/css/style.css" media="screen" rel="stylesheet" type="text/css">
<link href="/css/jQuery/jquery.mobile.css" media="screen" rel="stylesheet" type="text/css">
<link href="/css/style.css" media="screen" rel="stylesheet" type="text/css">
<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
It doesn't actually break anything to have the CSS linked twice, but it just makes the CSS-debugging bit weird. Lot of the declarations are twice in the list and browser has to determine which ones are effective and which ones are ignored in any particular case.
To find out what's going on, I swapped my template to contain:
{$this->headLink([
'rel' => 'shortcut icon',
'type' => 'image/vnd.microsoft.icon',
'href' => '/images/favicon.ico'
])}
{$this->headLink()
->prependStylesheet('/css/style.css')
->prependStylesheet('/css/jQuery/jquery.mobile.css')}
Whatta ... hell!? Now everything works as expected. First the favicon-link and then CSS-links. Without any unnecessary doubling.
After a nice long morning of debugging ZF2-view code revealed a solution:
{$this->headLink()
->prependStylesheet('/css/style.css')
->prependStylesheet('/css/jQuery/jquery.mobile.css')}
{$this->headLink()
->deleteContainer()}
{$this->headLink([
'rel' => 'shortcut icon',
'type' => 'image/vnd.microsoft.icon',
'href' => '/images/favicon.ico'
])}
Now everything renders nicely. No doubles, all in the order I wanted them to be. The key was to erase Zend\View\Helper\HeadLink's container after doing the stylesheets. The method is actually in the class Zend\View\Helper\Placeholder\Container\AbstractStandalone. Apparently headLink's container only adds up and any subsequent calls simply add to the existing storage. The mistake is to print the contents of the container in the middle. The final solution is not to touch headLink() twice:
{$this->headLink([
'rel' => 'shortcut icon',
'type' => 'image/vnd.microsoft.icon',
'href' => '/images/favicon.ico'
])
->prependStylesheet("/css/style.css")
->prependStylesheet("/css/jQuery/jquery.mobile.css")}
Now it works much better! The rendered HTML will have the items in appropriate order:
- /css/jQuery/jquery.mobile.css
- /css/style.css
- /images/favicon.ico
This was yet again one of the funny things that have changed since ZF1. I definitely would consider that as a bug, but don't want to bother sending Zend a report out of it. They'll yet again pull a Microsoft and declare it as a feature.
Zend Framework 2: preDispatch(), returning properly without executing action
Thursday, January 16. 2014
Getting ZF2 to do preDispatch() and postDispatch() like ZF1 had is widely known and documented. In your controller, add this:
protected function attachDefaultListeners()
{
parent::attachDefaultListeners();
$event_mgr = $this->getEventManager();
$event_mgr->attach('dispatch', array($this, 'preDispatch'), 100);
$event_mgr->attach('dispatch', array($this, 'postDispatch'), -100);
}
Two simple listeners are attached with proper priorities to trigger before and after the action.
However, to go somewhere else before the action is executed adds some complexity, as one can expect. In preDispatch() you can do one of two suggested things. A redirect:
// Do a HTTP/302 redirect
return $this->redirect()->toRoute(
'application', array('controller' => 'index', 'action' => 'index'
));
My issue here is, that it literally does a HTTP/302 redirect in your browser. Another problem is, that it still executes the action it was targeted to. It renders the view, runs all the listeners, does all the plugins and helpers as it started to do. It just redirects after all that. I don't want my user to do a redirect or to run all the bells and whistles including the action. Why cannot I simply return something else instead, like ZF1 could be programmed to do. On my top-1 list is to execute an action from another controller?
So, the another option to do is to simply call it quits right in the middle of preDispatch():
$url = $event->getRouter()
->assemble(
array('action' => 'index'),
array('name' => 'frontend')
);
$response = $event->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
exit();
That's pretty much the same as previous, but uglier. exit()!! Really? In Zend Framework?! I'd rather keep the wheels rolling and machine turning like it normally would do until it has done all the dirty deeds it wants to do. Poking around The Net reveals, that nobody is really offering anything else. Apparently everybody simply are doing a copy/paste from the same sites I found.
This is what I offer. Discard the current operation, start a new one and return that! A lot better alternative.
Example 1, return JSON-data:
$event->stopPropagation(true);
// Skip executing the action requested. Return this instead.
$result = new JsonModel(array(
'success' => false,
'loginrequired' => true
));
$result->setTerminal(true);
$event->setResponse(new Response());
$event->setViewModel($result);
The key is in the setResponse()-call.
Example 2, call another action:
$event->stopPropagation(true);
// Skip executing the action requested.
// Execute anotherController::errorAction() instead.
$event->setResponse(new Response());
$result = $this->forward()->dispatch('Another', array(
'action' => 'error'
));
$result->setTerminal(true);
$event->setViewModel($result);
Hope this helps somebody else trying to do a ZF1 to ZF2 transition. In the end, there is only one thing similar between them. Their name has Zend Framwork in it.
git and HTTPS (fatal: HTTP request failed)
Friday, January 10. 2014
Two facts first about git:
- A number of sites tells you to use git:// or ssh:// instead of https://. Apparently there is some unnecessary complexity when piggy-backing over HTTP-secure.
- I personally don't like git due to it's complexity. Its like a requirement of being an experienced mechanic before getting a driver's license. You can drive a car without exact technical knowledge about the inner workings of a car. But that seems to be the only way to go with git.
So, I choose to run my own repo on my own box and do it over HTTPS. Since HTTPS is a 2nd class protocol in the git-world many simple things are unnecessarily difficult.
My initial attempt was to do a simple clone from my existing repo:
git clone https://me@my.server/my/Project
Well, that doesn't end well. There is this fully explanatory fatal: HTTP request failed -error. Adding --verbose does not help. Then I found a fact that git uses curl as it's HTTPS-transport client and very helpful environment variable to diagnose the problem:
export GIT_CURL_VERBOSE=1
git clone https://me@my.server/my/Project
That way I got the required debug-information about Certificate Authority -certificates being used. It didn't use my own CA's file at all.
The next fix was to tweak the configuration:
git config --global http.sslverify false
It made my clone working! That, however, is not the way I do computer security. I need my certificates verified. From git-config(1) man-page I found the required piece of information. Adding the CA-root path of my Linux-distro makes the entire thing working:
git config --global http.sslverify true
git config --global http.sslCAPath /etc/pki/tls/certs
Finally I found the good site about all this: http://stackoverflow.com/questions/3777075/ssl-certificate-rejected-trying-to-access-github-over-https-behind-firewall/4454754 It seems to contain all this information.
Unfortunately too late! But wouldn't it be great for git to emit the proper error message about the "Peer's certificate issuer is not recognized"? That was the original problem to begin with. Also, why don't CentOS-people configure their curl-library to behave like OpenSSL does?