ZF2: How to propagate Controller return to layout template? - viewmodel

I'm returning data from Controller like this:
/**
* Password request sent
*
* #return array
*/
public function passwordRequestSentAction ()
{
return array(
'foo' => $this->bar,
);
}
But $this->foo is null within layout.phtml even though its correct within controller/passwordRequestSent.phtml
I had to create postDispatch method in my abstract controller and link to it in attachDefaultListeners() and do this in postDispatch:
$e->getViewModel()->setVariables($e->getResult()->getVariables());
Is that really the way to go? I simply want to share all my variables across, no matter if its layout or page template.

You can access the layout-template by calling $this->layout():
class MyController extends AbstractActionController
{
public function myAction()
{
$layout = $this->layout();
// Returns the ViewModel of the Layout
}
}
For more information & samples check the manual's examples.
However in most cases I'd suggest writing a viewhelper for these tasks - especially for navigation/... This encapsulates the controller's logic from viewing tasks like I want the navigation displayed here or Show me the user's login box. Same goes for almost every type of status messages.

Related

Drupal 8 - Add custom cache context

I have the following situation: I want to hide or show some local Tasks (Tabs) based on a field on the current user. Therefore I have implemented a hook_menu_local_tasks_alter() in my_module/my_module.module:
function my_module_menu_local_tasks_alter(&$data, $route_name, \Drupal\Core\Cache\RefinableCacheableDependencyInterface &$cacheability) {
... some logic ...
if ($user->get('field_my_field')->getValue() === 'some value')
unset($data['tabs'][0]['unwanted_tab_0']);
unset($data['tabs'][0]['unwanted_tab_1']);
... some logic ...
}
This works fine but I need to clear the caches if the value of field_my_field changes.
So I found that I need to implement a Cache Context like this in my my_module_menu_local_tasks_alter:
$cacheability
->addCacheTags([
'user.available_regions',
]);
I have defined my Cache Context like this:
my_module/my_module.services.yml:
services:
cache_context.user.available_regions:
class: Drupal\my_module\CacheContext\AvailableRegions
arguments: ['#current_user']
tags:
- { name: cache.context }
my_module/src/CacheCotext/AvailableRegions.php:
<?php
namespace Drupal\content_sharing\CacheContext;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
use Drupal\Core\Session\AccountProxyInterface;
/**
* Class AvailableRegions.
*/
class AvailableRegions implements CacheContextInterface {
protected $currentUser;
/**
* Constructs a new DefaultCacheContext object.
*/
public function __construct(AccountProxyInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* {#inheritdoc}
*/
public static function getLabel() {
return t('Available sub pages.');
}
/**
* {#inheritdoc}
*/
public function getContext() {
// Actual logic of context variation will lie here.
$field_published_sites = $this->get('field_published_sites')->getValue();
$sites = [];
foreach ($field_published_sites as $site) {
$sites[] = $site['target_id'];
}
return implode('|', $sites);
}
/**
* {#inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}
But every time I change the value of my field field_my_field I still need to clear the caches, so the Context is not working. Could anybody point me in the right direction how to get this solved or how to debug such kind of thigs?
Instead of providing a custom cache context, you should be able to use the default cacheability provided by core. I believe the issue is not so much the creation of the cacheable metadata, its seems that your hook_menu_local_tasks_alter is altering content that doesn't know it now relies on the user. So I believe you need 2 things:
General cache contexts that says 'this menu content now relies on the user', eg. user cache context.
Additional use of cache tag for a specific user to say: 'once this is cached, when this user entity changes, go regenerate the local tasks for this user'.
Note that HOOK_menu_local_tasks_alter provides a helper for cacheability, the third param of $cacheability. Drupal core also provides a mechanism here that allows us to say 'this piece of cache data relies on this other piece of cache data'.
Thus you should be able to do something like:
function my_module_menu_local_tasks_alter(&$data, $route_name, RefinableCacheableDependencyInterface &$cacheability) {
... some logic ...
// We are going to alter content by user.
$cacheability->addCacheableDependency($user);
// Note if you still really need your custom context, you could add it.
// Also note that any user.* contexts should already be covered above.
$cacheability->addCacheContexts(['some_custom_contexts']);
if ($user->get('field_my_field')->getValue() === 'some value')
unset($data['tabs'][0]['unwanted_tab_0']);
unset($data['tabs'][0]['unwanted_tab_1']);
... some logic ...
}

How to add an Action to Account Controller in Shopware

How to add a custom action to an existing Controller in Shopware?
Examples (url structure):
/account/bonus
/account/custom
/account/...
Usually it's easier and cleaner to create a new controller for that purpose, but in some cases it's necessary.
You should not replace the "account" controller.
You can define you own action for existing controller with following:
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_Frontend_Account_MyBonus' => 'onAccountMyBonus',
];
}
and then
public function onAccountMyBonus(\Enlight_Event_EventArgs $args)
{
$args->setProcessed(true);
.....
your code here
}
Spoiler: Replace the controller
There is no cleaner way than to replace the whole controller and extend it's functionality, so it's nearly as clean as Shopware's hooks.
Guide
Add a new Subscriber to your Plugin
class AccountSubscriber implements SubscriberInterface
{
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
'Enlight_Controller_Dispatcher_ControllerPath_Frontend_Account' => 'getAccountController'
);
}
/**
* #return string
*/
public function getAccountController()
{
return $this->getPath() . '/Controllers/Frontend/AccountExtended.php';
}
/**
* #return string
*/
public function getPath()
{
$plugin = Shopware()->Container()->get('kernel')->getPlugins()['AcmeYourPlugin'];
return $plugin->getPath();
}
}
Downsides
Unfortunately some controller have private methods which impact the logic. Like the Account Controller. So it's not always possible to simply extend the controller.
In the end, try to add a new controller with a new route.
It's easier, and cleaner.
There is a cleaner way than replacing the whole Controller.
It is also not recommended to replace a whole controller due to the lack of update compatibility.
In the worst case something like that could kill the whole website.
A while ago I created a thread in the shopware forum (german) discussing the exact same issue. I wanted to extend an existing finishAction() in the checkout Controller.
public function onPostDispatchCheckout(\Enlight_Controller_ActionEventArgs $args)
{
/** #var \Enlight_Controller_Action $controller */
$controller = $args->getSubject();
/** #var \Enlight_Controller_Request_Request $request */
$request = $controller->Request();
if ($request->getActionName() !== 'finish') {
return;
}
// do your stuff here
}
So even though it is not the exact same issue you have, the procedure is quite the same.
First off you subscribe to the controller (in my case the PostDispatchCheckout Controller) afterwards you edit the controller in your Bootstrap.php
To make sure, that it just alters a specific action you have to use the if-construction so your code gets just triggered on the wished action [in my case the finishAction()].
I hope this helps. What wonders me though is why you have to add a new action to an already existing controller. I can think of no situation where something like that is more practicable than creating a complete new custom controller.
Kind regards,
Max

How to stop rendering Phalcon template when Ajax is used?

I use a lot of Ajax in my Phalcon project, and each request is handled by a specific Controller/Action where I disabled the template rendering (only the view is rendered).
How can I disable template globally, if calls are made with Ajax?
I found the answer :)
abstract class ControllerBase extends Controller
{
/**
* Called in each Controller/Action request
*/
public function initialize(){
if($this->request->isAjax()){
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
}
...
The available render levels are:
Class Constant Description Order
LEVEL_NO_RENDER Indicates to avoid generating any kind of presentation.
LEVEL_ACTION_VIEW Generates the presentation to the view associated to the action. 1
LEVEL_BEFORE_TEMPLATE Generates presentation templates prior to the controller layout. 2
LEVEL_LAYOUT Generates the presentation to the controller layout. 3
LEVEL_AFTER_TEMPLATE Generates the presentation to the templates after the controller layout. 4
LEVEL_MAIN_LAYOUT Generates the presentation to the main layout. File views/index.phtml 5
For more information see: control-rendering-levels
For a specific action you can use either of these implementations:
public function saveAction()
{
$this->view->disable();
// Operations go here.....
$this->view->pick('some/view/to/display');
}
public function resetAction()
{
$this->view->disable();
// Operations go here.....
echo 'reset action'
}
public function cancelAction()
{
$this->view->disable();
// Operations go here.....
$response = new \Phalcon\Http\Response();
$response->setStatusCode(200, 'OK');
$response->setContentType('application/json', 'UTF-8');
$response->setJsonContent('some content goes here', JSON_UNESCAPED_SLASHES);
return $response->send();
}

Is there any decent way to Decorate models returned from a Magento `[model]_load_after`event?

I'm trying to overwrite some methods in models, and I'm on a mission to avoid overwrites and rewrites of models for maximum compatibility with other modules.
I figured the best way would be to simply decorate models after they are loaded from Magento, however as far as I can tell because of the way the observer pattern in Magento is written it's impossible to accomplish this. ( As Magento always returns the reference to $this ), and the lack of interfaces might also cause trouble later down the road? See this partial of Mage/Core/Model/Abstract.php
/**
* Processing object after load data
*
* #return Mage_Core_Model_Abstract
*/
protected function _afterLoad()
{
Mage::dispatchEvent('model_load_after', array('object'=>$this));
Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData());
return $this;
}
My question boils down to the title, is there a decent way of accomplishing this?, or am I simply stuck with rewrites :(?
The path I would like to take is;
On event [model]_load_after
return new Decorator($event->getObject())
Where the decorator class in my case would be something like;
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model); // sets $this->model on parent class, see below
}
// overwrite the getIncrementId method
public function getIncrementId()
{
return '12345';
}
// partial of parent class
public function __call($method, array $args)
{
return call_user_func_array(array($this->model, $method), $args);
}
And just some pseudo-code for extra clarification;
$model = Mage::getModel('sales/order_invoice')->load(1);
echo get_class($model);
Namespace_Decorator **INSTEAD OF** Mage_Sales_Model_...
echo $model->getIncrementId();
'12345' **INSTEAD OF** '1000001' ( or whatever the format might be )
Thanks for your time reading / commenting, I really hope there actually is a way to accomplish this in a clean fashion without making use of code overrides or rewrites of models.
Edit: extra clarification
Basically what I would like is to return an instance of the Decorator in a few cases, the sales_invoice being one of them and customer the other. So when any load() call is made on these models, it will always return the instance of the Decorator instead of the Model. Only method calls that the decorator overrides would be returned, and any other method calls would "proxied" through __call to the decorated object.
I'm not sure if I got your question right but here goes.
I think you can use the event [model]_load_after and simply do this:
$object = $event->getObject();
$object->setIncrementId('12345');
Or if you want to use a decorator class make it look like this:
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model);
$model->setIncrementId($this->getIncrementId());
}
public function getIncrementId()
{
return '12345';
}
I know that this is not exactly a decorator pattern but it should work.
I know that when adding a new method to the 'decorator' class you need to add it to attach data to the main model.
This is just my idea. I haven't got an other.
[EDIT]
You can try to rewrite the load method on the object to make it return what you need. But I wouldn't go that way. You can end up screwing a lot of other things.
I don't think there is an other way to do it because load always returns the current object no mater what you do in the events dispatched in the method. see Mage_Core_Model_Abstract::load()
public function load($id, $field=null)
{
$this->_beforeLoad($id, $field);
$this->_getResource()->load($this, $id, $field);
$this->_afterLoad();
$this->setOrigData();
$this->_hasDataChanges = false;
return $this;
}
By making it return new Decorator($this), you might achieve what you need, but just make sure that when calling $model->doSomething() and doSomething() is not a method in your decorator you still end up calling the original method on the model.

