I am using ZF2
I am want to get the user information who has logged into the system. Basically I am having a create_userid and last_update_userid for every table. I want to populate this with the id of the user who has logged in and performing the operations.
I can pass this as a parameter into my operations from the controller; I would like to get this automatically from the system.
I am sure someone else would have thought about this and performed this.
I did some research and found when a class is created to implement ServiceLocatorAwareInterface we can do all sorts of things with the service locator. Also, the servicelocator is set automatically by the MVC engine.
I created the Table to implement ServiceLocatorAwareInterface and I could get the user id of the logged in user.
I have the code below
namespace UserAdmin\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserTable implements ServiceLocatorAwareInterface {
protected $serviceLocator = null;
protected $loggedUser = 0;
protected $tableGateway;
protected $table = "user";
public function __construct( TableGateway $tableGateway ) {
$this->tableGateway = $tableGateway;
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator() {
return $this->serviceLocator;
}
protected function setLoggedUser( ) {
$serviceLocator = $this->getServiceLocator( );
$authService = $serviceLocator->get( 'AuthService' );
if ( $authService->hasIdentity( ) ) {
$this->loggedUser = $authService->getIdentity( )->user_id;
}
}
...
...
public function saveUser ( User $user ) {
if ( $this->loggedUser == 0 ) {
$this->setLoggedUser( );
}
...
...
$data [ 'last_update_userid' ] = $this->loggedUser;
$data [ 'last_update_timestamp' ] = date ( 'Y-m-d H:i:s' );
if ( is_null( $user_id ) ) {
$data [ 'create_userid' ] = $this->loggedUser;
$data [ 'create_timestamp' ] = date ( 'Y-m-d H:i:s' );
$this->tableGateway->insert( $data );
} else {
if ( $this->getUser ( $user_id ) ) {
$this->tableGateway->update( $data, array ( 'user_id' => $user_id, ) );
} else {
throw new \Exception ( "Could not find row in table $this->table for update" );
}
}
}
}
This takes care of most of my needs. There is just two aspects.
a> I tried calling the setLoggedUser function from the constructor and it does not work. I think the service locator is set after the entire construction. so I am calling the function just before a save, and that is when I need it. Is this a clean implementation or is there another way to do this.
b> An User can register himself on the site; and the userid is an autogenerated sequence so how do I populate the userid to be the same as the new userid.
Two things come to my mind;
1> After insert read the user record and update the record with the userid
2> Not worry about this so much and have a system superadmin id as the id to write users who self register.
This is a problem only for self registrations of users only. Any other table within the system will be inserted or updated only after the user is logged in.
To set the Service Locator in the UserTable Class -
In UserAdmin/Module.php -
'UserAdmin\Model\UserTable' => function($sm) {
$tableGateway = $sm->get('UserTableGateway');
$table = new UserTable($tableGateway);
//This way the serviceLocator is injected to the Table.
$table->setServiceLocator($sm);
return $table;
},
Now, when you will call the setLoggedUser from the controller, the value will be set.
Important:
Rather than having the above private function, the best approach will be to have a Controller Plugin. This plugin will check if any user is logged in or not and if yes then return the user's id.
Related
I have two different ways of loading my controller with it's domain model. I'd be interested in hearing which is better.
First method - traditional.
A controller factory injects the required service into the controller constructor. Within the controller action, the model is loaded based on the request param:
ClientAppointmentsControllerFactory.php
class ClientAppointmentsControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
$serviceManager = $serviceLocator->getServiceLocator();
$controller = new ClientAppointmentsController($serviceManager->get('Service\ClientAppointments'));
return $controller;
}
}
ClientAppointmentsController.php
class ClientAppointmentsController extends AbstractActionController
{
public function __construct(AppointmentFactory $appointmentFactory){
$this->appointmentFactory = $appointmentFactory;
}
public function indexAction() {
$viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
$appointments = $this->appointmentFactory->getClientAppointments($this->params()->fromRoute('clientId'));
$viewModel->setVariables([
'appointments' => $appointments
]);
return $viewModel;
}
}
Second Method - Accessing request/route parameters in factory
This seems a bit cleaner to me, as now the controller has no dependency on the service layer, and just expects (from whatever source) an array of loaded objects to pass to the view. I think this still fits the definition of a factory, since it is creating the controller with it's required dependencies, although is now actively creating them instead of passing this onto the controller to do:
ClientAppointmentsControllerFactory.php
class ClientAppointmentsControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
$getRequestParam = function($param) use($serviceLocator){
$serviceManager = $serviceLocator->getServiceLocator();
$request = $serviceManager->get('Request');
$router = $serviceManager->get('Router');
$match = $router->match($request); // \Zend\Mvc\Router\RouteMatch
$result = $match->getParam($param);
return $result;
};
$serviceManager = $serviceLocator->getServiceLocator();
$clientService = $serviceManager->get('Service\ClientAppointments');
$appointments = $clientService->fetchByClientId($getRequestParam('clientId));
$controller = new ClientAppointmentsController($appointments);
return $controller;
}
}
ClientAppointmentsController.php
class ClientAppointmentsController extends AbstractActionController
{
/**
* #param Array $appointments Array of Appointment objects
*/
public function __construct(Array $appointments){
$this->appointments = $appointments
}
public function indexAction() {
$viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
$viewModel->setVariables([
'appointments' => $this->appointments
]);
return $viewModel;
}
Which is better?
(I also have an idea of a mutable factory floating around.)
IMO, the second is not good at all, because it mixes creation logic with business logic. This means a business logic error will preclude a factory from working.
The first one is better, but not good, because you have got now business logic in the controller.
I would suggest moving business logic into a business model OR to a controller plugin.
I created a service layer AbcService in order to allow modules to access common lines of code. But I need to use database to extract values in my AbcService. So, I need to call getAbcTable() which calls $service->getServiceLocator(). When I try this, I get an error saying 'Call to undefined method getServiceLocator().
public function getAbcTable()
{
if (!$this->abcTable) {
$sm = $this->getServiceLocator();
$this->abcTable = $sm->get('Abc\Model\AbcTable');
}
return $this->abcTable;
}
You're trying to call a method that presumably doesn't exist. If you need AbcTable in your service, you should pass it in as a dependency.
Create a factory for your service that does this, in Module.php:
public function getServiceConfig()
{
return array(
'factories' => array(
'AbcService' => function($sm) {
$abcTable = $sm->get('Abc\Model\AbcTable');
$abcService = new AbcService($abcTable);
return $abcService;
},
);
}
and modify the constructor for your service to accept the table as a paramter:
class AbcService
{
protected $abcTable;
public function __construct($abcTable)
{
$this->abcTable = $abcTable;
}
// etc.
}
then, wherever you need AbcService, either inject it in, or grab it from the service locator:
public function indexAction()
{
$abcService = $this->getServiceLocator()->get('AbcService');
}
and the service will have the table class in it.
I'm quite new to zf2 and I'm experimenting with it. I have a view helper and I need it to access a table object. In my controller I can run:
$this->getServiceLocator();
But ideally I would run this inside my view helper. Unfortunately, I can't seem to access it from within my view helper. I tried passing it through the constructor, configuring a factory method in module.config.php, but when I try that, Zend will no longer pass a tablegateway object into one of my model objects created from a service factory method in the module's Module.php file. This seems to be because it no longer calls the factory method, and opts to run instantiate without any parameters.
I'm not certain I understand why the view factory methods would affect a different set of factory methods with different names.
Can anyone tell me what is wrong with what I'm doing? I can provide more details, but at this point I'm unclear on what details are actually important without supplying the entire codebase.
Thanks.
Crisp does provide a valid answer to your question, but I would suggest to take it one step further. The injection of the service locator makes your view helper tightly coupled to the framework and service locator pattern and vulnerable because every piece of code inside your application can modify every service in the service locator.
There are reasons to inject your dependency directly, so you only depend on your dependencies and you're not implementing this anti-pattern anymore. Let's assume your view helper depends on MyModule\Model\MyTable, then the constructor of your view helper would just look like this:
namespace MyModule;
use MyModule\Model\MyTable;
use Zend\View\Helper\AbstractHelper;
class MyViewHelper extends AbstractHelper
{
protected $table;
public function __construct(MyTable $table)
{
$this->table = $table;
}
}
As you pointed out, you just inject your MyTable now:
namespace MyModule;
class Module
{
public function getViewHelperConfig()
{
return array(
'factories' => array(
'MyViewHelper' => function($sm) {
$sm = $sm->getServiceLocator(); // $sm was the view helper's locator
$table = $sm->get('MyModule_MyTable');
$helper = new MyModule\View\Helper\MyHelper($table);
return $helper;
}
)
);
}
}
Note that inside a view helper factory your service manager is the view helper's service manager and not the "main" one where the table is registered (see also a blog post of I wrote earlier). The $sm->getServiceLocator() solves this for you.
I'm not certain I understand why the view factory methods would affect a different set of factory methods with different names.
It's not, so there is probably a bug in your code. If above does not work, please provide some more details on your service manager configuration so I can update my answer.
One of the great advantages of above approach is you make unit testing really easy for your view helper. You can mock the table gateway and focus on the complete behaviour of your view helper.
use MyModule\View\Helper\MyHelper;
public function testHelperusesTable
{
$mock = $this->getMock('MyModule\Model\MyTable');
$helper = new MyHelper($mock);
// Test your $helper now
}
You can inject the service locator into your view helper from the view helper config in Module.php
// Application/Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'myViewHelper' => function ($serviceManager) {
// Get the service locator
$serviceLocator = $serviceManager->getServiceLocator();
// pass it to your helper
return new \Application\View\Helper\MyViewHelper($serviceLocator);
}
)
);
}
In your view helper
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper,
Zend\ServiceManager\ServiceLocatorInterface as ServiceLocator;
class MyViewHelper extends AbstractHelper
{
protected $serviceLocator;
public function __construct(ServiceLocator $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
}
While working in Zend Framework,we often need custom helper,that make our work easy, In zf1 accessing database model from helper was easy,but i got stuck that how to access database model for any table in Custom View Helper, but as i was needing it i get around through the problem in unprofessional way by creatina new db adapter object in the view, which was never good way, but recently i came to know through very interesting way to access the database adapter in the view helper and there i have to execute any query on any table, it may be not so Zend F2 way, but very simple and short way to solve the issue.
Here is my Model Example...
<?php
namespace Application\Model;
use Zend\Db\TableGateway\TableGateway;
class SlideImageSubTable {
protected $tableGateway;
public $adapter;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
$this->adapter = $this->tableGateway->getAdapter();
}
public function fetchAll() {
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getSlideImageSub($id) {
$id = (int) $id;
$rowset = $this->tableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
public function getImageMenu($id) {
$id = (int) $id;
$rowset = $this->tableGateway->select(array('slide_image_id' => $id));
$rows = array_values(iterator_to_array($rowset));
if (!$rows) {
throw new \Exception("Could not find row $id");
}
return $rows;
}
public function saveSlideImageSub(SlideImageSub $slideImageSub) {
$data = array(
'slide_image_id' => $slideImageSub->slide_image_id,
'title' => $slideImageSub->title,
'description' => $slideImageSub->description
);
$id = (int) $slideImageSub->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getSlideImageSub($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
public function deleteSlideImageSub($id) {
$this->tableGateway->delete(array('id' => $id));
}
}
Just look at the 'public $adapter' public variable. And in the constructor i am going to initialize it by calling $this->tableGateway->getAdapter(); method, getAdapter() is available thorugh gateway object.
Then in my controller action view, i have to assign it to any variable and pass that variable to view page. like this..
public function equitiesAction() {
$image_id = $this->params('id');
$result = $this->getTable('SlideImageSub')->getImageMenu($image_id);
$adapter = $this->table->adapter;
$view = new ViewModel(array(
'result' => $result,
'adapter' => $adapter,
));
return $view;
}
And in the view i pass the 'adapter' object to custom view like this..
<?php echo $this->GetMenuProducts( $this->adapter); ?>
Now in custom view i can use this database adapter object and create select query on any table.
Hope this will help someone, i look around for using database access in custom view helper but the configurations methods provided was not working for me.
Thanks
$this->getView()->getHelperPluginManager()->getServiceLocator();
I need to get the adapter from the form, but still could not.
In my controller I can recover the adapter using the following:
// module/Users/src/Users/Controller/UsersController.php
public function getUsersTable ()
{
if (! $this->usersTable) {
$sm = $this->getServiceLocator();
$this->usersTable = $sm->get('Users\Model\UsersTable');
}
return $this->usersTable;
}
In my module I did so:
// module/Users/Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'Users\Model\UsersTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$uTable = new UsersTable($dbAdapter);
return $uTable;
},
//I need to get this to the list of groups
'Users\Model\GroupsTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$gTable = new GroupsTable($dbAdapter);
return $gTable;
},
),
);
}
Could someone give me an example how to get the adapter to the table from the group form?
I have followed this example to my form users:
http://framework.zend.com/manual/2.0/en/modules/zend.form.collections.html
EDITED from here...
Maybe I expressed myself wrong to ask the question.
What I really need to do is populate a select (Drop Down) with information from my table groups.
So I need to get the services inside my userForm class by ServiceLocatorAwareInterface (see this link) implemented because By default, the Zend Framework MVC registers an initializer That will inject into the ServiceManager instance ServiceLocatorAwareInterface Implementing any class.
After retrieving the values from the table groups and populate the select.
The problem is that of all the ways that I've tried, the getServiceLocator() returns this:
Call to a member function get() on a non-object in
D:\WEBSERVER\htdocs\Zend2Control\module\Users\src\Users\Form\UsersForm.php
on line 46
I just wanted to do this in my UserForm...
namespace Users\Form;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Form\Element;
use Zend\Form\Form;
class UsersForm extends Form implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
public function getServiceLocator ()
{
return $this->serviceLocator;
}
public function setServiceLocator (ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function __construct ($name = null)
{
parent::__construct('users');
$this->setAttribute('method', 'post');
$sm = $this->getServiceLocator();
$groups = $sm->get('Users\Model\GroupsTable')->fetchAll(); // line 46
$select = new Element\Select('groups');
$options = array();
foreach ($groups as $group) {
$options[$group->id] = $group->name;
}
$select->setValueOptions($options);
$this->add($select);
// and more elements here...
The other various answers here generally correct, for ZF < 2.1.
Once 2.1 is out, the framework has a pretty nice solution. This more or less formalizes DrBeza's solution, ie: using an initializer, and then moving any form-bootstrapping into an init() method that is called after all dependencies have been initialized.
I've been playing with the development branch, it it works quite well.
This is the method I used to get around that issue.
firstly, you want to make your form implement ServiceLocatorInterface as you have done.
You will then still need to manually inject the service locator, and as the whole form is generated inside the contrstuctor you will need to inject via the contructor too (no ideal to build it all in the constructor though)
Module.php
/**
* Get the service Config
*
* #return array
*/
public function getServiceConfig()
{
return array(
'factories' => array(
/**
* Inject ServiceLocator into our Form
*/
'MyModule\Form\MyForm' => function($sm) {
$form = new \MyModule\Form\MyFormForm('formname', $sm);
//$form->setServiceLocator($sm);
// Alternativly you can inject the adapter/gateway directly
// just add a setter on your form object...
//$form->setAdapter($sm->get('Users\Model\GroupsTable'));
return $form;
},
),
);
}
Now inside your controller you get your form like this:
// Service locator now injected
$form = $this->getServiceLocator()->get('MyModule\Form\MyForm');
Now you will have access to the full service locator inside the form, to get hold of any other services etc such as:
$groups = $this->getServiceLocator()->get('Users\Model\GroupsTable')->fetchAll();
In module.php I create two services. See how I feed the adapter to the form.
public function getServiceConfig()
{
return array(
'factories' => array(
'db_adapter' => function($sm) {
$config = $sm->get('Configuration');
$dbAdapter = new \Zend\Db\Adapter\Adapter($config['db']);
return $dbAdapter;
},
'my_amazing_form' => function ($sm) {
return new \dir\Form\SomeForm($sm->get('db_adapter'));
},
),
);
}
In the form code I use that feed to whatever:
namespace ....\Form;
use Zend\Form\Factory as FormFactory;
use Zend\Form\Form;
class SomeForm extends Form
{
public function __construct($adapter, $name = null)
{
parent::__construct($name);
$factory = new FormFactory();
if (null === $name) {
$this->setName('whatever');
}
}
}
We handle this in the model, by adding a method that accepts a form
public function buildFormSelectOptions($form, $context = null)
{
/**
* Do this this for each form element that needs options added
*/
$model = $this->getServiceManager()->get('modelProject');
if (empty($context)){
$optionRecords = $model->findAll();
} else {
/**
* other logic for $optionRecords
*/
}
$options = array('value'=>'', 'label'=>'Choose a Project');
foreach ($optionRecords as $option) {
$options[] = array('value'=>$option->getId(), 'label'=>$option->getName());
}
$form->get('project')->setAttribute('options', $options);
}
As the form is passed by reference, we can do something like this in the controller where the form is built:
$builder = new AnnotationBuilder();
$form = $builder->createForm($myEntity);
$myModel->buildFormSelectOptions($form, $myEntity);
$form->add(array(
'name' => 'submitbutton',
'attributes' => array(
'type' => 'submit',
'value' => 'Submit',
'id' => 'submitbutton',
),
));
$form->add(array(
'name' => 'cancel',
'attributes' => array(
'type' => 'submit',
'value' => 'Cancel',
'id' => 'cancel',
),
));
Note: The example assumes the base form is build via annotations, but it doesn't matter how you create the initial form.
An alternative method to the other answers would be to create a ServiceManager Initializer.
An example of an existing Initializer is how the ServiceManager is injected if your instance implements ServiceLocatorAwareInterface.
The idea would be to create an interface that you check for in your Initialiser, this interface may look like:
interface FormServiceAwareInterface
{
public function init();
public function setServiceManager(ServiceManager $serviceManager);
}
An example of what your Initializer may look like:
class FormInitializer implements InitializerInterface
{
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if (!$instance instanceof FormServiceAwareInterface)
{
return;
}
$instance->setServiceManager($serviceLocator);
$instance->init();
}
}
Anything that happens in init() would have access to the ServiceManager. Of course you would need to add your initializer to your SM configuration.
It is not perfect but it works fine for my needs and can also be applied to any Fieldsets pulled from the ServiceManager.
This is the way I used get around that issue.
firstly, In Module.php, create the service (just as you have done):
// module/Users/Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'Users\Model\UsersTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$uTable = new UsersTable($dbAdapter);
return $uTable;
},
//I need to get this to the list of groups
'Users\Model\GroupsTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$gTable = new GroupsTable($dbAdapter);
return $gTable;
},
),
);
}
Then in the controller, I got a reference to the Service:
$users = $this->getServiceLocator()->get('Test\Model\TestGroupTable')->fetchAll();
$options = array();
foreach ($users as $user)
$options[$user->id] = $user->name;
//get the form element
$form->get('user_id')->setValueOptions($options);
And viola, that work.
I'm creating my own blog engine to learn Symfony, and I have a question :
In the generated administration pages for a blog post, I have a drop-down list of authors, to indicate the author_id.
I'd like to hide that drop-down list, and set the author_id to the id of the current logged-in user when the post is created (but not when it is edited)
How can I accomplish that ?
Edit
I've tried those :
$request->setParameter(sprintf("%s[%s]", $this->form->getName(), "author_id"), $this->getUser()->getAttribute("user_id"));
$request->setParameter("content[author_id]", $this->getUser()->getAttribute("user_id"));
$request->setParameter("author_id", $this->getUser()->getAttribute("user_id"));
$request->setParameter("author_id", 2);
$request->setParameter("content[author_id]", 2);
$request->setParameter("author_id", "2");
$request->setParameter("content[author_id]", "2");
In processForm() and executeCreate()
Resolved !
The final code is :
public function executeCreate(sfWebRequest $request)
{
$form = $this->configuration->getForm();
$params = $request->getParameter($form->getName());
$params["author_id"] = $this->getUser()->getGuardUser()->getId();;
$request->setParameter($form->getName(), $params);
parent::executeCreate($request);
}
Override the executeCreate function in the actions file. When binding post data to the form, merge the current user's id into it.
2nd update
I did some experimenting, and this works:
class fooActions extends autoFooActions
{
public function executeCreate(sfWebRequest $request)
{
$form = $this->configuration->getForm();
$params = $request->getParameter($form->getName());
$params["author_id"] = 123;
$request->setParameter($form->getName(), $params);
parent::executeCreate($request);
}
}
change the widget in the form with the sfWidgetFormInputHidden and set the value with sfUser attribute (that defined when a user logged in)
override the executeCreate() and set the author_id widget (thanks to maerlyn :D )
public function executeCreate(sfWebRequest $request){
parent::executeCreate($request);
$this->form->setWidget('author_id', new sfWidgetFormInputHidden(array(),array('value'=>$this->getUser()->getAttribute('author_id'))) );
}
In Objects , the solution is: (new and $this)
class fooActions extends autoFooActions
{
public function executeCreate(sfWebRequest $request)
{
$this->form = new XxxxxForm();
$params = $request->getParameter($this->form->getName());
$params["author_id"] = 123;
$request->setParameter($this->form->getName(), $params);
parent::executeCreate($request);
}
}