I've created my own service class and I have a function inside it, handleRedirect() that's supposed to perform some minimal logical check before choosing to which route to redirect.
class LoginService
{
private $CartTable;
private $SessionCustomer;
private $Customer;
public function __construct(Container $SessionCustomer, CartTable $CartTable, Customer $Customer)
{
$this->SessionCustomer = $SessionCustomer;
$this->CartTable = $CartTable;
$this->Customer = $Customer;
$this->prepareSession();
$this->setCartOwner();
$this->handleRedirect();
}
public function prepareSession()
{
// Store user's first name
$this->SessionCustomer->offsetSet('first_name', $this->Customer->first_name);
// Store user id
$this->SessionCustomer->offsetSet('customer_id', $this->Customer->customer_id);
}
public function handleRedirect()
{
// If redirected to log in, or if previous page visited before logging in is cart page:
// Redirect to shipping_info
// Else
// Redirect to /
}
public function setCartOwner()
{
// GET USER ID FROM SESSION
$customer_id = $this->SessionCustomer->offsetGet('customer_id');
// GET CART ID FROM SESSION
$cart_id = $this->SessionCustomer->offsetGet('cart_id');
// UPDATE
$this->CartTable->updateCartCustomerId($customer_id, $cart_id);
}
}
This service is invoked in the controller after a successful login or registration. I'm not sure what's the best way to access redirect()->toRoute(); from here (or if I should do it here).
Also if you have other comments on how my code is structured please feel free to leave them.
Using plugins within your services is a bad idea as they require a controller to be set. When a service is created and you inject a plugin it has no idea of the controller instance so it will result in an error exception. If you want to redirect the user you might just edit the response object as the redirect plugin does.
Notice that I stripped the code to keep the example clear and simple.
class LoginServiceFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new LoginService($container->get('Application')->getMvcEvent());
}
}
class LoginService
{
/**
* #var \Zend\Mvc\MvcEvent
*/
private $event;
/**
* RedirectService constructor.
* #param \Zend\Mvc\MvcEvent $event
*/
public function __construct(\Zend\Mvc\MvcEvent $event)
{
$this->event = $event;
}
/**
* #return Response|\Zend\Stdlib\ResponseInterface
*/
public function handleRedirect()
{
// conditions check
if (true) {
$url = $this->event->getRouter()->assemble([], ['name' => 'home']);
} else {
$url = $this->event->getRouter()->assemble([], ['name' => 'cart/shipping-info']);
}
/** #var \Zend\Http\Response $response */
$response = $this->event->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
return $response;
}
}
Now from within your controller you can do the following:
return $loginService->handleRedirect();
Related
In the first place I had to configure parameters using the class "ParametersCompilerPass" to get data from database.Here si my class :
class ParametersCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$boutique = $em->getRepository('AcmeBundle:Boutique')->findOneByNom($container->getParameter('boutique.config'));
if(null !== $boutique){
$container->setParameter('url_site', $boutique->getUrl());
$container->setParameter('idboutique', $boutique->getId());
}else{
$container->setParameter('url_site', null);
$container->setParameter('idboutique', 0);
}
}
}
and when i set a parameter from request, it dont work, i tried in adding this code :
$request = $container->get('request_stack')->getCurrentRequest();
if($request->getMethod() == 'POST'){
if (null !== $choixbout = $request->get('choixbout')){
// $this->container->setParameter('idboutique',$choixbout);
}
}
the service request_stack return null.
I do not know how to configure a parameter from a POST variable.
Hope you can help me.
thanks
Is it solid requirement to have the parameter set?
It could be handy to create a service which has a request dependency that can act as a boutique parameter holder.
For example
# app/config/services.yml
app.boutique:
class: AppBundle\Boutique\Boutique
arguments: ['#request_stack']
app.boutique_info_dependant1:
class: AppBundle\Boutique\BoutiqueDependant1
arguments: ['#app.boutique']
app.boutique_info_dependant2:
class: AppBundle\Boutique\BoutiqueDependant2
arguments: ['#app.boutique']
This would be a parameter handler.
# AppBundle/Boutique/Boutique.php
class Boutique
{
/** #var RequestStack */
private $requestStack;
/**
* BoutiqueListener constructor.
* #param ContainerInterface $container
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getBoutique()
{
$request = $this->requestStack->getCurrentRequest();
/// here you can add an extra check if the request is master etc.
if ($request->getMethod() == Request::METHOD_POST) {
if (null !== $choixbout = $request->get('choixbout')) {
return $choixbout;
}
}
return null;
}
}
Then using the handler
class BoutiqueDependant1
{
public function __construct(Boutique $boutique)
{
$this->myBoutique = $boutique->getBoutique();
}
}
This does not look like the best solution but could work...
Other option would be to rethink the application architecture to handle boutique information somehow differently.
I have made a service to get Doctrine connection in my models (Not sure if it is a nice approach but I dont want to pass connection from controller to model constructor each time).
So lets say I want products in my controller
public function getProductsAction(Request $request) {
$product_model = new ProductModel();
return $product_model->getProducts();
}
I have Product model Which will access a helper to get "database_connection"
use AppBundle\Helper\ContainerHelper;
class ProductModel {
function getProducts() {
$helper = new ContainerHelper();
$db = $helper->getDoctrine();
$query = "SELECT * FROM customer_products;";
$statement = $db->prepare($query);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
Now this helper is defined in src/AppBundle/Helper/ContainerHelper.php
namespace AppBundle\Helper;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class ContainerHelper {
private $container;
public function __construct(Container $container) {
$this->container = $container;
}
public static function getDoctrine() {
$database_connection = $this->container->get('database_connection');
return $database_connection;
}
}
Lets say this service needs "service container" so in app/config/services.yml
services:
app.container_helper:
class: AppBundle\Helper\ContainerHelper
arguments: ['#service_container']
But it gives me error:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Helper\ContainerHelper::__construct() must implement
interface Symfony\Component\DependencyInjection\ContainerInterface,
none given, called in \src\AppBundle\Model\ProductModel.php
on line 148 and defined
While I believe that I have implemented it correctly according to http://symfony.com/doc/current/book/service_container.html and http://anjanasilva.com/blog/injecting-services-in-symfony-2/, its certain that I have missed something or just got the whole bad idea. I need to know if it is a correct concept or what I have missed
While #pavlovich is trying to fix your existing code, I really think you are making this much more convoluted than it has to be. ProductModel itself should be a service with your database connection injected into it.
class ProductModel {
public function __construct($conn) {
$this->conn = $conn;
}
public function getProducts() {
$stmt = $this->conn->executeQuery('SELECT * FROM customer_products');
return $stmt->fetchAll();
}
services:
product_model:
class: AppBundle\...\ProductModel
arguments: ['#database_connection']
// controller.php
$productModel = $this->get('product_model'); // Pull from container
$products = $productModel->getProducts();
Rather than using helpers, I'd recommend using constructor injection and autowiring. It's more safe, future proof and easier to extend and test.
In such case, you'd have to create ProductRepository (more common and standard name for ProductModel) and pass it to controller.
1. Controller
<?php
class SomeController
{
/**
* #var ProductRepository
*/
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function getProductsAction()
{
return $this->productRepository->getProducts();
}
}
If you have difficulties to register controller as a service, just use Symplify\ControllerAutowire bundle.
2. ProductRepository
// src/AppBundle/Repository/ProductRepository.php
namespace AppBundle\Repository;
class ProductRepository
{
/**
* #var Doctrine\DBAL\Connection
*/
private $connection;
public function __construct(Doctrine\DBAL\Connection $connection)
{
$this->connection = $connection;
}
public function fetchAll()
{
$query = "SELECT * FROM customer_products;";
$statement = $this->connection->prepare($query);
$statement->execute();
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
}
3. Service registration
# app/cofig/servies.yml
services:
product_repository:
class: AppBundle\Repository\ProductRepository
autowire: true
For more you can see similar question with answer here: Symfony 3 - Outsourcing Controller Code into Service Layer
With new version of Symfony 3.3, a new feature is added (Auto-wired Services Dependencies)
https://symfony.com/doc/current/service_container/autowiring.html
https://symfony.com/doc/current/service_container/3.3-di-changes.html
Using this feature, I solved this issue in following way:
Added a new directory /src/AppBundle/Model
Added my model classes in this directory
namespace AppBundle\Modal;
use Doctrine\ORM\EntityManagerInterface;
class ProductModal
{
private $em;
// We need to inject this variables later.
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
// We need to inject this variables later.
public function getProducts()
{
$statement = $this->em->getConnection()->prepare("SELECT * FROM product WHERE 1");
$statement->execute();
$results = $statement->fetchAll();
return $results;
}
}
Added in my app/config/services.yml
AppBundle\Modal\:
resource: '../../src/AppBundle/Modal/*'
public: true
In my controller I can use it like
$products = $this->get(ProductModal::class)->getProducts();
P.S. Dont forget to add use AppBundle\Entity\Product\Product; in controller
In a custom extbase extension, I need to call a show action, passing it another value than uid (this is a continuation of Use other than primary key as RealURL id_field).
As the value "other than uid" is not resolved, it results in the exception http://wiki.typo3.org/Exception/CMS/1297759968
EDIT: here's the current working (but ugly) Controller code:
/**
* ItemController
*/
class ItemController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* itemRepository
*
* #var \STUBR\Weiterbildung\Domain\Repository\ItemRepository
* #inject
*/
protected $itemRepository = NULL;
/**
* action list
*
* #return void
*/
public function listAction(\STUBR\Weiterbildung\Domain\Model\Item $item=null) {
if (!empty(\TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1'))){
$this->forward('show');
}
$items = $this->itemRepository->findAll();
$this->view->assign('items', $items);
}
/**
* action show
*
* #param \STUBR\Weiterbildung\Domain\Model\Item $item
* #return void
*/
public function showAction(\STUBR\Weiterbildung\Domain\Model\Item $item=null) {
$tx_weiterbildung_pi1_get = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1');
if (!empty($tx_weiterbildung_pi1_get)){
$dfid = intval($tx_weiterbildung_pi1_get['durchfuehrungId']);
}
$items = $this->itemRepository->findByDurchfuehrungId($dfid);
// helpful:
//\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($item);
$this->view->assign('item', $items[0]);
}
}
PS here are the 5 lines that could be used in the Repository if the magical method wouldn't work:
//public function findByDurchfuehrungId($dfid) {
// $query = $this->createQuery();
// $query->matching($query->equals('durchfuehrungId', $dfid));
// return $query->execute();
//}
Yes, when you're using actions with model binding in param it will always look for object by field defined as ID - in our case it's always uid, but remember, that you do not need to bind model automatically, as you can retrive it yourself.
Most probably you remember, that some time ago I advised to use <f:link.page additionalParams="{originalUid:myObj.originalUid}"Click here</f:link.page> instead of <f:link.action...>
In that case your show action would look like this:
public function showAction() {
$item = $this->itemRepository->findByOriginalUid(intval(GeneralUtility::_GET('originalUid')));
$this->view->assign('item', $item);
}
Where findByOriginalUid should work magically without declaration, but even if it doesn't it's just matter of 5 lines in the repo ;)
Other sample
According to the code you pasted I'd use it rather like this, in this case listAction gets a role of dispatcher for whole plugin:
public function listAction() {
// access get param in array
$tx_weiterbildung_pi1_get = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1');
$dfId = intval($tx_weiterbildung_pi1_get['durchfuehrungId']);
if ($dfId > 0) { // tx_weiterbildung_pi1 exists and is positive, that means you want to go to showAction
$item = $this->itemRepository->findByDurchfuehrungId($dfId);
if (!is_null($item)) { // Forward to showAction with found $item
$this->forward('show', null, null, array('item' => $item));
}else { // Or redirect to the view URL after excluding single item ID from GET params
$this->redirectToUri($this->uriBuilder->setArgumentsToBeExcludedFromQueryString(array('tx_weiterbildung_pi1'))->build());
}
}
// No `tx_weiterbildung_pi1` param, so it should be displayed as a listAction
$items = $this->itemRepository->findAll();
$this->view->assign('items', $items);
}
/**
* #param \STUBR\Weiterbildung\Domain\Model\Item $item
*/
public function showAction(\STUBR\Weiterbildung\Domain\Model\Item $item = null) {
$this->view->assign('item', $item);
}
Your finder should also getFirst() object if possible:
public function findByDurchfuehrungId($DfId) {
$query = $this->createQuery();
$query->matching($query->equals('durchfuehrungId', $DfId));
return $query->execute()->getFirst();
}
To clean up my controller code I want to move the "newPostAction" to a service. The problem I get is that now I cannot pass as a result of the funtion in the service two variables to the controller. I use the function to create a form, and the get the slug from the form's post and render it. I do not know how to pass it to the controller. I tried using the "list()" function but it does not get the info right.
How can I call the pos's "slug" from inside the controller?
Here is the controller code:
/**
* #param Request $request
* #return array
*
* #Route("/new/_post", name="_blog_backend_post_new")
* #Template("BlogBundle:Backend/Post:new.html.twig")
*/
public function newPostAction(Request $request)
{
$form_post = $this->getPostManager()->createPost($request);
$slug_post = ¿How do I get it from inside the createPost()?;
if (true === $form_post)
{
$this->get('session')->getFlashBag()->add('success', 'Your post was submitted successfully');
return $this->redirect($this->generateUrl('blog_blog_post_show', array('slug' => $slug_post)));
}
return array(
'post_slug' => $slug_post,
'form_post' => $form_post->createView()
);
}
Here is the PostManager service to create the new post entity:
/**
* Create and validate a new Post
*
* #param Request $request
* #return bool|FormInterface
*/
public function createPost (Request $request)
{
$post = new Post();
$post->setAuthor($this->um->getloggedUser());
$form_post = $this->formFactory->create(new PostType(), $post);
$form_post->handleRequest($request);
$slug_post = $post->getSlug();
if ($form_post->isValid())
{
$this->em->persist($post);
$this->em->flush();
return true;
}
return $form_post;
}
You just need to return an array from the service and access the values from the controller.
UPDATE
Some changes need to be made to your code in order to get things to work.
Explanation: when the form is valid, the previous code (I deleted it) returned true therefore $ret["form_post"] didn't make sense because $ret was not an array. It surprises me that it didn't throw you an error.
Anyway, that could explain why Doctrine didn't persist your entity. Talking about the redirection, the error could be due to the same reason. $ret was true (a boolean) and $ret["form_slug"] didn't make sense either.
I hope this fixes the problems. Please, let me know if it works.
Service
public function createPost (Request $request)
{
$post = new Post();
$post->setAuthor($this->um->getloggedUser());
$form_post = $this->formFactory->create(new PostType(), $post);
$form_post->handleRequest($request);
$slug_post = $post->getSlug();
if ($form_post->isValid())
{
$this->em->persist($post);
$this->em->flush();
return array("form_post" => true, "slug_post" => $slug_post);;
}
return array("form_post" => $form_post, "slug_post" => $slug_post);
}
Controller:
public function newPostAction(Request $request)
{
$ret = $this->getPostManager()->createPost($request);
$form_post = $ret["form_post"];
$slug_post = $ret["slug_post"];
if (true === $form_post)
{
$this->get('session')->getFlashBag()->add('success', 'Your post was submitted successfully');
return $this->redirect($this->generateUrl('blog_blog_post_show', array('slug' => $slug_post)));
}
return array(
'post_slug' => $slug_post,
'form_post' => $form_post->createView()
);
}
i just implemented ACL in my Zend Framework which already uses Zend Auth.
I want to give access to some controllers and tried it this way:
$roleGuest = new Zend_Acl_Role('guest');
$this->addRole($roleGuest);
$this->addRole(new Zend_Acl_Role('supplier'));
$this->addRole(new Zend_Acl_Role('admin'));
$this->add(new Zend_Acl_Resource('Articles'));
$this->add(new Zend_Acl_Resource('Index'));
$this->deny();
$this->allow('supplier', 'Articles');
$this->allow('admin', null);
But a user, who is supplier (he is really :)) is not able to see the Controller Articles.
What am I doing wrong?
Thanks for help.
BR frgtv10
I think the best solution is to create a plugin and write something like this
class Application_Controller_Plugin_AclManager extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $Request)
{
$AclManager = new Zend_Acl();
$AclManager->addRole(new Zend_Acl_Role('Guest'));
$AclManager->addRole(new Zend_Acl_Role('Supplier'), 'Guest');
$AclManager->addResource(new Zend_Acl_Resource('controller1'));
$AclManager->addResource(new Zend_Acl_Resource('controller2'));
$AclManager->addResource(new Zend_Acl_Resource('controller3'));
$AclManager->allow('Guest', 'controller1', 'index');
$AclManager->allow('Supplier', 'controller2');
$AclManager->allow('Supplier', 'controller3');
It will work great. In addition you can write
if (! $AclManager->isAllowed(USER_ROLE, $Request->getControllerName(), $Request->getActionName()))
{
$this->getResponse()->setRedirect(SOME_URL_TO_REDIRECT);
}
The approach from user707795 is good. I build up my resources with Pike_Reflection_Resource to automaticly define your resources. It's not perfectly documented yet but usage is very simple:
You download the latest version of the Pike library
http://code.google.com/p/php-pike/
Then you create a ACL class which extends Zend_Acl:
<?php
class Application_Acl extends Zend_Acl
{
/**
* Constructor
*/
public function __construct()
{
$this->_addRoles();
$this->_addResources();
$this->_setAuthorization();
}
/**
* Adds roles to ACL
*/
protected function _addRoles()
{
/**
* Get your roles from the application config here or the database like below (Doctrine2)
*/
// $repository = $this->_em->getRepository('BestBuy\Entity\Usergroup');
$roles = array('guest', 'admin', 'moderator');
foreach($roles as $role) {
$this->addRole(new Zend_Acl_Role(strtolower($role)));
}
}
/**
* Adds resources to ACL
*
* Here are resources added to the ACL. You don't have to do this manually
* because Pike_Reflection_Resource will search automaticly in your controller
* directories to define which actions there are and adds every resource as:
* modulename_controller_actionname all lowercase.
*/
public function _addResources()
{
$resourceReflection = new Pike_Reflection_Resource();
$resources = $resourceReflection->toFlatArray('default');
foreach ($resources as $resource => $humanValue) {
$this->addResource(new Zend_Acl_Resource($resource));
}
}
/**
* Sets authorization
*/
public function _setAuthorization()
{
//$permissions = $this->_em->getRepository('BestBuy\Entity\Permission')->findAll();
/**
* I retrieve my permissions here from the database but you could receive the
* from the roles attribute too:
*/
$resourceReflection = new Pike_Reflection_Resource();
$resources = $resourceReflection->toArray('default');
foreach ($resources as $moduleName => $controllers) {
foreach($controllers as $controllerName=>$actions) {
foreach($actions as $actionName=>$action) {
$resourceName = sprintf('%s_%s_%s',
strtolower($moduleName),
strtolower($controllerName),
strtolower($actionName)
);
if(isset($action['roles'])) {
foreach($action['roles'] as $role) {
if ($this->hasRole($role) && $this->has($resourceName)) {
$this->allow($role, $resourceName);
}
}
}
}
}
}
}
}
?>
Then you set up a frontcontroller plugin something just like above:
<?php
class Application_Controller_Plugin_Authorization extends Zend_Controller_Plugin_Abstract
{
/**
* Request
*
* #var Zend_Controller_Request_Abstract
*/
protected $_request;
/**
* ACL
*
* #var Buza_Acl
*/
protected $_acl;
/**
* Called before Zend_Controller_Front enters its dispatch loop.
*
* #param Zend_Controller_Request_Abstract $request
* #return void
*/
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$this->_request = $request;
$this->_acl = new Application_Acl();
Zend_Registry::set('acl', $this->_acl);
$this->_checkAuthorization();
}
/**
* Checks if the current user is authorized
*/
protected function _checkAuthorization()
{
$allowed = false;
$currentResource = sprintf('%s_%s_%s',
strtolower($this->_request->getModuleName()),
strtolower($this->_request->getControllerName()),
strtolower($this->_request->getActionName())
);
if(Zend_Auth::getInstance()->hasIdentity()) {
$user = Zend_Auth::getInstance()->getIdentity());
$identityRole = strtolower($user->getRole()); //make sure you implement this function on your user class/identity!
} else {
$identityRole = 'guest';
}
if ($this->_acl->hasRole($identityRole) && $this->_acl->has($currentResource)) {
if ($this->_acl->isAllowed($identityRole, $currentResource)) {
$allowed = true;
}
}
if ($allowed !== true) {
throw new Zend_Controller_Exception('No permission', 403);
}
}
}
?>
Finally in your controller/actions you define your permissions as follows:
<?php
class IndexController extends Zend_Controller_Action {
/**
* #human Some description for the permissions of this action
* #roles guest|admin|moderator
*/
public function indexAction() {
}
/**
* #human Only for admins!
* #roles admin
*/
public function secretAction() {
}
}
?>
This approach is the best and set-up for small applications. For applications where you want to define the allowed actions in the interface of the application you should leave the roles tag and get the permissions for the database.
Please be aware that code below is not tested but with some reviewing it will work and you can control your permissions in the code.