Codeigniter models loaded in controller overwritten by models loaded in models

I'm having Codeigniter object scope confusion.
Say I load a model in a controller:
$this->load->model('A');
$this->A->loadUser(123); // loads user with ID 123
// output of $this->A now shows user 123
$this->load->model('B');
$this->B->examineUser ();
// output of $this->A now shows user 345
class B extends Model
{
public function examineUser ()
{
$this->load->model('A');
$this->A->loadUser(345); // loads user with ID 345
}
}
I would have thought that $this->A would be different from $this->B->A but they are not. What is the best solution to this issue? It appears the ->load->model('A') in the examineUser () method does nothing because it was loaded in the controller. Then the call to loadUser () inside that method overwrites the stored properties of $this->A. This seems like a bugfest waiting to happen. If I needed global models, I would have use static classes. What I wanted was something scoped pretty much locally to the model object I was in.
Is there a way I can accomplish this but not go way outside of CI's normal way of operating?
Followup/related:
Where do most people put there "->load->model" calls? All at the beginning of a controller action? I figured it would be easier -- though perhaps not excellent programming from a dependency injection perspective -- to load them in the model itself (construct or each method).
Whenever you use the Loader Class ($this->load->), it will load the object into the main CI object. The CI object is the one you keep referring to as $this->. What you've done is load model A twice into the CI object.
Essentially, all object loaded using the Loader class goes into a single global scope. If you need two of the same type, give them different names, as per $this->load->model('A','C'). I don't know of any way around it unless you revert to using bog-standard PHP.
In my team's code, we generally load the models in the controller's constructor, then load the data to send to the view in the function, often _remap().
This is not how the loader works sadly. CodeIgniter implements a singleton pattern, which will check to see if the class is included, instantiated and set to $this->A then will be ignored if loaded again. Even if you are inside a model, $this->A will be referenced to the super-instance via the __get() in class Model. Alis it, or just do:
class B extends Model
{
public function examineUser ()
{
$user = new A;
$user->loadUser(345); // loads user with ID 345
}
}
Here's what I've decided to do, please comment if you have advice:
I've extended the CI Loader class:
<?php
class SSR_Loader extends CI_Loader
{
function __construct()
{
parent::__construct ();
}
/**
* Model Retriever
*
* Written by handerson#executiveboard.com to create and return a model instead of putting it into global $this
*
* Based on original 2.0.2 CI_Loader::model ()
*
*/
function get_model($model)
{
if (empty ($model))
{
return;
}
$name = basename ($model);
if (!in_array($name, $this->_ci_models, TRUE))
{
$this->model ($model);
}
$name = ucfirst($name);
return new $name ();
}
}
Do any CI guru's see a problem with that before I invest time in changing my code a bit to accept the return obj, ala:
// in a controller:
public function test ($user_id=null)
{
$this->_logged_in_user = $this->load->get_model ('/db/users');
$this->_viewed_user = $this->load->get_model ('/db/users');
$this->_logged_in_user->load($this->session->userdata ('user.id'));
$this->_viewed_user->load($user_id);
}
I could also do private $_logged_in_user to make it available in the controller but positively force it to be limited to just the current controller and not spill anywhere else, or I could just do $_logged_in_user = $this->load->get_model ('/db/users'); and limit it to just the current method, which is probably what I'll do more often.
This seems like a pretty straightforward way to "fix" this issue (I say "fix" b/c it's not really a bug, just a way of doing things that I think is a bad idea). Anyone see any flaws?

Resources