Symfony 3.4/Doctrine 2 one-to-one association error - php

I have two entities - User and Cart. They're supposed to have a one-to-one association, created by the following:
User.php:
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\OneToOne(targetEntity="Cart", mappedBy="user")
*/
private $cart;
// other things not related to the association
}
Cart.php:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Class Cart
* #package AppBundle\Entity
*
* #ORM\Entity
* #ORM\Table(name="Cart")
*/
class Cart
{
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="cart")
*/
private $user;
// other things not related to the association
}
I create the cart and persist all entities when someone attempts to view their cart for the first time:
CartController.php:
public function showCartAction()
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$user = $this->getUser();
$cart = $user->getCart();
if ($cart) {
$items = $cart->getCartItems();
} else {
$cart = new Cart();
$user->setCart($cart);
$em = $this->getDoctrine()->getManager();
$em->persist($cart);
$em->persist($user);
$em->flush();
$items = $cart->getCartItems();
}
// other unrelated things in the controller
}
With this, carts are created, but there's no association between User and Cart:
user_id should have the user's id.
Any ideas on why this isn't working? And is it possible to simply do something like:
public function __construct()
{
$this->cart = new Cart();
}
In my User entity? Would that enforce the association? Or is that the wrong way to go?

You probably want to ensure that a cart can not bet created without a user being associated with it. Unlike fields, associations are nullable by default. You have to add a JoinColumn annotation for this.
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="cart")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
The other thing is that since you have a bi-directional association you have to make sure that when calling $user->setCart($cart) this will also add the user to the cart, e.g. like this:
public function setCart(Cart $cart): void
{
$this->cart = $cart;
$cart->setUser($this);
}
Since the cart can not be created without a user you could also make the user a constructor argument instead:
public function __construct(User $user)
{
$this->user = $user;
$user->setCart($this);
}
This way you can omit the setUser() method on the cart ensuring that no one switches the cart owner by accident.
With the latter one you might not even have to link back to the user, since the cart is the owning side, but to ensure that both entities have a consistent state I would always do this, when you have a bi-directional association.

Related

Symfony / Doctrine EntityListener doesn't work

I've a problem with an EntityListener... My EntityListener is not called, he doesn't work and I don't know why ! I use Symfony 3.4 and Doctrine 2.5.
So, my Entity :
<?php
namespace TelecomlineBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* LigneTelecom
*
* #ORM\Table(name="LIGNETELECOM")
* #ORM\Entity(repositoryClass="TelecomlineBundle\Repository\LigneTelecomRepository")
* #ORM\EntityListeners({"TelecomlineBundle\Doctrine\Listener\LigneTelecomListener"})
*/
class LigneTelecom
{
// etc ...
}
My EntityListener :
<?php
namespace TelecomlineBundle\Doctrine\Listener;
use TelecomlineBundle\Entity\LigneTelecom;
use TelecomlineBundle\Service\myService;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
class LigneTelecomListener
{
/** #var myService $myService */
private $myService;
public function __construct(myService $myService)
{
$this->myService = $myService;
}
/**
* #ORM\PostUpdate()
*
* #param LigneTelecom $ligneTelecom
* #param LifecycleEventArgs $eventArgs
*/
public function postUpdate(LigneTelecom $ligneTelecom, LifecycleEventArgs $eventArgs)
{
$this->myService->calculate($ligneTelecom);
}
// etc ...
My service.yml :
telecomline.doctrine.lignetelecom_listener:
class: "TelecomlineBundle\Entity\LigneTelecom"
arguments: ["#telecomline.porte"]
tags:
- { name: doctrine.orm.entity_listener }
If anyone have a solution, I block on that since 3 hours :'(
you should specify which events subscriber wants to listen to
you use long form of definition or inherit from EventSubscriber and override getSubscribedEvents
The Symfony doc is not correct. You don't need a config in services.yaml. Just put an annotation to the entity class like shown on Doctrine docs.
<?php
namespace MyProject\Entity;
use App\EventListener\UserListener;
#[Entity]
#[EntityListeners([UserListener::class])]
class User
{
// ....
}

ManyToMany itemOperations "access_control"

Thats the code from the docu
// https://api-platform.com/docs/core/security/#security
itemOperations={
"get"={"access_control"="is_granted('ROLE_USER') and object.owner == user"}
}
how can i get that realized with many to many, i tried many different expressions but everytime i get a error.
<?php
// api/src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Secured resource.
*
* #ApiResource(
* itemOperations={
* "get"={"access_control"="is_granted('ROLE_USER') and object.users == user"}
* }
* )
* #ORM\Entity
*/
class Book
{
// ...
/**
* #var User The owner
*
* #ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="book", cascade={"persist"})
*/
public $users;
// ...
}
nYou cant in those cases where the target relation is a collection. In this case, users collection.
For these cases, you should create a subscriber with PRE_SERIALIZE event and throw Access Denied exception there.
You have to do something like this. As you say you have a ManyToMany relation, I guess that you have an intermediate entity between book and user, so you should use that repository for find user <-> book then.
<?php
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\User;
use App\Entity\Book;
use App\Repository\UserRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class ChatMessagePreSerializeSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
private $userRepository;
private $authorizationChecker;
public function __construct(
TokenStorageInterface $tokenStorage,
UserRepository $userRepository,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository;
$this->authorizationChecker = $authorizationChecker;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['bookPreSerialize', EventPriorities::PRE_SERIALIZE],
];
}
public function bookPreSerialize(GetResponseForControllerResultEvent $event)
{
$book = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$book instanceof Book || (Request::METHOD_GET !== $method)) {
return;
}
$currentUser = $this->tokenStorage->getToken()->getUser();
if (!$currentUser instanceof User)
return;
$user = $this->userRepository->findOneBy(['id' => $currentUser->getId(), 'book' => $book]);
if (!$user instanceof User)
throw new AccessDeniedHttpException();
}
}
Here is something I did for a resource that is ManytoOne related to intermediate entity Events ManytoOne related to one Organizer, with Users ManyToMany related to Organizers (collection).
I transform the collection to Array and use "in" operator to compare data. For a more sophisticated operation you should look at Doctrine Extension as it's describe in API Platform doc.
#[ApiResource(
operations: [
new GetCollection(),
new Post(),
new Get(security: "object.getEvent().getOrganizer() in user.getOrganizers().toArray()"),
new Patch(),
new Delete()
]
)]

