zf3 onRoute event listener - event-listener

I have a piece of code in my model:
public function init(ModuleManager $manager)
{
// Get event manager.
$eventManager = $manager->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
// Register the event listener method.
$sharedEventManager->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, [$this, 'myFunc'], 100);
}
// Event listener method.
public function myFunc(MvcEvent $event)
{
echo 'it works!';
exit;
}
The listener is invoked.
Although if I change event type to MvcEvent::EVENT_ROUTE the listener is not invoked any more. How to solve it?

So, apparently object Zend\EventManager\EventManager calls method triggerListeners for event 'dispatch' twice. Once with identifiers set as:
Array
(
[0] => Zend\Mvc\Application
)
and Second with identifiers set as:
Array
(
[0] => Zend\Mvc\Controller\AbstractController
[1] => Application\Controller\IndexController
[2] => Application
[3] => Zend\Stdlib\DispatchableInterface
[4] => Zend\EventManager\EventManagerAwareInterface
[5] => Zend\EventManager\EventsCapableInterface
[6] => Zend\Mvc\InjectApplicationEventInterface
[7] => Zend\Mvc\Controller\AbstractActionController
)
Because my listeners are defined for Application identifier they are executed during 2nd call.
The problem starts when call is made for 'route' event. It seems like it is called only once, just for this set of identifiers:
Array
(
[0] => Zend\Mvc\Application
)
Then later, the route listeners are not executed, because their identifier is just 'Application'. To solve it is just enough to attach them with Zend\Mvc\Application identifier:
$sharedEventManager->attach('Zend\Mvc\Application', MvcEvent::EVENT_ROUTE, [$this, 'myFunc'], 100);

Related

Laminas / ZF3: Add manually Error to a field

is it possible to add manually an Error Message to a Field after Field Validation and Input Filter ?
I would need it in case the Username and Password is wrong, to mark these Fields / Display the Error Messages.
obviously in ZF/ZF2 it was possible with $form->getElement('password')->addErrorMessage('The Entered Password is not Correct'); - but this doesnt work anymore in ZF3/Laminas
Without knowing how you do your validation (there are a few methods, actually), the cleanest solution is to set the error message while creating the inputFilter (and not to set it to the element after it has been added to the form).
Keep in mind that form configuration (elements, hydrators, filters, validators, messages) should be set on form creation and not in its usage.
Here the form is extended (with its inputfilter), as shown in the documentation:
use Laminas\Form\Form;
use Laminas\Form\Element;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Validator\NotEmpty;
class Password extends Form implements InputFilterProviderInterface {
public function __construct($name = null, $options = []) {
parent::__construct($name, $options);
}
public function init() {
parent::init();
$this->add([
'name' => 'password',
'type' => Element\Password::class,
'options' => [
'label' => 'Password',
]
]);
}
public function getInputFilterSpecification() {
$inputFilter[] = [
'name' => 'password',
'required' => true,
'validators' => [
[
'name' => NotEmpty::class,
'options' => [
// Here you define your custom messages
'messages' => [
// You must specify which validator error messageyou are overriding
NotEmpty::IS_EMPTY => 'Hey, you forgot to type your password!'
]
]
]
]
];
return $inputFilter;
}
}
There are other way to create the form, but the solution is the same.
I also suggest you to take a look at the laminas-validator's documentation, you'll find a lot of useful informations
The Laminas\Form\Element class has a method named setMessages() which expects an array as parameter, for example
$form->get('password')
->setMessages(['The Entered Password is not Correct']);
Note that this will clear all error messages your element may already have. If you want to add your messages as in the old addErrorMessage() method you can do like so:
$myMessages = [
'The Entered Password is not Correct',
'..maybe a 2nd custom message'
];
$allMessages = array_merge(
$form->get('password')->getMessages(),
$myMessages);
$form
->get('password')
->setMessages($allMessages);
You can also use the error-template-name Laminas uses for its error messages as key in your messages-array to override a specific error message:
$myMessages = [
'notSame' => 'The Entered Password is not Correct'
];

Yii2 rest: checkAccess on restAction

After tackling this other question we would now like to check if the authenticated user can view, update or delete an existing record. Since checkAccess() is called by default in all restActions the following seemed the most logic thing to try:
public function checkAccess($action, $model = null, $params = []) {
if(in_array($action, ['view', 'update', 'delete'])) {
if(Yii::$app->user->identity->customer->id === null
|| $model->customer_id !== Yii::$app->user->identity->customer->id) {
throw new \yii\web\ForbiddenHttpException('You can\'t '.$action.' this item.');
}
}
}
But the API seems to ignore this function. We added this function in our controller. The actions (view, update and delete) are the default restActions.
Our BaseController sets actions like this:
...
'view' => [
'class' => 'api\common\components\actions\ViewAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->viewScenario,
],
...
Are we forgetting something?
Just add the following inside your custom action before executing any other code as it was done in the default view action (see source code here):
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id, $model);
}
note: $this->checkAccess is defined in parent yii\rest\Action so your custom ActionView class need to either extend yii\rest\Action or redefine the variable public $checkAccess;
We obviously should have seen that the viewAction is not the default but an altered api\common\components\actions\ViewAction ... Not sure how we missed that...

