In my application, I have to allow my users to comments two kind of entities : Recipe and News.
I want to know what is the best practice for doing it.
A Comment object with a ref_id(integer) and ref(string) that I manage manually or a commun interface between my to entity and something like #ManyToMany(targetEntoty="MyInterfaceHere") in my Comment object ?
Thanks you for your answers
well a good implementation would be if Recipes and News extend an abstract class
use Doctrine\ORM\Mapping\MappedSuperclass;
/**
* Abstract base class
*
* #MappedSuperclass
*/
abstract class EntityWithComments {
/**
*
*#ORM(many-to-bla)
*/
private $comments;
public function addComment(){...};
public function removeComment(){...};
public function getComments(){...};
...
and your classes extend it, for example Recipe:
class Recipe extends EntityWithComments { ...
so this way you can
$recipe->addComment($comment);
$news->addComment($comment);
straightforward ...
Related
I have a repository class for photos:
use Imagine\Image\ImageInterface;
use Imagine\Image\ImagineInterface;
use Imagine\Image\BoxInterface;
class PhotoRepository extends ServiceEntityRepository
{
protected $imagineInterface;
protected $mode;
protected $box;
public function __construct(ImagineInterface $imagineInterface,
BoxInterface $box,
$mode = ImageInterface::THUMBNAIL_OUTBOUND)
{
$this->imagineInterface = $imagineInterface;
$this->$box = $box;
$this->mode = $mode;
}
I am getting the typical Cannot autowire service "App\Repository\PhotoRepository": argument "$box" of method "__construct()" references interface "Imagine\Image\BoxInterface" but no such service exists. Did you create a class that implements this interface?
The Imagine\ImageBox class clearly exists in my vendor folder and implements the BoxInterface, it starts out as follows:
namespace Imagine\Image;
use Imagine\Exception\InvalidArgumentException;
/**
* A box implementation
*/
final class Box implements BoxInterface
{
/**
* #var integer
*/
private $width;
Here is a picture of my folder structure, you can see that this Box class is there and that it implements BoxInterface:
I'm stuck because it says that the service doesn't exist but you can see that it does.
Any help is greatly appreciated.
To reply your question regarding working with interfaces, check this section of the docs: https://symfony.com/doc/current/service_container/autowiring.html#working-with-interfaces
However you're misunderstanding the purpose of services. Imagine's BoxInterface is by no means a service and shouldn't be declared as one. A service is needed only when you need only one instance of it through your whole application.
BoxInterface just describes a coordinates of a picture, therefore there will be as many instances as you need pictures instances.
Just create for example $box = new Imagine\Image\Box(50, 50); when you need a box.
I want to create this tables description in symfony:
GeneralAssignment:
id (int)
name (text)
course (Course)
type (1|2)
assignment (Assignment1 if type==1|Assignment2 if type==2)
Course:
id (int)
name (text)
Assignment1:
id (int)
general_assignment (GeneralAssignment)
Assignment2:
id (int)
general_assignment (GeneralAssignment)
So the best solution is to use this relation schema:
[Simple relation ManyToOne] (Course) : Course.id <------> GeneralAssignment.course (GeneralAssignment)
And
[JOINED Ineritence type] (GeneralAssignment) : GeneralAssignment.type [1=Assignment1|2=Assignment2]
So I will get this class model (sorry for this bad photo):
Problem
I used to create this model because I want to firstly, create the Course, then create the GeneralAssignment using the course id and finally, create the specific assignment using the general assignment's id
The problem is, I can't create a relation doctrine relation between Assignment1 and GeneralAssignment using Assignment1.general_assignment : "Cannot instantiate abstract class AssignmentGeneral". (the same thing between Assignment2 and AssignmentGeneral).
I need the Assignment1.assignment_general attribute because i'm going to use Assignment1 and Assignment2 classes later and if I can't know the general_assignment id, I can't even know the assignment general informations because of the inheritence from AssignmentGeneral to the specific Assignment classes (Assignment1, Assignment2)
The AssignmentGeneral is an abstract class because of the DiscriminatorMap requires that the class should be abstract.
So what to should I do ?
I can't set the AssignmentGeneral from "abstract class" to "class" and I can't create a relation between Assignment1 (or Assignment2) and GeneralAssignment in Assignment1
PS : These class names are not the real ones, but they are similiar to my real problem. Just to get it simple, I exposed this simple problem.
Thank you very much for each answer
Best regards
[Edit]
I'm adding the AssignmentGeneral declaration
/**
*
* #ORM\Table(name="assignment_general")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="integer")
* #ORM\MappedSuperclass
* #ORM\DiscriminatorMap({
1 = "Assignment1",
* 2 = "Assignment2"
* })
*/
abstract class StatisticsGeneralAbstract
{
//...
}
And also:
/**
*
* #ORM\Table(name="assignment1")
*
*/
class Assignment1 extends AssignmentGeneral
{
//...
}
/**
*
* #ORM\Table(name="assignment2")
*
*/
class Assignment2 extends AssignmentGeneral
{
//...
}
Class table inheritance ('JOINED') doesn't require the parent class to be abstract.
Besides, one of the benefits of inheritance is to get the parent's properties when you instantiate a child class. So you don't have to instantiate both, just instantiate Assignment1 or Assignment2.
Add relations to both of them in the Course entity and an assignmentType property so that you know which one it's linked to.
For legacy reasons I have table that is used for many purposes. Only subset of rows is relevant to Entity I'm writing. Criteria is simple, just check 'project' field for specific value.
Instead of reimplementing find, findAll, findByName, findByID, findBy.... Just notify doctrine to append single condition to them all. Is that possible without reimplementing each and every find* ?
Or maybe it can be done on lover level still?
UPDATE:
Reworked question, to specify what kind of solution would be acceptable.
An available easy-to-use solution is to create a Repository with your custom find function.
Then, if all your entities has a specific Repository, make them (Repository) extending from yours (which contains the custom find method), otherwise (you doesn't have a Repository per entity), assign the repository to all your entities with the repositoryClass option of the #ORM\Entity annotation like :
#ORM\Entity(repositoryClass="YourMainRepository")
Otherwise, if you doesn't want put any repository in your entities, override the whole default repository and customise the find method.
I already used the last option because of a specific need, also I invite you to see the following question :
Abstract repository and #Entity annotation inheritance
Look at the solution wich contains a gist of all required steps for override the default doctrine repository.
See Custom Repository classes
Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Phrase
*
* #ORM\Table(name="User")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
.............................................................
.............................................................
.............................................................
Your Repository:
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/** For example **/
public function getByName($name)
{
$qb = $this->createQueryBuilder('u')
->where('u.name = :name')->setParameter('name', $name)
->andWhere('u.lastname LIKE :name')->setParameter('lastname', '%'.$name.'%');
$query = $qb->getQuery();
return $query->getResult();
}
}
In Your Controller:
/**
* #Route("/", name="index")
*/
public function indexAction(Request $request)
{
$userRepository = $this->getDoctrine()->getRepository('AppBundle:User');
$userName = $userRepository->getByName($name);
..................................................................................
..................................................................................
How do you share an entity between multiple bundles with different relationships?
For example both the ZooAnimalBundle and FarmAnimalBundle need a User Entity. A third Bundle AccountUserBundle has the User Entity.
In both the Zoo and Farm AnimalBundles I create a User Entity like so:
use Account\UserBundle\Entity\User as BaseUser;
class User extends BaseUser
{
}
I then have a Hospital entity in Zoo:
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_zoo_animals")
*/
protected $users;
And a Room entity in Farm:
class Room {
/**
* #ORM\ManyToMany(targetEntity="Farm\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_farm_animals")
*/
protected $users;
Everything works so far in that I can call Zoo.Room->getUsers() or Farm.Hospital->getUsers()
However the problem is I'm not sure on how to set up the inverse relationship in their respective User entities.
If for example I update the FarmAnimal User Entity and run doctrine:generate:entities
/**
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Room", mappedBy="users", cascade={"persist"})
*/
protected $rooms;
}
It will copy the protected $properties from BaseUser and create all the set and get methods which is not what I want. What is the correct way of setting up these relationships?
Update
If you don't setup the inverse relationship, how would you select all users where hospital.id = 1
$qb = $this->getEntityManager()->createQueryBuilder()
->select(
'u'
)
->from('Account\UserBundle\Entity\User','u')
->leftJoin('u.hospitals', 'h')
->andWhere('h.id = :hospital_id')
->setParameter('hospital_id',$hospital_id);
This gives the error:
Class Account\UserBundle\Entity\User has no association named hospitals
I know I could select from hospital and join user because that relationship does exist but I need to select users because I am using them with Doctrine\ORM\Tools\Pagination\Paginator
The query would be
$qb = $this->createQueryBuilder('a')
->select(
'h', 'u'
)
->leftJoin('h.users', 'u')
The problem with this is Paginator only sees one result Hospital because the Users are attached to it.
You can define abstract entity dependencies and implement them with other bundles.
First, each of the bundles depending on a user entity should define a User interface. For example:
namespace Foo\BarBundle\Entity;
interface UserInterface
{
public function getId();
public function getEmail();
// other getters
}
Then, in each entity depending on the user, define the relationship, e.g.:
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Something
{
/**
* #ORM\ManyToOne(targetEntity="UserInterface")
* #Assert\NotNull
*/
protected $User;
// add other fields as required
}
Now you need to register the User entity as an implementation of the UserInterfaces:
namespace Foo\UserBundle\Entity;
use Foo\BarBundle\Entity\UserInterface as BarUserInterface;
use Foo\FoobarBundle\Entity\UserInterface as FoobarUserInterface;
/**
* #ORM\Entity
*/
class User implements BarUserInterface, FoobarUserInterface
{
// implement the demanded methods
}
Then add the following to app/config/config.yml:
doctrine:
orm:
resolve_target_entities:
Foo\BarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
Foo\FooarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
(Heads up: there will usually already be a doctrine.orm node which you'll have to extend.)
This is not a perfect solution, because you cannot really say which fields the user entity should have. On the other hand, it's strictly OOP, as you don't have to know about internals of the User implementation – you just need it to return the right values.
Creating multiple definitions of the account is the wrong way to do it, unless you want to create 3 seperate user tables (even then it's better not to do it this way).
Really you want your other entities to map to the your user entity in the account bundle.
I.e.,
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AccountBundle\Entity\User")
*/
protected $users;
Now, there is no need to create the inverse relationship. In fact, this is a bad practice since you have a bi-directional dependency. Users don't know about hospitals, but hospital knows about it's users. Now, any bundle can map to the user entity and reuse it.
Say I have the interface:
namespace Acme\Bundle\FooBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
interface IFoo {
/**
* #Assert\NotBlank
* #Assert\MaxLength(3000)
*/
function getBody();
}
Two classes implement the interface and I want those classes to also be able to make use of the validation annotations on the getBody declaration. (i.e. I don't want to have to duplicate the validation code in each subclass implementing IFoo since it violates DRY).
Doing this however gives me the following exception:
Trying to invoke abstract method Acme\Bundle\FooBundle\Entity\IFoo::getBody()
Does anyone know if this is possible, or any workarounds?
Seems that you can't annotate an interface, there is a ticket open on github for this issue:
https://github.com/symfony/symfony/issues/2841
I don't think you can use validation for method declarations as they are supposed to be used with properties. You could use an abstract mapped superclass for this, though.
Something along the lines of
/** #MappedSuperclass */
abstract class Foo implements FooInterface
{
/** #Column(type="string")
* #Assert\NotBlank
* #Assert\MaxLength(3000)
*/
protected function $body;
// rest of the class
}
You could then extend your other classes from this one.