Laravel : send pivot tables data in view

I'm new to Laravel 5 and I have some difficulties with pivot tables, controllers and repositories.
I have the tables 'users', 'sites', 'site_user', and here is what I have now :
App\Models\User
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
protected $table = 'users';
public function sites()
{
return $this->belongsToMany('App\Models\Site')
->withPivot('site_id', 'user_id', 'relation');
}
}
App\Models\Site
class Site extends Model {
protected $table = 'sites';
public function user()
{
return $this->belongsToMany('App\Models\User')
->withPivot('site_id', 'user_id', 'relation');
}
}
App\Repositories\SiteRepository
<?php namespace App\Repositories;
use App\Models\Site, App\Models\User;
class SiteRepository extends BaseRepository
{
/**
* The User instance.
*
* #var App\Models\User
*/
protected $user;
/**
* Create a new SiteRepository instance.
*
* #param App\Models\Site $site
* #return void
*/
public function __construct (Site $sites, User $user)
{
$this->model = $sites;
$this->user = $user;
}
/**
* Get sites collection paginate.
*
* #param int $n
* #return Illuminate\Support\Collection
*/
public function index($n)
{
return $this->model
->latest()
->paginate($n);
}
App\Http\Controllers\SiteController
<?php namespace App\Http\Controllers;
use App\Repositories\SiteRepository;
use App\Repositories\UserRepository;
use App\Http\Requests\SiteCreateRequest;
use App\Http\Requests\SiteUpdateRequest;
use App\Models\Site;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class SiteController extends Controller {
/**
* The SiteRepository instance.
*
* #var App\Repositories\SiteRepository
*/
protected $site_gestion;
/**
* The UserRepository instance.
*
* #var App\Repositories\UserRepository
*/
protected $user_gestion;
/**
* Create a new SiteController instance.
*
* #param App\Repositories\SiteRepository $site_gestion
* #param App\Repositories\UserRepository $user_gestion
* #return void
*/
public function __construct (SiteRepository $site_gestion, UserRepository $user_gestion)
{
$this->site_gestion = $site_gestion;
$this->user_gestion = $user_gestion;
$this->middleware('admin');
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index(SiteRepository $site_gestion)
{
//$counts = $this->site_gestion->counts();
$sites = $site_gestion->index(25);
$links = $sites->render();
return view('back.sites.index', compact('sites'));
}
views\back\sites\table.blade.php
#foreach ($sites as $site)
[...some code...]
#endforeach
What I want to do is to get all the sites of the logged in user. I've tried many things, but none of them are working. And I'm still not sure where to put the code, repository or controller...
I've read tutorials about pivot in Laravel, and I've tried with some things like this in the repo, but it doesn't work...
$user = $this->user->find(auth()->user()->id); //This line is working
foreach ($user->sites as $site) {
return $site
->latest()
->paginate($n);
}
If you want all sites of a logged user simply do it like this:
$sites = Auth::user()->sites;
That's all you need to do to get to these sites. If you want to use query and pagination try like this:
$sites = Auth::user()->sites()->latest()->paginate($n);
So what you've done seems pretty close.
So you pretty much have it, when iterating over the sites they should be instances of the site model.
$user = auth()->user(); // This is a way of saying your first line without a db query for the user
foreach ($user->sites as $site) {
// each site in here is a site model
$site->pivot->relation;
}
The only other thing that looks slightly strange is how you've defined the pivots. Generally when calling withPivot you wouldn't define the joining ids, if you wish to vary from the defaults you can pass it as an argument to the belongsToMany like so.
return $this->belongsToMany('App\Models\User', 'site_user', 'user_id', 'site_id')
->withPivot('relation');

Symfony2 - FOSUserBundle set user entity field after registration

I have a user entity with firstname,lastname and url attributes, extending the FOSUserBundle User. I want to store in the url attribute the value of fistname and lastname as one strig.
Some people suggested that i should use a listener and a service, so i 've made:
The event seems to work after registration, but i can't find a way to change my user url...
servises.yml
services:
kernel.listener.RegistrationListener:
class: Apana\Bundle\MainBundle\EventListener\RegistrationListener
tags:
- { name: kernel.event_listener, event: fos_user.registration.completed, method: onUserRegistration }
And in RegistrationListener.php :
<?php
namespace Apana\Bundle\MainBundle\EventListener;
use Apana\Bundle\MainBundle\Entity\User;
use Symfony\Component\Security\Core\SecurityContext;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\UserEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
class RegistrationListener
{
public function onUserRegistration()
{
$user = new User();
$ap= = $user->getFirstname();
echo($ap);exit();
}
}
If I understand correctly, what you want to do is a slug with the firstname and lastname field.
First and last name to be used as slug must also be sanitizated to replace accented characters, spaces, etc..
To do everything automatically, you can use the Doctrine Extension, in particular Sluggable: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sluggable.md
This is an example using annotations for what you need:
<?php
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(length=64, unique=false)
*/
private firstname;
/**
* #ORM\Column(length=64, unique=false)
*/
private lastname;
/**
* #Gedmo\Slug(fields={"firstname", "lastname"}, separator="-")
* #ORM\Column(length=128, unique=true)
*/
private $slug;
public function __construct()
{
parent::__construct();
// your own logic
}
//... setter and getter...
}
Why you don't use UserManager for that instead of a listener? I would override the FOSUser register with my own and do something like:
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->createUser();
$user->setUrl("any data");
It's more easy and less complicated at least for me
Ok, i 've solved my problem with an eventlistener and the FOSUserBundle UserEvent
<?php
namespace Apana\Bundle\MainBundle\EventListener;
use FOS\UserBundle\Event\UserEvent;
class RegistrationListener
{
public function onUserRegistration(UserEvent $event)
{
$user = $event->getUser();
$url = strtolower($user->getFirstname().".".$user->getLastname());
$user->setUrl($url);
}
}

Doctrine and Symfony2 ManyToMany: Association refers to inverse side that doesn't exist error

Im trying to establish a many to many relationship between two Entities
All I need is the exact same code that is documented here:
http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-bidirectional
here is the example code in case you don't want to open the link
<?php
/** #Entity **/
class User
{
// ...
/**
* #ManyToMany(targetEntity="Group", inversedBy="users")
* #JoinTable(name="users_groups")
**/
private $groups;
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
/** #Entity **/
class Group
{
// ...
/**
* #ManyToMany(targetEntity="User", mappedBy="groups")
**/
private $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
Here is my code:
//My User entity
namespace Gabriel\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class User extends BaseUser
{
//...
/**
* #ORM\ManyToMany(targetEntity="Gabriel\UploadBundle\Entity\Image", mappedBy="imageowner")
*/
protected $ownedimage;
public function __construct()
{
$this->ownedimage = new \Doctrine\Common\Collections\ArrayCollection();
}
//...
}
//My Image entity
namespace Gabriel\UploadBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Image
{
/**
* #ORM\ManyToMany(targetEntity="Gabriel\UserBundle\Entity\User", inversedBy="ownedimage")
* #ORM\JoinTable(name="imageowner_ownedimage")
*/
protected $imageowner;
public function __construct()
{
$this->imageowner = new \Doctrine\Common\Collections\ArrayCollection();
}
}
It triggers this error:
The association Gabriel\UploadBundle\Entity\Image#imageowner refers to
the inverse side field Gabriel\UserBundle\Entity\User#ownedimage which
does not exist.
I have been searching for hours I would appreciate if someone had an idea
Why a ManyToMany relationship. For me it's a OneToMany relationship.

Categories