Fuel PHP - to_array() method and multiple belongs_to relationships and eager loading

I am attempting to migrate some legacy data models/schemas to a fuel API, and have run into an odd issue with the to_array() method on a model that has two $_belongs_to properties.
When I load the model without the to_array() method, I properly receive both related items with eager loading, but as soon as I pass them through this function to convert the data to make it digestable by the new API, it will strip out the second $_belongs_to property. If I re-order the props in the $belongs_to array, it will show whichever item is first in the array.
My question is, how can I convert this data to an array without losing the second relationship?
Here are some cleaned up examples for ease of reference:
Transaction Model:
protected static $_belongs_to = array(
'benefactor' => array(
'key_from' => 'from_user_id',
'model_to' => 'Model\\Legacy\\User',
'key_to' => 'id',
),
'user' => array(
'key_from' => 'user_id',
'model_to' => 'Model\\Legacy\\User',
'key_to' => 'id',
),
);
Transaction Controller:
$result = array();
$id = $this->param('id');
if (!empty($id)) {
$transaction = Transaction::find($id, array('related' => array('user', 'benefactor',)));
if (!empty($transaction)) {
// Works -- both benefactor and user are returned
$result['transaction_works'] = $transaction;
// Does not work -- only the benefactor is returned
$result['transaction_doesnt_work'] = $transaction->to_array();
}
}
return $this->response($result);
For any googlers looking for help on this issue, I was seemingly able to return all relationships by simply executing the to_array() method before setting the return/results variable:
$result = array();
$id = $this->param('id');
if (!empty($id)) {
$transaction = Transaction::find($id, array('related' => array('user', 'benefactor',)));
if (!empty($transaction)) {
$transaction->to_array();
$result['transaction_works'] = $transaction;
}
}
return $this->response($result);
Good luck!

zf2 restful not reach update method

I made a restful controller that if I send the id the get method receives it. But when I update a form I expect the update method to process but I cant get to the right config for this and after 1 day with this issue I decided to right it down here.
Here the code involved
route in module config:
'activities' => array(
'type' => 'segment',
'options' => array(
'route' => '/activities[/:id][/:action][.:formatter]',
'defaults' => array(
'controller' => 'activities'
),
'constraints' => array(
'formatter' => '[a-zA-Z0-9_-]*',
'id' => '[0-9_-]*'
),
),
),
Head of controller:
namespace Clock\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\Mvc\MvcEvent;
use Zend\View\Model\ViewModel;
use Zend\Form\Annotation\AnnotationBuilder;
use Zend\Form;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Clock\Entity\Activity;
use \Clock\Entity\Project;
Wich contains the get method:
public function get($id)
{
$entity = $this->getRepository()->find($id);
$form = $this->buildForm(new Activity());
#$form->setAttribute('action', $this->url()->fromRoute("activities", array('action' => 'update')));
$form->setAttribute('action', "/activities/$id/update");
$form->bind($entity);
return array(
"activities" => $entity,
"form" => $form
);
}
That feeds this view:
<h3>Edit activity</h3>
<div>
<?php echo $this->form()->openTag($form);?>
<?php echo $this->formSelect($form->get("project"));?><br>
<?php echo $this->formInput($form->get("duration"));?><br>
<?php echo $this->formInput($form->get("description"));?><br>
<input type="submit" value="save changes" />
<?php echo $this->form()->closeTag($form);?>
</div>
After sending it, I expect update method in activities to take control, but I get:
A 404 error occurred
Page not found.
The requested controller was unable to dispatch the request.
Controller:
activities
EDIT:#DrBeza
This is what i get, that i think (not a master in routes) is right:
Zend\Mvc\Router\Http\RouteMatch Object
(
[length:protected] => 21
[params:protected] => Array
(
[controller] => activities
[id] => 30
[action] => update
)
[matchedRouteName:protected] => activities
)
--
That's it.
Any help?
Quick Fix
The RouteMatch object tries to dispatch ActivitiesController::updateAction but you have defined ActivitiesController::update
That's due to you using a Restful Controller. the Controller::update-Method is specifically tied to PUT-Requests. You need to define an extra method to handle updates via POST-Requests.
I suggest you define ActivitiesController::updateAction, make clear in the docblock it is meant to handle POST-Update requests and refactor both ::updateAction and ::update to share as much common helper-methods as possible for a fast solution.
Common URI Structur information
As a nice information to have when you start developing RESTful applications/APIs:
The ruby community suggests the following url-structure for your resources:
# These are restful
/resource GET (lists) | POST (creates)
/resource/:id PUT (updates) | DELETE (deletes)
# these are just helpers, not restful, and may accept POST too.
/resource/new GET (shows the create-form), POST
/resource/:id/edit GET (shows the update-form), POST
Detailed Problem Analysis
A restful update will be sent by an consumer via PUT, but browsers sending HTML-forms may only send GET or POST requests. You should never use GET to create something. So you have to use POST in a forms-context.
Looking at the problem from an architectural perspective a multitude of possibilities emerge, depending on how big your application is.
For a small application, tight integration (formhandling and API handling in the controller) apply best.
Getting bigger you may want to split up API-Controllers (only restful actions) from Helper-Controllers (form, website handling) which talk to your API-Controllers
Being big (multitude of API-Users) you will want to have dedicated API Servers and dedicated Website Servers (independent applications!). In this case your website will consume the API serverside (thats what twitter is doing). API Servers and Website Servers still may share libraries (for filtering, utilities).
Code Sample
As an educational example I made an gist to show how such a controller could look like in principle. This controller is a) untested b) not production ready and c) only marginally configurable.
For your special interest here two excerpts about updating:
/* the restful method, defined in AbstractRestfulController */
public function update($id, $data)
{
$response = $this->getResponse();
if ( ! $this->getService()->has($id) )
{
return $this->notFoundAction();
}
$form = $this->getEditForm();
$form->setData($data);
if ( ! $form->isValid() )
{
$response->setStatusCode(self::FORM_INVALID_STATUSCODE);
return [ 'errors' => $form->getMessages() ];
}
$data = $form->getData(); // you want the filtered & validated data from the form, not the raw data from the request.
$status = $this->getService()->update($id, $data);
if ( ! $status )
{
$response->setStatusCode(self::SERVERSIDE_ERROR_STATUSCODE);
return [ 'errors' => [self::SERVERSIDE_ERROR_MESSAGE] ];
}
// if everything went smooth, we just return the new representation of the entity.
return $this->get($id);
}
and the editAction which satisfies browser-requests:
public function editAction()
{
/*
* basically the same as the newAction
* differences:
* - first fetch the data from the service
* - prepopulate the form
*/
$id = $this->params('id', false);
$dataExists = $this->getService()->has($id);
if ( ! $dataExists )
{
$this->flashMessenger()->addErrorMessage("No entity with {$id} is known");
return $this->notFoundAction();
}
$request = $this->getRequest();
$form = $this->getEditForm();
$data = $this->getService()->get($id);
if ( ! $request->isPost() )
{
$form->populateValues($data);
return ['form' => $form];
}
$this->update($id, $request->getPost()->toArray());
$response = $this->getResponse();
if ( ! $response->isSuccess() )
{
return [ 'form' => $form ];
}
$this->flashMessenger()->addSuccessMessage('Entity changed successfully');
return $this->redirect()->toRoute($this->routeIdentifiers['entity-changed']);
}
That error message suggests the dispatch process is unable to find the requested controller action and therefore using notFoundAction().
I would check the route matched and make sure the values are as expected. You can do this by adding the following into your module's onBootstrap() method:
$e->getApplication()->getEventManager()->attach('route', function($event) {
var_dump($event->getRouteMatch());
exit;
});

