I'm newbie on Symfony. I am trying to update an old project under symfony 2.6 to symfony 3.3.
After multiple bug fixes I am stuck on a point: I have an Error in my EntityRepository.php file with the constructor.
Type error: Too few arguments to function Doctrine\ORM\EntityRepository::__construct(), 1 passed in /Users/.../var/cache/dev/appDevDebugProjectContainer.php on line 3434 and exactly 2 expected
I understand the error, but my EntityRepository file not contain any __construct method. Should I fix something between Symfony 2 and 3 for the consructor to work?
Thanks a lot.
Here is my MilestoneRepository.php file:
namespace MilestonesBundle\Entity\Repository;
use DateTime;
use Doctrine\ORM\EntityRepository;
use Milestones\Entity\Factory\MilestoneFactoryInterface;
use Milestones\Entity\Repository\MilestoneRepositoryInterface;
class MilestoneRepository extends EntityRepository implements MilestoneFactoryInterface, MilestoneRepositoryInterface
{
protected $current = false;
/**
* #see MilestoneFactoryInterface
*/
public function create()
{
$class = $this->getClassName();
return new $class;
}
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
if (!$orderBy) {
$orderBy['startsAt'] = 'ASC';
}
return parent::findBy($criteria, $orderBy, $limit, $offset);
}
/**
* #see MilestoneRepositoryInterface
*/
public function findCurrent()
{
$now = new DateTime;
if ($this->current === false) {
$this->current = $this->createQueryBuilder('m')
->where('m.startsAt <= :now')
->andWhere('(m.endsAt IS NULL OR :now < m.endsAt)')
->setParameter('now', $now->format('Y-m-d'))
->orderBy('m.startsAt', 'ASC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
}
return $this->current;
}
/**
* #see MilestoneRepositoryInterface
*/
public function isOpen()
{
$current = $this->findCurrent();
return $current && $current->isStart();
}
}
And here is my EntityRepository.php file:
namespace Common\Doctrine\ORM;
use Doctrine\ORM\EntityRepository as BaseEntityRepository;
use Doctrine\ORM\QueryBuilder;
class EntityRepository extends BaseEntityRepository
{
protected $alias = 'x';
public function add($entity)
{
$em = $this->getEntityManager();
$em->persist($entity);
$em->flush();
}
public function remove($entity)
{
$em = $this->getEntityManager();
$em->remove($entity);
$em->flush();
}
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null, $result = true)
{
$alias = $this->alias;
$builder = $this->createQueryBuilder($alias);
$this->applyCriteria($builder, $alias, $criteria);
$this->applyOrderBy($builder, $alias, $orderBy);
$this->applyLimit($builder, $limit);
$this->applyOffset($builder, $offset);
if (!$result) {
return $builder;
}
return $builder->getQuery()->getResult();
}
public function findOneBy(array $criteria, array $orderBy = null, $result = true)
{
$alias = $this->alias;
$builder = $this->createQueryBuilder($alias);
$this->applyCriteria($builder, $alias, $criteria);
$this->applyOrderBy($builder, $alias, $orderBy);
if (!$result) {
return $builder;
}
return $builder->getQuery()->getOneOrNullResult();
}
protected function applyCriteria(QueryBuilder $builder, $alias, array $criteria)
{
$map = $this->getCriteriaMap();
foreach ($criteria as $property => $value) {
if (array_key_exists($property, $map)) {
call_user_func_array($map[$property], [$builder, $alias, $property, $value]);
} else {
$this->applyDefaultCriterion($builder, $alias, $property, $value);
}
}
}
protected function getCriteriaMap()
{
return [];
}
protected function applyDefaultCriterion($builder, $alias, $property, $value)
{
if (null === $value) {
$builder->andWhere($alias.'.'.$property.' IS NULL');
} else {
$parameter = 'p_' . uniqid();
$builder->andWhere($alias.'.'.$property.' = :'.$parameter);
$builder->setParameter($parameter, $value);
}
}
/**
* Apply order by
*
* #param QueryBuilder $builder
* #param string $alias
* #param array|null $orderBy
* #return void
*/
protected function applyOrderBy(QueryBuilder $builder, $alias, array $orderBy = null)
{
if (empty($orderBy)) {
$orderBy = $this->getDefaultOrder();
}
$map = $this->getOrderingMap();
foreach ($orderBy as $property => $direction) {
if (array_key_exists($property, $map)) {
call_user_func_array($map[$property], [$builder, $alias, $property, $direction]);
} else {
$this->applyDefaultOrder($builder, $alias, $property, $direction);
}
}
}
protected function getDefaultOrder()
{
return [];
}
protected function getOrderingMap()
{
return [];
}
protected function applyDefaultOrder(QueryBuilder $builder, $alias, $property, $direction)
{
$builder->orderBy($alias.'.'.$property, $direction);
}
protected function applyLimit(QueryBuilder $builder, $limit = null)
{
if ($limit) {
$builder->setMaxResults($limit);
}
}
protected function applyOffset(QueryBuilder $builder, $offset = null)
{
if ($offset) {
$builder->setFirstResult($offset);
}
}
}
I think I'm accessing through a service, with this:
services:
# Factories
milestones.factory.milestone:
alias: milestones.repository.milestone
arguments: [ MilestonesBundle\Entity\Milestone ]
# Repositories
milestones.repository.milestone:
class: MilestonesBundle\Entity\Repository\MilestoneRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments: [ MilestonesBundle\Entity\Milestone ]
replace this code:
milestones.repository.milestone:
class: MilestonesBundle\Entity\Repository\MilestoneRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments: [ MilestonesBundle\Entity\Milestone ]
with this one:
milestones.repository.milestone:
class: MilestonesBundle\Entity\Repository\MilestoneRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments: [ MilestonesBundle\Entity\Milestone ]
factory method - getRepository
I think somewhere in jour code this method is called:
public function create()
{
$class = $this->getClassName();
return new $class;
}
And this is calling the constructor of Doctrine\ORM\EntityRepository:
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
therefore you will have to inject the arguments if you want to use your create method... I think it should be something like new $class($entityManager, Entity::class)
Related
Good afternoon,
I have this issue where the Admin Controller is not reading the method findBynom from the UserRepository even everything is included here is my code:
here is the UserRepository:
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ManagerRegistry;
/**
* #method User|null find($id, $lockMode = null, $lockVersion = null)
* #method User|null findOneBy(array $criteria, array $orderBy = null)
* #method User[] findAll()
* #method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* #param $type
* #return int|mixed|string
*/
public function findBynom($type)
{
return $this->createQueryBuilder('p')
->Where('p.fullname LIKE :type')
->setParameter('type','%'.$type.'%')
->getQuery()
->getResult()
;
}
public function findByMail($type)
{
return $this->createQueryBuilder('p')
->Where('p.email LIKE :type')
->setParameter('type','%'.$type.'%')
->getQuery()
->getResult()
;
}
// /**
// * #return User[] Returns an array of User objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->orderBy('u.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
public function findOneBySomeField($value): ?User
{
return $this->createQueryBuilder('u')
->andWhere('u.activation_token = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
}
and here is the AdminController:
<?php
namespace App\Controller;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use GuzzleHttp\Psr7\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
/**
* #Route("/admin", name="admin_")
*/
class AdminController extends AbstractController
{
/**
* Liste les utilisateur du side
* #Route("/", name="users", methods={"GET"})
*/
public function index(UserRepository $users){
return $this->render("admin/index.html.twig",[
'users'=> $users->findAll()
]);
}
/**
* #Route("/new", name="app_admin_new", methods={"GET", "POST"})
*/
public function new(Request $request, UserRepository $userRepository): Response
{
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $form->get('Photo')->getData();
$Filename = md5(uniqid()).'.'.$file->guessExtension();
$file->move(
$this->getParameter('images'),
$Filename
);
$user->setPhoto($Filename);
$userRepository->add($user);
return $this->redirectToRoute('app_admin_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('admin/new.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="app_admin_show", methods={"GET"})
*/
public function show(User $user): Response
{
return $this->render('admin/show.html.twig', [
'user' => $user,
]);
}
/**
* #Route("/{id}/edit", name="app_admin_edit", methods={"GET", "POST"})
*/
public function edit(Request $request, User $user, UserRepository $userRepository): Response
{
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$userRepository->add($user);
return $this->redirectToRoute('app_admin_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('admin/edit.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="app_admin_delete", methods={"POST"})
*/
public function delete(Request $request, User $user, UserRepository $userRepository): Response
{
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
$userRepository->remove($user);
}
return $this->redirectToRoute('app_admin_index', [], Response::HTTP_SEE_OTHER);
}
/**
* #Route("/{id}/ban", name="admin_ban", methods={"GET", "POST"})
*/
public function ban(Request $request, User $user, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('ban'.$user->getId(), $request->request->get('_token'))) {
$user->setIsBanned(true);
$entityManager->flush();
}
return $this->redirectToRoute('admin_index', [], Response::HTTP_SEE_OTHER);
}
/**
* #Route("/{id}/unban", name="admin_unban", methods={"GET", "POST"})
*/
public function unban(Request $request, User $user, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('unban'.$user->getId(), $request->request->get('_token'))) {
$user->setIsBanned(false);
$entityManager->flush();
}
return $this->redirectToRoute('admin_index', [], Response::HTTP_SEE_OTHER);
}
/**
* #Route("/searchuser", name="utilsearchuser")
*/
public function searchPlan($searchString): JsonResponse
{
$serializer = new Serializer([new ObjectNormalizer()]);
$repository = $this->getDoctrine()->getRepository(User::class);
$users = $repository->findBynom($searchString);
$data=$serializer->normalize($users);
return new JsonResponse($data);
}
}
thanks for your help!
adzdafzferfzfaefzrfzrgd a aefzk ff ae ae ak,er ear ae rea rak aer !
Most likely you didn't specify UserRepository in annotations to your entity. Just declare the repositoryClass parameter in your entity
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User
Then you can get an instance of your repository:
$users = $this->getDoctrine()->getRepository(User::class)->findBynom($searchString);
Or you can specify the UserRepository in an attribute of your method, after which the Dependency Injection Component will include it
/**
* #Route("/searchuser", name="utilsearchuser")
*/
public function searchPlan($searchString, UserRepository $userRepository): JsonResponse
{
$serializer = new Serializer([new ObjectNormalizer()]);
$users = $userRepository->findBynom($searchString);
$data=$serializer->normalize($users);
return new JsonResponse($data);
}
Im working on Symfony 4.2. I get this error
App\Entity\Shopcart object not found by the #ParamConverter annotation.
when I try to delete or edit an item on shopcard.I tried many solution but failed.I need your suggestions.
Here is my ShopcartController
<?php
namespace App\Controller;
use App\Entity\Shopcart;
use App\Form\ShopcartType;
use App\Repository\ProductRepository;
use App\Repository\ShopcartRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/shopcart")
*/
class ShopcartController extends AbstractController
{
/**
* #Route("/", name="shopcart_index", methods={"GET"})
*/
public function index(ShopcartRepository $shopcartRepository, ProductRepository $productRepository): Response
{
$slider=$productRepository->findBy([], ['name'=>'ASC'], 3); //slider doesn't exist hatası için ekledim
return $this->render('shopcart/index.html.twig', [
'shopcarts' => $shopcartRepository->getAllShops(),
'slider' => $slider, //slider doesn't exist hatası için ekledim
]);
}
/**
* #Route("/new", name="shopcart_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$shopcart = new Shopcart();
$form = $this->createForm(ShopcartType::class, $shopcart);
$form->handleRequest($request);
echo $submittedToken=$request->request->get('token');
if($this->isCsrfTokenValid('add-item', $submittedToken)) { //alınan tokenla uyuşuyorsa göre sepete ekliyor
if ($form->isSubmitted()) {
$entityManager = $this->getDoctrine()->getManager();
$user=$this->getUser();
$shopcart->setQuantity($request->request->get('quantity'));
$shopcart->setUserid($user->getid());
$entityManager->persist($shopcart);
$entityManager->flush();
return $this->redirectToRoute('shopcart_index');
}
}
return $this->render('shopcart/new.html.twig', [
'shopcart' => $shopcart,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="shopcart_show", methods={"GET"})
*/
public function show(Shopcart $shopcart): Response
{
return $this->render('shopcart/show.html.twig', [
'shopcart' => $shopcart,
]);
}
/**
* #Route("/{id}/edit", name="shopcart_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Shopcart $shopcart): Response
{
$form = $this->createForm(ShopcartType::class, $shopcart);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('shopcart_edit',['id'=>$shopcart->getId()]);
}
return $this->render('shopcart/edit.html.twig', [
'shopcart' => $shopcart,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="shopcart_delete", methods={"DELETE"})
*/
public function delete(Request $request, Shopcart $shopcart): Response
{
//methodu post ile değiştirip dene
if ($this->isCsrfTokenValid('delete'.$shopcart->getId(), $request->request->get('_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($shopcart);
$entityManager->flush();
}
return $this->redirectToRoute('shopcart_index');
}
/**
* #Route("/{id}/del", name="shopcart_del", methods={"DELETE"})
*/
public function del(Request $request, Shopcart $shopcart): Response
{
$em=$this->getDoctrine()->getManager();
$em->remove($shopcart);
$em->flush();
return $this->redirectToRoute('shopcart_index');
}
}
And here is ShopcartRepository.I use only getAllShops method.
<?php
namespace App\Repository;
use App\Entity\Shopcart;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use phpDocumentor\Reflection\Types\Integer;
/**
* #method Shopcart|null find($id, $lockMode = null, $lockVersion = null)
* #method Shopcart|null findOneBy(array $criteria, array $orderBy = null)
* #method Shopcart[] findAll()
* #method Shopcart[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ShopcartRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Shopcart::class);
}
// /**
// * #return Shopcart[] Returns an array of Shopcart objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('s')
->andWhere('s.exampleField = :val')
->setParameter('val', $value)
->orderBy('s.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?Shopcart
{
return $this->createQueryBuilder('s')
->andWhere('s.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
//LET JOIN WITH SQL
public function getAllShops():array
{
$conn = $this->getEntityManager()->getConnection();
$sql = '
SELECT p.name, p.price, s.*, u.id FROM shopcart s, product p, user u
WHERE s.productid = p.id and s.userid=u.id
';
$stmt=$conn->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
public function getUserShopCartTotal($userid): float
{
$em=$this->getEntityManager();
$query= $em->createQuery('
SELECT sum(p.price *s .quantity) as total
FROM App\Entity\Shopcart s, App\Entity\Product p
WHERE s.productid = p.id and s.userid=:userid
')
->setParameter('userid',$userid);
$result=$query->getResult();
if($result[0]['total']!=null){
return $result[0]["total"];
} else {
return 0;
}
}
public function getUserShopCartCount($userid): Integer
{
$em=$this->getEntityManager();
$query= $em->createQuery('
SELECT count(s.id) as shopcount
FROM App\Entity\Shopcart s
WHERE s.userid=:userid
')
->setParameter('userid',$userid);
$result=$query->getResult();
if($result[0]['shopcount']!=null){
return $result[0]["shopcount"];
} else {
return 0;
}
}
public function getUserShopCart($userid): array
{
$em=$this->getEntityManager();
$query= $em->createQuery('
SELECT p.name, p.price, s.quantity,s.productid, s.userid, (p.price * s.quantity) as total
FROM App\Entity\Shopcart s, App\Entity\Product p
WHERE s.productid = p.id and s.userid=:userid
')
->setParameter('userid',$userid);
return $query->getResult();
}
}
I think the problem is that you have two delete functions. The del function is a copy of the delete function. If you remove 'del' function your problem will be solved. The 'del' function override the route oh other functions.
I updated composer on my symfony project, and i could not continue to use my custom "SecurityRolesType".
Any idea to update it ? following, codes and errors.
Error : Notice: Array to string conversion 500 Internal Server Error - ContextErrorException
Here a stack screen capture http://s11.postimg.org/j6nlup4ar/screencapture_stack.jpg
Here a capture of rendered HTML http://s17.postimg.org/l6mmskmof/Screen_Shot_2015_09_28_at_10_41_19_PM.png
<?php
namespace MYP\UserBundle\Form\Type;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
use Sonata\AdminBundle\Admin\Pool;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class SecurityRolesType extends ChoiceType
{
protected $pool;
/**
* #param Pool $pool
*/
public function __construct(Pool $pool)
{
parent::__construct();
$this->pool = $pool;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
}
/**
* {#inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
parent::buildView($view, $form, $options);
$attr = $view->vars['attr'];
if (isset($attr['class']) && empty($attr['class'])) {
$attr['class'] = 'sonata-medium';
}
$view->vars['attr'] = $attr;
$view->vars['read_only_choices'] = $options['read_only_choices'];
//$view->vars['full_name'] = substr($view->vars['full_name'], 0, -2);
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$roles = array();
$rolesReadOnly = array();
$securityContext = $this->pool->getContainer()->get('security.context');
// get roles from the Admin classes
foreach ($this->pool->getAdminServiceIds() as $id) {
try {
$admin = $this->pool->getInstance($id);
} catch (\Exception $e) {
continue;
}
$isMaster = $admin->isGranted('MASTER');
$securityHandler = $admin->getSecurityHandler();
// TODO get the base role from the admin or security handler
$baseRole = $securityHandler->getBaseRole($admin);
foreach ($admin->getSecurityInformation() as $role => $permissions) {
$role = sprintf($baseRole, $role);
if ($isMaster) {
// if the user has the MASTER permission, allow to grant access the admin roles to other users
$roles[$role] = $role;
} elseif ($securityContext->isGranted($role)) {
// although the user has no MASTER permission, allow the currently logged in user to view the role
$rolesReadOnly[$role] = $role;
}
}
}
// get roles from the service container
foreach ($this->pool->getContainer()->getParameter('security.role_hierarchy.roles') as $name => $rolesHierarchy) {
if ($securityContext->isGranted($name)) {
$roles[$name] = $name . ': ' . implode(', ', $rolesHierarchy);
foreach ($rolesHierarchy as $role) {
if (!isset($roles[$role])) {
$roles[$role] = $role;
}
}
}
}
$resolver->setDefaults(array(
'choices' => function (Options $options, $parentChoices) use ($roles) {
return empty($parentChoices) ? $roles : array();
},
'read_only_choices' => function (Options $options) use ($rolesReadOnly) {
return empty($options['choices']) ? $rolesReadOnly : array();
},
'data_class' => null,
//'expanded' => true
));
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return 'choice';
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'myp_security_roles';
}
}
Finally, the fix you can see commented was working after some cache deletion.
$view->vars['full_name'] = substr($view->vars['full_name'], 0, -2);
I'm building a multi-tenant application. In Symfony1 I would restrict access to data by accessing the user details and extending the createQuery function:
class PersonTable extends Doctrine_Table{
public function createQuery($alias = '')
{
$query = parent::createQuery($alias);
try {
$user = sfContext::getInstance()->getUser();
}catch(Exception $e){
if ($e->getMessage() == 'The "default" context does not exist.'){
return $query;
}else{
throw $e;
}
}
if ($user->hasGroup('Team1')){
//all good
}else if ($user->hasGroup('Team2')){
$user_id = $user->getGuardUser()->getStaff()->getId();
$alias = $query->getRootAlias();
$time = date('Y-m-d H:i:s',time());
$query->andWhere("$alias.type='type1' and pe.assigned_psw_id");
}
$query->orderBy('name asc');
return $query;
}
}
I know there are downsides to accessing the user object through sfContext in sf1, but this method seemed superior to others, as you can't "forget" to secure a controller against wrong user access.
How can I achieve the same in Symfony2?
I have solved this problem the following way.
Standardise how EntityRepository is fetched among controllers:
public function getUserRestrictedRepository($entity, $em = null )
{
$securityContext = $this->get( 'security.context' );
if (!$em){
$em = $this->getDoctrine()->getManager();
}
return $em
->getRepository( 'MyBundle:' . $entity )
->setSecurityContext( $securityContext );
}
Add a trait to provide queries with injected security query:
trait UserRestrictedEntityRepository {
private $securityContext;
/**
* #return mixed
*/
public function getSecurityContext()
{
return $this->securityContext;
}
/**
* #param mixed $securityContext
*/
public function setSecurityContext($securityContext)
{
$this->securityContext = $securityContext;
return $this;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->getSecurityContext()->getToken()->getUser();
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
function secureQueryWithUser($alias, $qb)
{
$qb->where("1=0");
}
function appendOrderBy($qb, $orderBy)
{
$first = true;
foreach ($orderBy as $field => $dir) {
if (!$dir) $dir = 'asc';
if ($first) {
$qb->orderBy('c.' . $field, $dir);
$first = false;
}else{
$qb->addOrderBy('c.' . $field, $dir);
}
}
}
public function createUnrestrictedQueryBuilder($alias)
{
return parent::createQueryBuilder($alias);
}
/**
* Creates a new QueryBuilder instance that is prepopulated for this entity name.
*
* #param string $alias
*
* #return QueryBuilder
*/
public function createQueryBuilder($alias, $indexBy=NULL)
{
if ($this->getUser()) {
$qb = $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
if (isset($this->defaultOrder) && $this->defaultOrder){
$this->appendOrderBy($qb, $this->defaultOrder);
}
if ($this->getUser()->isSuperAdmin()){
return $qb;
}else{
return $this->secureQueryWithUser($alias, $qb);
}
}else{
throw new Exception('Run setUser() before querying ' . $this->getName() .' model.');
}
}
/**
* Finds all entities in the repository.
*
* #return array The entities.
*/
public function findAll()
{
return $this->findBy(array());
}
/**
* Finds entities by a set of criteria.
*
* #param array $criteria
* #param array|null $orderBy
* #param int|null $limit
* #param int|null $offset
*
* #return array The objects.
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$qb = $this->createQueryBuilder('c');
foreach ($criteria as $fkey => $fval){
$qb->andWhere($fkey.' = :'.$fval);
}
if ($limit){
$qb->setMaxResults($limit);
}
if ($offset){
$qb->setFirstResult($offset);
}
$query = $qb->getQuery();
return $query->getResult();
}
}
Implement query additions based on user access in the EnityRepository
class FarmerRepository extends EntityRepository
{
use UserRestrictedEntityRepository;
private $name = 'Farmer';
private $defaultOrder = array('name' => 'asc');
function secureQueryWithUser($alias, $qb)
{
if ($this->getSecurityContext()->isGranted( 'ROLE_CLINIC_ADMIN' )) {
return $qb
->innerJoin("$alias.vet", 'v')
->innerJoin("v.clinic", "cl")
->innerJoin("cl.VetsOfClinic", "vc")
->andWhere('vc.user_id= :userid')
->setParameter('userid', $this->getUser()->getId());
}else if ($this->getSecurityContext()->isGranted( 'ROLE_VET' )){
return $qb
->innerJoin("$alias.vet", 'v')
->andWhere('v.user_id= :userid')
->setParameter('userid', $this->getUser()->getId());
}else{
return $qb
->where("$alias.user_id= :userid")
->setParameter('userid', $this->getUser()->getId());
}
}
}
I am currently working on a symfony2.3 project with doctrine2 trying to implement personal translations management in the Sonata backend.
Translations are based on the doctrine2 translatable behaviour model: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md
and more precisely Personal Translations.
The admin form is using the symfony2.3 version of TranslatedFieldType.php.
My entity class is as follows:
<?php
namespace Hr\OnlineBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #Gedmo\TranslationEntity(class="Hr\OnlineBundle\Entity\CategoryTranslation")
*/
class Category
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #Gedmo\Translatable
* #ORM\Column(length=64)
*/
private $title;
/**
* #Gedmo\Translatable
* #ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* #ORM\OneToMany(
* targetEntity="CategoryTranslation",
* mappedBy="object",
* cascade={"persist", "remove"}
* )
*/
private $translations;
public function __construct()
{
$this->translations = new ArrayCollection();
}
public function getTranslations()
{
return $this->translations;
}
public function addTranslation(CategoryTranslation $t)
{
if (!$this->translations->contains($t)) {
$this->translations[] = $t;
$t->setObject($this);
}
}
public function getId()
{
return $this->id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getDescription()
{
return $this->description;
}
public function __toString()
{
return $this->getTitle();
}
}
The related Personal Translation class is this:
<?php
namespace Hr\OnlineBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation;
/**
* #ORM\Entity
* #ORM\Table(name="category_translations",
* uniqueConstraints={#ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "locale", "object_id", "field"
* })}
* )
*/
class CategoryTranslation extends AbstractPersonalTranslation
{
/**
* Convinient constructor
*
* #param string $locale
* #param string $field
* #param string $value
*/
public function __construct($locale, $field, $value)
{
$this->setLocale($locale);
$this->setField($field);
$this->setContent($value);
}
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="translations")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;
}
The admin form class is this:
<?php
namespace Hr\OnlineBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Hr\OnlineBundle\Form\Type\TranslatedFieldType;
class CategoryAdmin extends Admin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('title', 'translatable_field', array(
'field' => 'title',
'personal_translation' => 'Hr\OnlineBundle\Entity\CategoryTranslation',
'property_path' => 'translations',
))
->end();
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
;
}
}
The admin form is using the following form type class:
<?php
namespace Hr\OnlineBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber;
class TranslatedFieldType extends AbstractType
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
if(! class_exists($options['personal_translation']))
{
Throw new \InvalidArgumentException(sprintf("Unable to find personal translation class: '%s'", $options['personal_translation']));
}
if(! $options['field'])
{
Throw new \InvalidArgumentException("You should provide a field to translate");
}
$subscriber = new addTranslatedFieldSubscriber($builder->getFormFactory(), $this->container, $options);
$builder->addEventSubscriber($subscriber);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'remove_empty' => true,
'csrf_protection'=> false,
'field' => false,
'personal_translation' => false,
'locales'=>array('en', 'fr', 'de'),
'required_locale'=>array('en'),
'widget'=>'text',
'entity_manager_removal'=>true,
));
}
public function getName()
{
return 'translatable_field';
}
}
and the corresponding Event Listener:
<?php
namespace Hr\OnlineBundle\Form\EventListener;
use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\FormError;
class AddTranslatedFieldSubscriber implements EventSubscriberInterface
{
private $factory;
private $options;
private $container;
public function __construct(FormFactoryInterface $factory, ContainerInterface $container, Array $options)
{
$this->factory = $factory;
$this->options = $options;
$this->container = $container;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that we want to listen on the form.pre_set_data
// , form.post_data and form.bind_norm_data event
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::POST_BIND => 'postBind',
FormEvents::BIND => 'bindNormData'
);
}
private function bindTranslations($data)
{
//Small helper function to extract all Personal Translation
//from the Entity for the field we are interested in
//and combines it with the fields
$collection = array();
$availableTranslations = array();
foreach($data as $Translation)
{
if(strtolower($Translation->getField()) == strtolower($this->options['field']))
{
$availableTranslations[ strtolower($Translation->getLocale()) ] = $Translation;
}
}
foreach($this->getFieldNames() as $locale => $fieldName)
{
if(isset($availableTranslations[ strtolower($locale) ]))
{
$Translation = $availableTranslations[ strtolower($locale) ];
}
else
{
$Translation = $this->createPersonalTranslation($locale, $this->options['field'], NULL);
}
$collection[] = array(
'locale' => $locale,
'fieldName' => $fieldName,
'translation' => $Translation,
);
}
return $collection;
}
private function getFieldNames()
{
//helper function to generate all field names in format:
// '<locale>' => '<field>|<locale>'
$collection = array();
foreach($this->options['locales'] as $locale)
{
$collection[ $locale ] = $this->options['field'] .":". $locale;
}
return $collection;
}
private function createPersonalTranslation($locale, $field, $content)
{
//creates a new Personal Translation
$className = $this->options['personal_translation'];
return new $className($locale, $field, $content);
}
public function bindNormData(FormEvent $event)
{
//Validates the submitted form
$data = $event->getData();
$form = $event->getForm();
$validator = $this->container->get('validator');
foreach($this->getFieldNames() as $locale => $fieldName)
{
$content = $form->get($fieldName)->getData();
if(
NULL === $content &&
in_array($locale, $this->options['required_locale']))
{
$form->addError(new FormError(sprintf("Field '%s' for locale '%s' cannot be blank", $this->options['field'], $locale)));
}
else
{
$Translation = $this->createPersonalTranslation($locale, $fieldName, $content);
$errors = $validator->validate($Translation, array(sprintf("%s:%s", $this->options['field'], $locale)));
if(count($errors) > 0)
{
foreach($errors as $error)
{
$form->addError(new FormError($error->getMessage()));
}
}
}
}
}
public function postBind(FormEvent $event)
{
//if the form passed the validattion then set the corresponding Personal Translations
$form = $event->getForm();
$data = $form->getData();
$entity = $form->getParent()->getData();
foreach($this->bindTranslations($data) as $binded)
{
$content = $form->get($binded['fieldName'])->getData();
$Translation = $binded['translation'];
// set the submitted content
$Translation->setContent($content);
//test if its new
if($Translation->getId())
{
//Delete the Personal Translation if its empty
if(
NULL === $content &&
$this->options['remove_empty']
)
{
$data->removeElement($Translation);
if($this->options['entity_manager_removal'])
{
$this->container->get('doctrine.orm.entity_manager')->remove($Translation);
}
}
}
elseif(NULL !== $content)
{
//add it to entity
$entity->addTranslation($Translation);
if(! $data->contains($Translation))
{
$data->add($Translation);
}
}
}
}
public function preSetData(FormEvent $event)
{
//Builds the custom 'form' based on the provided locales
$data = $event->getData();
$form = $event->getForm();
// During form creation setData() is called with null as an argument
// by the FormBuilder constructor. We're only concerned with when
// setData is called with an actual Entity object in it (whether new,
// or fetched with Doctrine). This if statement let's us skip right
// over the null condition.
if (null === $data)
{
return;
}
foreach($this->bindTranslations($data) as $binded)
{
$form->add($this->factory->createNamed(
$binded['fieldName'],
$this->options['widget'],
$binded['translation']->getContent(),
array(
'label' => $binded['locale'],
'required' => in_array($binded['locale'], $this->options['required_locale']),
'auto_initialize' => false,
)
));
}
}
}
So, the form shows the three fields for the three specified locales, which is fine, but when submitting the form the following error occurs:
FatalErrorException: Error: Call to a member function getField() on a non-object in C:\wamp\www\hronline\src\Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber.php line 47
in C:\wamp\www\hronline\src\Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber.php line 47
at ErrorHandler->handleFatal() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Debug\ErrorHandler.php line 0
at AddTranslatedFieldSubscriber->bindTranslations() in C:\wamp\www\hronline\src\Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber.php line 136
at AddTranslatedFieldSubscriber->postBind() in C:\wamp\www\hronline\app\cache\dev\classes.php line 1667
at ??call_user_func() in C:\wamp\www\hronline\app\cache\dev\classes.php line 1667
at EventDispatcher->doDispatch() in C:\wamp\www\hronline\app\cache\dev\classes.php line 1600
at EventDispatcher->dispatch() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\EventDispatcher\ImmutableEventDispatcher.php line 42
at ImmutableEventDispatcher->dispatch() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 631
at Form->submit() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 552
at Form->submit() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 645
at Form->bind() in C:\wamp\www\hronline\vendor\sonata-project\admin-bundle\Sonata\AdminBundle\Controller\CRUDController.php line 498
at CRUDController->createAction() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2844
at ??call_user_func_array() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2844
at HttpKernel->handleRaw() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2818
at HttpKernel->handle() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2947
at ContainerAwareHttpKernel->handle() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2249
at Kernel->handle() in C:\wamp\www\hronline\web\app_dev.php line 28
at ??{main}() in C:\wamp\www\hronline\web\app_dev.php line 0
It appears that on this line of the event listener AddTranslatedFieldSubscriber:
if(strtolower($Translation->getField()) == strtolower($this->options['field']))
the $Translation variable comes as a string (e.g. string 'Lorem' (length=5)) instead of an object.
For some reason the form data is converted to an array of strings instead of an array of objects of type CategoryTranslation.
What could be the reason for this? Thanks!
Do yourself a favour and don't use the Gedmo Translatable behaviour. It is very buggy, slow and has a lot of weird edge cases. I recommend using the KNP translatable behaviour which is quite good (requires PHP 5.4) or the Prezent Translatable which is similar but can work on PHP 5.3. Disclaimer: I wrote that last one. It's beta but works fine. I just added the documentation for it.
You can use the a2lix bundle to integrate it all in Sonata Admin.