In my Symfony project, I've come across a bizarre problem with kernel.request event listeners firing multiple times when embedded Twig controllers are used.
In my custom event listener I have an event listener that sends a redirect response if a certain condition exists (an expired password in this case). In order to prevent a redirect loop I checked if we were already on the page:
if ($event->getRequest()->get('_route') != 'user_change_password') {
$response = new RedirectResponse($this->router->generate('user_change_password'));
$event->setResponse($response);
}
But that didn't stop the redirect loops. Until I added logging, I had no idea that an embedded controller would fire the kernel.request event (it's obvious in hindsight, since these embedded controllers work by sending a "sub-request"). I have a single embedded controller in by base twig template that checks for any alert messages and displays them.
Given the above, how can I
be able to insert dynamic content into a base template (that all other templates extend), AND
not have kernel.request event listeners fire multiple times.
Even though Symfony suggests inserting that dynamic content into base templates using embedded controllers, is this considered bad practice?
Would it be better to create a Twig extension to solve this problem? From what I've seen, Twig extensions are generally only used for simple stuff, like the price example in the cookbook, though I can't see why it wouldn't work for more complex, database-connected things. I'm just not sure on how to do that.
Examples are appreciated.
Possible related?: Symfony Controller executed multiple Time
You could do the redirection only if the event listener is executed for the master request:
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...
if ($event->isMasterRequest() && $event->getRequest()->get('_route') != 'user_change_password') {
$response = new RedirectResponse($this->router->generate('user_change_password'));
$event->setResponse($response);
}
If you are still bound to Symfony 2.3, you can use the getRequestType() method compare its return value with the MASTER_REQUEST constant from the HttpKernelInterface (that's what isMasterRequest() does internally):
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType() && $event->getRequest()->get('_route') != 'user_change_password') {
$response = new RedirectResponse($this->router->generate('user_change_password'));
$event->setResponse($response);
}
Related
I am working in Vue and also i use VueRouter, VueX and VueWebsocket. My App has component called App which holds all other components inside itself. Also I have websocket event which is set globally like this:
this.$options.sockets.onmessage = (websocket) => { /* sth1 */ }
When it gets any data from websocket, sth1 is called. it works like charm. However deep inside App component is another component, let's call it InputComponent. It may be included in App or not becaue it is single page aplication and some parts do include InputComponent, and some do not. Inside InputComponent there is also:
this.$options.sockets.onmessage = (websocket) => { /* sth2 */ }
And of course it overwrites function on message so sth1 will never be executed if InputComponent is nested by App component. It is quite obvious. However if i remove (in next SPA page), and InputComponent disappears i still have my onmessage event overwritten which i would like to have in original version.
I could ofcourse make some kind of merging functionalities of sth1 and sth2 in App component or InputComponent but it is repeating myself.
Here comes the question - is there a way to return original version of onmessage event without reloading whole App Component? In other words: can i have temporary overwritten function and then come back to its functionalities? Something like extending an eent with new functionalities of sth2.
I hope you get the idea!
K.
The general way to do that would be to use addEventListener and removeEventListener. So in the input component
created() {
this.$options.sockets.addEventListener('message', handleMessage);
},
destroyed() {
this.$options.sockets.removeEventListener('message', handleMessage);
}
Note that this approach doesn't prevent the original handler from also receiving the events. Without knowing more about the app architecture, it's hard to suggest the best way to avoid this behavior, but perhaps you can set a messageHandled flag on the event in the component's handler; then check that flag in the parent.
So I’m looking to make some routes within my super cool can.js application. Aiming for something like this…
#!claims ClaimsController - lists claims
#!claims/:id ClaimController - views a single claim
#!claims/new ClaimController - creates a new claim
#!claims/:id/pdf - do nothing, the ClaimController will handle it
#!admin AdminController - loads my Administrative panel with menu
#!admin/users - do nothing, the AdminController will handle it
#!admin/settings - do nothing, the AdminController will handle it
So how might we do this?
“claims route”: function() { load('ClaimsController'); },
“claims/:id route”: function() { load('ClaimController'); },
“admin”: function() { load(‘AdminController’); },
Cool beans, we’re off. So what if someone sends a link to someone like...
http://myapp#!claims/1/pdf
Nothing happens! Ok, well let’s add the route.
“claims/:id/pdf route”: function() { load('ClaimController'); },
Great. Now that link works. Here, the router’s job is only to load the controller. The controller will recognize that the pdf action is wanted, and show the correct view.
So pretend I’ve loaded up a claim claims/:id and I edit one or two things. Then I click the Print Preview button to view the PDF and change my route to claims/:id/pdf.
What should happen… the Claim Controller is watching the route and shows the pdf view.
What actually happens… the router sees the change, matches the claims/:id/pdf route we added, and reloads the Claim Controller, displaying a fresh version of the claim pulled from the server/cache, losing my changes.
To try and define the problem, I need the router to identify when the route changes, what controller the route belongs to, and if the controller is already loaded, ignore it. But this is hard!
claims //
claims/:id // different controllers!
claims/:id //
claims/:id/pdf // same controller!
We could just bind on the "controller" change. So defining routes like can.route(':controller') and binding on :controller.
{can.route} controller
// or
can.route.bind('controller', function() {...})
But clicking on a claim (changing from ClaimsController to ClaimController) won't trigger, as the first token claim is the same in both cases.
Is there a convention I can lean on? Should I be specifying every single route in the app and checking if the controller is loaded? Are my preferred route urls just not working?
The following is how I setup routing in complex CanJS applications. You can see an example of this here.
First, do not use can.Control routes. It's an anti-pattern and will be removed in 3.0 for something like the ideas in this issue.
Instead you setup a routing app module that imports and sets up modules by convention similar to this which is used here.
I will explain how to setup a routing app module in a moment. But first, it's important to understand how can.route is different from how you are probably used to thinking of routing. Its difference makes it difficult to understand at first, but once you get it; you'll hopefully see how powerful and perfect it is for client-side routing.
Instead of thinking of urls, think of can.route's data. What is in can.route.attr(). For example, your URLs seem to have data like:
page - the primary area someone is dealing with
subpage - an optional secondary area within the page
id - the id of a type
For example, admin/users might want can.route.attr() to return:
{page: "admin", subpage: "users"}
And, claims/5 might translate into:
{page: "claims", id: "5"}
When I start building an application, I only use urls that look like #!page=admin&subpage=users and ignore the pretty routing until later. I build an application around state first and foremost.
Once I have the mental picture of the can.route.attr() data that encapsulates my application's state, I build a routing app module that listens to changes in can.route and sets up the right controls or components. Yours might look like:
can.route.bind("change", throttle(function(){
if( can.route.attr("page") == "admin" ) {
load("AdminController")
} else if(can.route.attr("page") === "claims" && can.route.attr("id") {
load("ClaimController")
} else if ( ... ) {
...
} else {
// by convention, load a controller for whatever page is
load(can.capitalize(can.route.attr("page")+"Controller")
}
}) );
Finally, after setting all of that up, I make my pretty routes map to my expected can.route.attr() values:
can.route(":page"); // for #!claims, #!admin
can.route("claims/new", {page: "claims", subpage: "new"});
can.route("claims/:id", {page: "claims"});
can.route("admin/:subpage",{page: "admin"});
By doing it this way, you keep your routes independent of rest of the application. Everything simply listens to changes in can.route's attributes. All your routing rules are maintained in one place.
Problem
When I try to add a block into my transactional email template in the following manner:
{{block type='core/template' area='frontend' template='invent/baskettimer/email_items.phtml' record=$record}}
I get the following error, and nothing is rendered.
CRIT (2): Not valid template file:frontend/base/default/template/invent/baskettimer/email_items.phtml
Troubleshooting
Normally this warning points to a typo which is breaking the inheritance but I have quadruple checked and this should work.
I then copied the file into the base and did a test, it rendered correctly.
Create a custom block and set the template, same error is displayed.
Theory
To me it seems template inheritance is broken / not implemented for emails, so it is always looking in base, I cannot put my templates there so I am not sure how to call them.
Possible workarounds
Render the block to html then send it to as a variable to render, problem with this is I am sending the emails from Model level and am having a hard time pre rendering the block, even with a helper.
Render the data using a method, don't really want to do this as it is message / against MVC.
Any help is much appreciated.
Bounty update
So I have traced down the problem, it is probably an easy solution now.
The problem is that I am calling it from a cronjob does not have the correct store view, it is fairly easy to replicate similar situation by using a shell script, then changing the _appCode to null.
<?php
require_once 'abstract.php';
class Mage_Shell_Shell extends Mage_Shell_Abstract
{
protected $_appCode = ''; // works - remove to not work
/**
* Run script
*
*/
public function run()
{
Mage::getModel('invent_baskettimer/email')->sendJob();
}
}
$shell = new Mage_Shell_Shell();
$shell->run();
So basically the question has become:
How do I call a block->toHtml() regardless of store view?
There is not way of setting a cronjob to be like that. Lucky magento lets you emulate your store views, see the following to emulate the default store.
public function cronjob()
{
$iDefaultStoreId = Mage::app()
->getWebsite()
->getDefaultGroup()
->getDefaultStoreId();
$appEmulation = Mage::getSingleton('core/app_emulation');
$initialEnvironmentInfo = $appEmulation->startEnvironmentEmulation($iDefaultStoreId);
.. do your stuff here ..
$appEmulation->stopEnvironmentEmulation($initialEnvironmentInfo);
}
For more info see: http://inchoo.net/ecommerce/magento/emulate-store-in-magento/
I've started bumping into errors when my session has been lost, or upon rebuilding my project, as my forms authentication cookie still lives on.
In WebForms I'd use the masterpage associated with pages which require login to simply check for the session.
How would I do this in one location in MVC ? I'd hate having to check for session state in every action in my controllers.
On the other hand I can't just apply a global filter either, since not all Controllers require session state.
Would it perhaps be possible in my layout view ? It's the only thing the pages which require session have in common.
One thing you could do is to sub-class the controllers that do need session state. This way you could create a filter on just this base controller. This would allow you to do it all in one place. Plus, as you pointed out, a global filter won't help you here since the logic does not apply to every controller.
add it to session start. if a session loss happens it needs to trigger a session start too. you can handle it in there as follows:
protected void Session_Start(object src, EventArgs e)
{
if (Context.Session != null)
{
if (Context.Session.IsNewSession)
{
string sCookieHeader = Request.Headers["Cookie"];
if ((null != sCookieHeader) && (sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
{
// how to simulate it ???
// RedirectToAction(“ActionName”, “ControllerName”, route values);
Response.Redirect("/Home/TestAction");
}
}
}
}
I agree with what Steve has mentioned, but I suggest to use Global Filters instead of creating a base class for all your controllers. The reason for this is everytime you create a new controller, you should always remember to derive from the base controller or you may experience random behaviours in your application that may take you hours of debugging. This is especially important when you stop development for a while and then get back to it.
Also, another reason is the "Favour composition over inheritance" principle.
In my Symfony project I'm trying to provide a "save as template"-button for an embedded Form. The embedded form contains dynamic embedded forms itself.
Example:
The user should be able to save the template without saving the whole form. So i'm going to use AJAX to achieve this (as I already did, for the dynamic add-behavior).
The actual problem is that Symfony names the form in dependence on the parent form, e.g.
<input name="Project[Workflow][1][name]" />
But the template isn't related to "Project" at all. On the other hand, this naming format is required later, when saving the whole form.
Sending the whole form to the server might be a solution, but I think it's a bad practice / overkill / waste of bandwidth.
Is there a common way how to do this?
If not, do you have a basic approach in mind?
Regards,
Uli
symfony sfForms take 2 arrays on the bind method, you don't really need to take them from the request.
since you have several WorkflowForms there is a loop involved!
$formData = $request->getParameter('Project'); // you could do Project['Workflow'] except for symfony 1.4
foreach ($formData['Workflow'] as $embeddedData)
{
$formFiles = $request->getFiles('Project');
$embeddedFiles = $formFiles['Workflow'];
$form = new WorkflowForm();
$form->bind($embeddedData, $embeddedFiles);
if ($form->isValid())
{
// do your thing
// ...
$form->save();
}
}
then you process each form as you would usually do on // do your thing