Get all parameters after action in Zend?

When I call a router like below in Zend:
coupon/index/search/cat/1/page/1/x/111/y/222
And inside the controller when I get $this->_params, I get an array:
array(
'module' => 'coupon',
'controller' => 'index',
'action' => 'search',
'cat' => '1',
'page' => '1',
'x' => '111',
'y' => '222'
)
But I want to get only:
array(
'cat' => '1',
'page' => '1',
'x' => '111',
'y' => '222'
)
Could you please tell me a way to get the all the params just after the action?
IMHO this is more elegant and includes changes in action, controller and method keys.
$request = $this->getRequest();
$diffArray = array(
$request->getActionKey(),
$request->getControllerKey(),
$request->getModuleKey()
);
$params = array_diff_key(
$request->getUserParams(),
array_flip($diffArray)
);
As far as I know, you will always get the controller, action and module in the params list as it is part of the default. You could do something like this to remove the three from the array you get:
$url_params = $this->getRequest()->getUserParams();
if(isset($url_params['controller']))
unset($url_params['controller']);
if(isset($url_params['action']))
unset($url_params['action']);
if (isset($url_params['module']))
unset($url_params['module']);
Alternatively as you don't want to be doing that every time you need the list, create a helper to do it for you, something like this:
class Helper_Myparams extends Zend_Controller_Action_Helper_Abstract
{
public $params;
public function __construct()
{
$request = Zend_Controller_Front::getInstance()->getRequest();
$this->params = $request->getParams();
}
public function myparams()
{
if(isset($this->params['controller']))
unset($this->params['controller']);
if(isset($this->params['action']))
unset($this->params['action']);
if (isset($this->params['module']))
unset($this->params['module']);
return $this->params;
}
public function direct()
{
return $this->myparams();
}
}
And you can simply call this from your controller to get the list:
$this->_helper->myparams();
So for example using the url:
http://127.0.0.1/testing/urls/cat/1/page/1/x/111/y/222
And the code:
echo "<pre>";
print_r($this->_helper->myparams());
echo "</pre>";
I get the following array printed:
Array
(
[cat] => 1
[page] => 1
[x] => 111
[y] => 222
)
How about this?
In controller:
$params = $this->getRequest()->getParams();
unset($params['module'];
unset($params['controller'];
unset($params['action'];
Pretty clunky; might need some isset() checks to avoid warnings; could jam this segment into its own method or helper. But it would do the job, right?