I must insert a link in an email that send to my user. So I have an Entity class that send this mail. But I don't know how i can create this link with "url" method of the view/controller system of ZF2.
My class is:
class UserEntity
{
public function sendMail($user)
{
$link = $unknow->url("route",array("param" => "param")); //how can create this ?
$text = "click here $link";
$this->sendMail($to,$text);
}
}
Can you help me? Thanks
In terms of design, it would be considered bad practice to have your domain model responsible for the creation of the URL (or anything else that that does not describe the entity in its simplest terms).
I would create a UserService that would encapsulate the a SendMail function where a UserEntity could be passed as an argument and it's email property used to send the email.
class UserService {
protected $mailService;
public function __construct(MailService $mailService) {
$this->mailService = $mailService;
}
public function sendUserEmail(UserEntity $user, $message) {
$this->mailService->send($user->getEmail(), $message);
}
}
The mail service could be another service encapsulating the Zend\Mail\Transport instances.
Your controller would use the UserService to send the mail to the correct user.
The $message which needs to include a URL that is generated using the Zend\Mvc\Controller\Plugin\Url controller plugin
class UserController extends AbstractActionController {
protected $userService;
public function __construct(UserService $userService) {
$this->userService = $userService;
}
public function sendEmailAction() {
// load $user from route params or form post data
$user = $this->userService->findUserByTheirId($this->params('id'));
// Generate the url
$url = $this->url()->fromRoute('user/foo', array('bar' => 'param1'));
$message = sprintf('This is the email text link!', $url);
$this->userService->sendUserEmail($user, $message);
}
}
These are contrived examples but my point is that you should only store information in your entity allowing you to "do stuff" with it, not within it.
Related
guys,
At this point i am close to start pulling hair out of my head. I don't find a way to achieve this.
I have a custom class that belongs to a custom folder i created under my WebServices Module src folder. I need to be able to instantiate this class from inside another module/controller but when i do that and dump the services member it contains null.
How can i have the service manager accesible from inside my ApiAuthentication class.
Any help will be appreciated. Thanks
<?php
namespace WebServices\Services;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ApiAuthenticationService extends \Zend\Soap\Client implements ServiceLocatorAwareInterface{
public $services;
function __construct($options = null){
parent::__construct('http://tinysoa.local/soap/security/api_authentication?wsdl',$options);
}
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
When i call this from inside another module/controller it dumps a null value:
class IndexController extends AbstractActionController
{
public function indexAction()
{
$a = new \WebServices\Services\ApiAuthenticationService();
var_dump($a->services);
Responding with my own answer to add-on to Adrian's, and the question you asked in response.
If your service has dependencies of it's own, you just use a factory instead of going the invokable route.
Say your service needs a cache adapter and database adapter. Also imagine that it can optionally be configured with some other service (FooService, below):
<?php
public function getServiceConfig()
{
return array(
'factories' => array(
'my_service' => function($sm){
$cache = $sm->get('Cache');
$dbAdapter = $sm->get('DefaultDbAdapter');
$fooService = $sm->get('FooService');
// instantiate your service with required dependencies
$mySvc = new \My\Shiny\Service($cache, $dbAdapter);
// inject an optional dependency
$mySvc->setFooService($fooService);
// return your shiny new service
return $mySvc;
}
)
);
}
Side Note: It's generally bad design to inject the ServiceManager all over the place. You're better off managing your dependencies more explicitly, like above.
This stuff is covered quite well in the Quick Start, if you haven't already read that.
Register your Service in Service Config and access it through getServiceLocator() method in controller.
Module.php
public function getServiceConfig()
{
return array(
'invokables' => array(
'my_service' => 'WebServices\Services\ApiAuthenticationService'
)
);
}
Controller
public function indexAction()
{
$service = $this->getServiceLocator()->get('my_service');
}
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();
On my website, I use a ReCaptcha widget in the form used to add comments. Once the form has been correctly sent, I write a cookie to the user's computer.
I would like to remove the ReCaptcha widget when the user has that cookie, so that returning visitors don't have to type a captcha. Can I do that in forms/commentForm.class.php, or do I need to create a new form ?
Save your flag in session:
<?php
...
if ($form->isValid()) {
...
// comment added
$this->getUser()->setAttribute('is_bot', false);
...
}
In another action:
<?php
$this->form = new CommentForm();
if ($this->getUser()->getAttribute('is_bot', true)) {
$this->form->setWidget(); // set captcha widget
$this->form->setValdiator(); // set captcha valdiator
}
Hope this helps.
It is often handy to pass a User instance as an option when creating a form in action:
public function executeNew(sfWebRequest $request)
{
$this->form = new ModelForm(null, array('user'=>$this->getUser));
}
Now you can configure you form based on user session attributes:
class ModelForm extends BaseModelForm
{
public function configure()
{
if ($this->getOption('user')->getAttribute('is_bot', false)
{
//set your widgets and validators
}
}
}
I'm trying to save some users in a custom admin form and I'd like to set them in a particular group, in the sfGuardUserGroup.
So If the user I've just created has an id of 25, then I'd expect an entry in the sfGuardUserGroup table with a user_id of 25 and a group_id of 8 (8 is my group id I want o add these users to.)
Could I do this in the form class, or in the processForm action?
I'm using doctrine and SF1.4
Thanks
This should do what you need:
<?php
class AdminUserForm extends sfGuardUserForm
{
public function configure()
{
//customise form...
}
public function save($con = null)
{
//Do the main save to get an ID
$user = parent::save($con);
//Add the user to the relevant group, for permissions and authentication
if (!$user->hasGroup('admin'))
{
$user->addGroupByName('admin');
$user->save();
}
return $user;
}
}
If you require this behaviour for all sfGuardUser's created you should put this logic in the model for sfGuardUser class. [example below]
// sfGuardUser class
public function save(Doctrine_Connection $conn = null) {
if (!$this->hasGroup('group_name'))
$this->addGroupByName('group_name', $conn);
parent::save($conn);
}
If you require this functionality only on this specific form, you should put the logic within the form. Adding logic to the processForm action would be incorrect as you would be placing business logic within the controller.
Let's consider the following simple schema (in Doctrine, but Propel users are welcome too):
User:
columns:
name: string
Article:
columns:
user_id: integer
content: string
relations:
User:
local: user_id
foreign: id
Now, if you create a route for Article model and generate a module via doctrine:generate-module-for-route frontend #article_route you get a CRUD application that manages all the articles. But in frontend you would normally want to manage objects related to signed-in User, so you have to manually get the id of the User, pass id to the model and write a bunch of methods that would retrieve objects related to this User, for example:
public function executeIndex(sfWebRequest $request)
{
$this->articles = Doctrine::getTable('Articles')
->getUserArticles(this->getUser());
}
public function executeShow(sfWebRequest $request)
{
$this->article = $this->getRoute()->getObject();
if (!$this->article->belongsToUser($this->getUser()))
{
$this->redirect404();
}
}
and model:
class ArticleTable extends Doctrine_Table
{
public function getUserArticles(sfUser $user)
{
$q = $this->createQuery('a')
->where('a.user_id = ?', $user->getId());
return $q->execute();
}
}
class Article extends BaseArticle
{
public function belongsToUser(sfUser $user)
{
return $this->getUserId() == $user->getId();
}
}
This is trivial stuff and yet you have to manually write this code for each new relation. Am I missing some kind of way to take advantage of Doctrine relations? Anyways, how would you do it? Thank you.
I believe you should be able to do this with a custom routing class. I have never done this, but there is a tutorial in the More with Symfony book: Advanced Routing. My guess is that it should look something like this:
class objectWithUserRoute extends sfDoctrineRoute
{
public function matchesUrl($url, $context = array())
{
if (false === $parameters = parent::matchesUrl($url, $context))
{
return false;
}
$parameters['user_id'] = sfContext::getInstance()->getUser()->getId();
return array_merge(array('user_id' => sfContext::getInstance()->getUser()->getId()), $parameters);
}
protected function getRealVariables()
{
return array_merge(array('user_id'), parent::getRealVariables());
}
protected function doConvertObjectToArray($object)
{
$parameters = parent::doConvertObjectToArray($object);
unset($parameters['user_id']);
return $parameters;
}
}
You would then need to set the routing class in routing.yml to use objectWithUserRoute. I haven't tested this, but I think it is the best way to go about solving the problem.