I have create a controller that creates a Owner record into database. Everything was done on the CreateOwnerController like this and working properly:
class CreateOwnerController extends Controller
{
public function executeAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$owner = new Owner($request->request->get("name"));
$em->persist($owner);
$em->flush();
return new Response('Added',200);
}
}
Now,In order to refactor that I have created an interface that defines the OwnerRepository:
interface OwnerRepositoryInterface {
public function save(Owner $owner);
}
And a OwnerRepository that implements this interface:
class OwnerRepository extends EntityRepository implements OwnerRepositoryInterface {
public function save(Owner $owner) {
$this->_em->persist($owner);
$this->_em->flush();
}
}
Then I have Created for the application layer a CreateOwnerUseCase Class that receives a OwnerRepository and executes a method to save in into OwnerRepository:
class CreateOwnerUseCase {
private $ownerRepository;
public function __construct(OwnerRepositoryInterface $ownerRepository) {
$this->ownerRepository = $ownerRepository;
}
public function execute(string $ownerName) {
$owner = new Owner($ownerName);
$this->ownerRepository->save($owner);
}
}
Ok, i'm spliting the initial Controller intro layer Domain / Aplication / Framework layers.
On the CreateOwnerController now i have instantiated that Use Case and passed as parameter the OwnerRepository like this:
class CreateOwnerController extends Controller {
public function executeAction(Request $request) {
$createOwnerUseCase = new CreateOwnerUseCase(new OwnerRepository());
$createOwnerUseCase->execute($request->request->get("name"));
return new Response('Added',200);
}
}
But it fails when Make the request to create new Owner:
Warning: Missing argument 1 for Doctrine\ORM\EntityRepository::__construct(), called in /ansible/phpexercises/Frameworks/mpweb-frameworks-symfony/src/MyApp/Bundle/AppBundle/Controller/CreateOwnerController.php
It happens on OwnerRepository passed as parameter. It wants an $em and Mapped Class... What is the meaning of this mapped Class? How solve this error?
This answer is for Symfony 3.3+/4+.
You need to register your repository as a service. Instead of extending it 3rd party code, you should use composition over inheritance.
final class OwnerRepository implements OwnerRepositoryInterface
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function save(Owner $owner)
{
$this->entityManager->persist($owner);
$this->entityManager->flush();
}
}
And register it as a service:
# app/config/services.yml
services:
App\Repository\:
# for location app/Repository
resource: ../Repository
You might need to tune paths a bit, to make that work.
To get more extended answer, see How to use Repository with Doctrine as Service in Symfony
Related
I found this piece of code shared in a Gist (somewhere I lost the link) and I needed something like that so I started to use in my application but I have not yet fully understood and therefore I am having some problems.
I'm trying to create dynamic menus with KnpMenuBundle and dynamic means, at some point I must verify access permissions via database and would be ideal if I could read the routes from controllers but this is another task, perhaps creating an annotation I can do it but I will open another topic when that time comes.
Right now I need to access the SecurityContext to check if the user is logged or not but not know how.
I'm render the menu though RequestVoter (I think) and this is the code:
namespace PlantillaBundle\Menu;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
class RequestVoter implements VoterInterface {
private $container;
private $securityContext;
public function __construct(ContainerInterface $container, SecurityContextInterface $securityContext)
{
$this->container = $container;
$this->securityContext = $securityContext;
}
public function matchItem(ItemInterface $item)
{
if ($item->getUri() === $this->container->get('request')->getRequestUri())
{
// URL's completely match
return true;
}
else if ($item->getUri() !== $this->container->get('request')->getBaseUrl() . '/' && (substr($this->container->get('request')->getRequestUri(), 0, strlen($item->getUri())) === $item->getUri()))
{
// URL isn't just "/" and the first part of the URL match
return true;
}
return null;
}
}
All the code related to securityContext was added by me in a attempt to work with it from the menuBuilder. Now this is the code where I'm making the menu:
namespace PlantillaBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
class MenuBuilder extends ContainerAware {
public function mainMenu(FactoryInterface $factory, array $options)
{
// and here is where I need to access securityContext
// and in the near future EntityManger
$user = $this->securityContext->getToken()->getUser();
$logged_in = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav');
if ($logged_in)
{
$menu->addChild('Home', array('route' => 'home'))->setAttribute('icon', 'fa fa-list');
}
else
{
$menu->addChild('Some Menu');
}
return $menu;
}
}
But this is complete wrong since I'm not passing securityContext to the method and I don't know how to and I'm getting this error:
An exception has been thrown during the rendering of a template
("Notice: Undefined property:
PlantillaBundle\Menu\MenuBuilder::$securityContext in
/var/www/html/src/PlantillaBundle/Menu/MenuBuilder.php line 12") in
/var/www/html/src/PlantillaBundle/Resources/views/menu.html.twig at
line 2.
The voter is defined in services.yml as follow:
plantilla.menu.voter.request:
class: PlantillaBundle\Menu\RequestVoter
arguments:
- #service_container
- #security.context
tags:
- { name: knp_menu.voter }
So, how I inject securityContext (I'll not ask for EntityManager since I asume will be the same procedure) and access it from the menuBuilder?
Update: refactorizing code
So, following #Cerad suggestion I made this changes:
services.yml
services:
plantilla.menu_builder:
class: PlantillaBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory", "#security.context"]
plantilla.frontend_menu_builder:
class: Knp\Menu\MenuItem # the service definition requires setting the class
factory_service: plantilla.menu_builder
factory_method: createMainMenu
arguments: ["#request_stack"]
tags:
- { name: knp_menu.menu, alias: frontend_menu }
MenuBuilder.php
namespace PlantillaBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class MenuBuilder {
/**
* #var Symfony\Component\Form\FormFactory $factory
*/
private $factory;
/**
* #var Symfony\Component\Security\Core\SecurityContext $securityContext
*/
private $securityContext;
/**
* #param FactoryInterface $factory
*/
public function __construct(FactoryInterface $factory, $securityContext)
{
$this->factory = $factory;
$this->securityContext = $securityContext;
}
public function createMainMenu(RequestStack $request)
{
$user = $this->securityContext->getToken()->getUser();
$logged_in = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');
$menu = $this->factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav');
if ($logged_in)
{
$menu->addChild('Home', array('route' => 'home'))->setAttribute('icon', 'fa fa-list');
}
else
{
$menu->addChild('Some Menu');
}
return $menu;
}
}
Abd ib my template just render the menu {{ knp_menu_render('frontend_menu') }} but now I loose the FontAwesome part and before it works, why?
Your menu builder is ContainerAware, so I guess that in it you should access the SecurityContext via $this->getContainer()->get('security.context').
And you haven't supplied any use cases for the voter class, so I'm guessing you're not using the matchItem method.
You should definitely try to restructure your services so that the dependencies are obvious.
Per your comment request, here is what your menu builder might look like:
namespace PlantillaBundle\Menu;
use Knp\Menu\FactoryInterface;
class MenuBuilder {
protected $securityContext;
public function __construct($securityContext)
{
$this->securityContext = $securityContext;
}
public function mainMenu(FactoryInterface $factory, array $options)
{
// and here is where I need to access securityContext
// and in the near future EntityManger
$user = $this->securityContext->getToken()->getUser();
...
// services.yml
plantilla.menu.builder:
class: PlantillaBundle\Menu\MenuBuilder
arguments:
- '#security.context'
// controller
$menuBuilder = $this->container->get('plantilla.menu.builder');
Notice that there is no need to make the builder container aware since you only need the security context service. You can of course inject the entity manager as well.
================================
With respect to the voter stuff, right now you are only checking to see if a user is logged in. So no real need for voters. But suppose that certain users (administrators etc) had access to additional menu items. You can move all the security checking logic to the voter. Your menu builder code might then look like:
if ($this->securityContext->isGranted('view','homeMenuItem')
{
$menu->addChild('Home', array('route' ...
In other words, you can get finer controller over who gets what menu item.
But get your MenuBuilder working first then add the voter stuff if needed.
In my Zend Framework 2 project, I have an external lib and I want to save my information in the base with the model.
....
....
....
EDITED MESSAGE :
I explain again my need: In my controllers, I make insertions and deletions in the database and I want to log all actions in a "t_log" table . To do it, I have thought to create an external class.
My question is: How I can call my models method from my external class ?
namespace Mynamespace;
use Firewall\Model\Logs;
use Firewall\Model\LogsTable;
class StockLog
{
public function addLog()
{
$log = $this->getServiceLocator()->get('Firewall\Model\LogTable');
$log->save('user added');
die('OK');
}
}
My model :
namespace Firewall\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Sql\Select;
class UserGroupTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function save()
{
// How I Can call this method from the StockLog method ?
}
}
Thanks you !
getServiceLocator is a function of \Zend\Mvc\Controller\AbstractActionController so it is supposed to be used in your controllers.
I dont know what your StockLog class is, but it is not extending any other class, so i guess it has not that function and your error is one step before, in the call to getSErviceLocator that is not defined, so its not returning an object.
Probably you can inject the service locator with something like
class StockLog
{
private $serviceLocator= null;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function add()
{
# Do you know how I can call the service ??
$User = $this->serviceLocator->get('Firewall\Model\UserTable');
}
}
and then, when you create your StockLog object, in your controller, you inject the servicelocator
public class yourController extends AbstractActionController {
public function yourAction(){
$mStockLog = new StockLog ();
$mStockLog->setServiceLocator($this->getServiceLocator());
/...
}
}
Also, if you only need the 'Firewall\Model\UserTable' service, you should inject just that, instead of the serviceLocator.
At any rate you should minimice the knowledge of your model classes about the rest of the system, hving always in mind the dependency inversion principle, to get a better decoupling
UPDATE
inject the log table
namespace Mynamespace;
use Firewall\Model\Logs; use Firewall\Model\LogsTable;
class StockLog {
private $logTable= null;
public function setLogTable($logTable)
{
$this->logTable= $logTable;
}
public function addLog()
{
$this->logTable->save('user added');
die('OK');
}
}
and then, when you create your StockLog (in your controller, or wherever you do it, before you use it) you inject the logtable object
$mStockLog = new StockLog ();
$mStockLog->setLogTable($this->getServiceLocator()->get('Firewall\Model\LogTable'));
Of course, Im suposing that you configured correctly your Firewall\Model\LogTable class to be retrieved by means of the service manager, in getServiceConfig() in your Module.php
public function getServiceConfig() {
return array (
'factories' => array (
'Firewall\Model\LogTable' => function ($sm) {
$logTable = //create it as you use to
return $logTable;
}
)
}
I have a read a lot of topics on this and I can't seem to find a solution to my problem.
I feel like the problem is obvious and maybe I have just been staring at it too long.
The error is FatalErrorException: Error: Call to a member function has() on a non-object in /vagrant/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php line 198
Looking at the error line, it says.
public function getDoctrine()
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application.');
}
return $this->container->get('doctrine');
}
Here is my code...
This is the main controller that is calling the DAO Controller
public function clickThroughAction(request $request, $hash)
{
if (isset($hash)) {
$dbController = $this->get('database_controller');
print_r($dbController->getReferralIdByHash($hash));
return $this->redirect($this->generateUrl('home'));
} else {
return 0;
}
}
This is the service that is being used.
services:
database_controller:
class: Fuel\FormBundle\Controller\DatabaseController
This is the dao controller that is calling the database.
public function getReferralIdByHash($hash)
{
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'Select u From FuelFormBundle:UserReferrals u WHERE u.user_hash = :hash'
)->setParameter('hash', $hash);
$referral = $query->getResult();
if (!$referral) {
throw $this->createNotFoundException(
'No product referral found'
);
$logger = $this->get('logger');
$logger->info('I just got the logger');
$logger->crit('An error occurred, hash for referrals is not recognized. current hash is: ' . $hash . " .");
return $this->redirect($this->generateUrl('home'));
}
$clickThroughCount = $referral[0]->getClickThrough();
$referral[0]->setClickThrough($clickThroughCount + 1);
$em->flush();
return $referral;
}
I think the problem is that the doctrine container is not present which is why I am having issues. I am not sure how to solve this.
Any help is appreciated. Thanks!
Edit
Ok so here is what I changed.
Main Controller stayed the same.
DAO Controller a couple of things were added.
class DatabaseController extends Controller{
protected $entityManager;
public function __construct($entityManager) {
$this->entityManager = $entityManager;
}
public function getReferralIdByHash($hash)
{
$em = $this->entityManager;
$query = $em->createQuery(
'Select u From FuelFormBundle:UserReferrals u WHERE u.user_hash = :hash'
)->setParameter('hash', $hash);
$referral = $query->getResult();
if (!$referral) {
throw $this->createNotFoundException(
'No product referral found'
);
$logger = $this->get('logger');
$logger->info('I just got the logger');
$logger->crit('An error occurred, hash for referrals is not recognized. current hash is: ' . $hash . " .");
return $this->redirect($this->generateUrl('home'));
}
$clickThroughCount = $referral[0]->getClickThrough();
$referral[0]->setClickThrough($clickThroughCount + 1);
$em->flush();
return $referral;
}
}
Service ended up looking like this
services:
database_controller:
class: Fuel\FormBundle\Controller\DatabaseController
arguments: ["#doctrine.orm.entity_manager"]
The problem is the container not being injected into the controller here.
Normally, Symfony does this automatically if you're extending Symfony\Bundle\FrameworkBundle\Controller\Controller, which itself extends Symfony\Component\DependencyInjection\ContainerAware:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class YourController extends Controller
The container is injected into the controller (if not explicitly defined as a service) using setter injection calling the method setContainer() with the container as an argument.
Now, as you configured your controller as a service you need to add the setContainer call to your service configuration:
services:
database_controller:
class: Fuel\FormBundle\Controller\DatabaseController
calls:
- [setContainer, ["#service_container"]]
Clear your cache afterwards.
Not sure why you would make a controller a service. For what use case? Normally a service is a Plain Old PHP Object.
About your problem .. since you are using the controller as a service it does not get the container automatically. So you have to inject the entire container, which is kind of heavy if you just need doctrine.
So it's better just to inject the things you really need. To inject doctrine, in your yml below class:
arguments: ["#doctrine.orm.entity_manager"]
Then in your controller constructor:
public function __construct($entityManager) {
$this->entityManager = $entityManager;
}
Possible you will need to call the parent constructor (be aware of that).
If you want do inject the complete service container anyway, here is the right section in the manual how you can do that: http://symfony.com/doc/current/cookbook/service_container/scopes.html#passing-the-container-as-a-dependency-of-your-service
One more suggestion: when you are moving database connection out of main controller you need to construct new instance of Entity Manager for instance:
class StoreController extends Controller{
public function addstoreAction(Request $request){
$checkStore = new StoreExistsCheck($this ->getDoctrine()->getManager());
//your code here
}
using /myBundle/Model folder
namespace myBundle\Model;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use myBundle\Entity\Store;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query;
class StoreExistsCheck extends Controller{
protected $em = null;
protected $kernel = null;
public function __construct(EntityManager $em) {
$this->em = $em;
}
public function storeExists($argosStore){
$storeExists = $this->em ->getRepository('myBundle:Store')
->findOneBystoreNumber($argosStore->getStoreNumber());
return $storeExists;
}
How to get translator in model?
Inside view we can get translator using this code
$this->translate('Text')
Inside controller we can get translator using this code
$translator=$this->getServiceLocator()->get('translator');
$translator->translate("Text") ;
But how to get translator in model?
I'd tried so many ways to get service locator in models
2 of those
1)Using MVC events
$e=new MvcEvent();
$sm=$e->getApplication()->getServiceManager();
$this->translator = $sm->get('translator');
if i pring $sm it is showing null. but it works fine in Model.php onBootstrap
2)Created one model which implements ServiceLocatorAwareInterface
SomeModel.php
<?php
namespace Web\Model;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class SomeModel implements ServiceLocatorAwareInterface
{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
and used that inside my model
$sl = new SomeModel();
$sm=$sl->getServiceManager();
var_dump($sm); exit;
$this->translator = $sm->get('translator');
this is also printing null.
If you don't need the servicemanager instance in your model, simply inject translator instance to it.
For example:
// Your model's constructor
class MyModel {
// Use the trait if your php version >= 5.4.0
use \Zend\I18n\Translator\TranslatorAwareTrait;
public function __construct( $translator )
{
$this->setTranslator( $translator );
}
public function modelMethodWhichNeedsToUseTranslator()
{
// ...
$text = $this->getTranslator()->translate('lorem ipsum');
// ...
}
}
When you creating your model first time on service or controller level
class someClass implements ServiceLocatorAwareInterface {
public function theMethodWhichCreatesYourModelInstance()
{
// ...
$sm = $this->getServiceLocator();
$model = new \Namespace\MyModel( $sm->get('translator') )
// ...
}
}
If you need to instantiate your model (new MyModel();) on multiple methods/classes, consider to writing a factory for it.
Here is a nice article about Dependency Injection and PHP by Ralph Schindler for more detailed comments about this approach.
For your Model class to be ServiceLocatorAware, you not only need to implement the interface, you also need to make your model a service of the service manager, and fetch the model from there.
Add your model to the service manager, since it doesn't appear to need any constructor params, it's invokable, so you can add it to the invokables array in service manager config. You can do that by using the getServiceConfig() method in your Module class...
class Module
{
public function getServiceConfig()
{
return array(
'invokables' => array(
'SomeModel' => 'Fully\Qualified\ClassName\To\SomeModel',
),
);
}
}
Then, instead of calling the new keyword to create your model instance, you fetch it from the service manager, for instance, by calling the getServiceLocator() method in a controller action...
public function fooAction()
{
$sm = $this->getServiceLocator();
$model = $sm->get('SomeModel');
}
When your model is fetched from the service manager, a service initializer will look to see if it implements the ServiceLocatorAwareInterface and automatically call setServiceLocator() if it does, passing it an instance of the service manager.
Im seraching over and cannot find answer.
I have database role model in my application. User can have a role but this role must be stored into database.
But then user needs to have default role added from database. So i created a service:
<?php
namespace Alef\UserBundle\Service;
use Alef\UserBundle\Entity\Role;
/**
* Description of RoleService
*
* #author oracle
*/
class RoleService {
const ENTITY_NAME = 'AlefUserBundle:Role';
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function findAll()
{
return $this->em->getRepository(self::ENTITY_NAME)->findAll();
}
public function create(User $user)
{
// possibly validation here
$this->em->persist($user);
$this->em->flush($user);
}
public function addRole($name, $role) {
if (($newrole = findRoleByRole($role)) != null)
return $newrole;
if (($newrole = findRoleByName($name)) != null)
return $newrole;
//there is no existing role
$newrole = new Role();
$newrole->setName($name);
$newrole->setRole($role);
$em->persist($newrole);
$em->flush();
return $newrole;
}
public function getRoleByName($name) {
return $this->em->getRepository(self::ENTITY_NAME)->findBy(array('name' => $name));
}
public function getRoleByRole($role) {
return $this->em->getRepository(self::ENTITY_NAME)->findBy(array('role' => $role));
}
}
my services.yml is:
alef.role_service:
class: Alef\UserBundle\Service\RoleService
arguments: [%doctrine.orm.entity_manager%]
And now I want to use it in two places:
UserController and User entity. How can i get them inside entity?
As for controller i think i just need to:
$this->get('alef.role_service');
But how to get service inside entity?
You don't. This is a very common question. Entities should only know about other entities and not about the entity manager or other high level services. It can be a bit of a challenge to make the transition to this way of developing but it's usually worth it.
What you want to do is to load the role when you load the user. Typically you will end up with a UserProvider which does this sort of thing. Have you read through the sections on security? That should be your starting point:
http://symfony.com/doc/current/book/security.html
The reason why it's so difficult to get services into entities in the first place is that Symfony was explicitly designed with the intent that services should never be used inside entities. Therefore, the best practice answer is to redesign your application to not need to use services in entities.
However, I have found there is a way to do it that does not involve messing with the global kernel.
Doctrine entities have lifeCycle events which you can hook an event listener to, see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events For the sake of the example, I'll use postLoad, which triggers soon after the Entity is created.
EventListeners can be made as services which you inject other services into.
Add to app/config/config.yml:
services:
example.listener:
class: Alef\UserBundle\EventListener\ExampleListener
arguments:
- '#alef.role_service'
tags:
- { name: doctrine.event_listener, event: postLoad }
Add to your Entity:
use Alef\UserBundle\Service\RoleService;
private $roleService;
public function setRoleService(RoleService $roleService) {
$this->roleService = $roleService;
}
And add the new EventListener:
namespace Alef\UserBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Alef\UserBundle\Service\RoleService;
class ExampleListener
{
private $roleService;
public function __construct(RoleService $roleService) {
$this->roleService = $roleService;
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if(method_exists($entity, 'setRoleService')) {
$entity->setRoleService($this->roleService);
}
}
}
Just keep in mind this solution comes with the caveat that this is still the quick and dirty way, and really you should consider redesigning your application the proper way.
Thanks to Kai's answer above which answer to the question, but it's not compatible with symfony 5.x .
It's good to precise it's a bad practice, but required in some special case like legacy code or a bad DB design (as a temporary solution before schema migration)
As in my case, I use this code with a mailer and translator, which introduce an issue with the private property if Symfony >= 5.3 , so here the solution for recent version of symfony:
in config/services.yaml:
services:
Alef\UserBundle\EventListener\ExampleListener:
tags:
- { name: doctrine.event_listener, event: postLoad }
ExampleListener:
namespace Alef\UserBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Alef\UserBundle\Entity\Role;
class ExampleListener
{
public function postLoad(LifecycleEventArgs $postLoad): void
{
$entity = $postLoad->getEntity();
if ($entity instanceof User) {
$repository = ;
$entity->roleRepository(
$postLoad->getEntityManager()->getRepository(Role::class)
);
}
}
}
And in your Entity (or in a trait if you use it in more than one entity):
use Alef\UserBundle\Service\RoleService;
/** #internal bridge for legacy schema */
public function roleRepository(?RoleRepository $repository = null) {
static $roleRepository;
if (null !== $repository) {
$roleRepository = $repository;
}
return $roleRepository;
}
public function getRoleByName($name) {
return $this->roleRepository()->findBy(array('name' => $name));
}