Assuming the following two tables, 'user' and 'friends';
'user'
column: id
column: name
'friends'
column: user_id
column: user2_id
Both columns in the friends table correspond to the user table id column.
Now I can simply find users by partial name with the following;
$query='jim';
$result=$em->getRepository('\User\Entity\User')
->createQueryBuilder('u')
->where('u.name like :match')
->setParameter('match', $query.'%')
->setMaxResults(5)
->getQuery()
->getResult();
Now assuming an object of \User\Entity\User userA, how would I do a partial string match for all users that userA is not friends with already ?
EDIT Added the Entity definitions
/**
* User
*
* #ORM\Table(name="user", uniqueConstraints={#ORM\UniqueConstraint(name="name_key", columns={"name"})})
* #ORM\Entity
*/
class User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, precision=0, scale=0, nullable=false, unique=false)
*/
private $name;
}
/**
* UserFriends
*
* #ORM\Table(name="user_friends", indexes={#ORM\Index(name="user_id_key", columns={"user_id"}), #ORM\Index(name="friend_user_id_key", columns={"friend_user_id"})})
* #ORM\Entity
*/
class UserFriends
{
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \User\Entity\User
*
* #ORM\ManyToOne(targetEntity="User\Entity\User")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="friend_user_id", referencedColumnName="id", nullable=true)
* })
*/
private $friendUser;
/**
* #var \User\Entity\User
*
* #ORM\ManyToOne(targetEntity="User\Entity\User")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
* })
*/
private $user;
}`
We’ll do it in two steps. First, we find out which users they are friends with, then we do a query excluding them.
First, we retrieve an ID list of the user’s friends:
$friendIds = $em->createQuery("
SELECT IDENTITY(uf.user)
FROM User\Entity\UserFriends uf
WHERE uf.user = :id")
->setParameter("id", $userId) // $userId is the ID of the target user
->getResult();
$friendIds = array_map("current", $friendIds); // needed for flattening the array
Now we simply query the user table, excluding the IDs of our friends:
$notMyFriends = $em->createQuery("
SELECT u
FROM User\Entity\User u
WHERE u.id != :ownid
AND WHERE u.id NOT IN (:friends)")
->setParameter("ownid", $userId) // $userId is the ID of the target user
->setParameter("friends", $friendIds)
->getResult();
I’m using plain DQL here, but if you prefer the query builder, you can as well rewrite it to a QB method chain.
(All of the above is from the top of my head, hope it works. If not, please leave a comment and I'll try to fix it.)
By the way, please let me give you a few hints regarding your entity classes:
Entity classes should always be in singular UserFriends → UserFriend.
all of your name="foobar" annotation parameters are redundant, as Doctrine will auto-name your tables, indices and columns.
Same goes for the JoinColumns … you can omit them altogether, unless you want to explicitely change the default behaviour (which you usually don’t).
nullable=true is the default for an xToOne relation, so it can be omitted, too.
nullable=false and unique=false don’t make sense on an ID field. The first is redundant, the second is impossible.
Related
I've been working on a multi users web application using symfony 3.4 framework with fos user bundle in order to easily manipulate users.
I've integrated the bundle and everything work fine except that the bundle features don't match my need when it comes to multi users through inheritance !
Is there any trick to implement the multi user inheritance in fos bundle ?
I've tried a lot of different tricks like changing the roles , changing the user model interface, using symfony groups but all of them seemed to be not working !
The thing that will solve [with an ugly way] my problem is to change the value of the discriminator column .
* #ORM\Table(name="fos_user")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="typeutilisateur", type="string")
* #ORM\DiscriminatorMap({"Parent"="User","admin" =
"Administrateur","association"
="AsoociationsBundle\Entity\Association",
"Demandeurservice"="EldersStoryBundle\Entity\Demandeurservice",
"Formateur"="FormationBundle\Entity\Formateur"
,"Prestataire"="AnnonceEldersCareBundle\Entity\Prestataireservice"})
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=30, nullable=true)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="prenom", type="string", length=30, nullable=true)
*/
private $prenom;
/**
* #var string
*
* #ORM\Column(name="adresse", type="string", length=50)
*/
private $adresse;
/**
* #var string
*
* #ORM\Column(name="telephone", type="integer")
*/
private $telephone;
/**
* #var string
*
* #ORM\Column(name="sexe", type="string", length=30, nullable=true)
*/
private $sexe;
/**
* #var \DateTime
*
* #ORM\Column(name="datecreation", type="datetime")
*/
private $datecreation;
/**
* #var string
*
* #ORM\Column(name="avatar", type="string", length=255)
*/
private $avatar;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Group")
* #ORM\JoinTable(name="fos_user_user_group",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
public function __construct()
{
parent::__construct();
}
}
/*This is the sub class*/
<?php
namespace EldersStoryBundle\Entity;
use AppBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
/**
* Demandeurservice
*
* #ORM\Table(name="demandeurservice")
* #ORM\Entity(repositoryClass="EldersStoryBundle\
Repository\DemandeurserviceRepository")
*/
class Demandeurservice extends User
{
/**
* #var string
*
* #ORM\Column(name="typemaladie", type="string", length=50)
*/
private $typemaladie;
/**
* #var string
*
* #ORM\Column(name="descriptionmaladie", type="string", length=255)
*/
private $descriptionmaladie;
/**
* #var string
*
* #ORM\Column(name="etatmaladie", type="string", length=255)
*/
private $etatmaladie;
/**
* #var int
*
* #ORM\Column(name="pointelderly", type="integer")
*/
private $pointelderly;
}
Everytime i subscribe i get the row in the table but with a discriminator column value ="parent"
So is there any major way to get this done ? or at least to change the value of the discriminator column ?
Remove the DiscriminatorMap. If you don't create one, Doctrine will generate one automagically. So long as you don't go messing around with names of Entity objects (ie, change Person to Persona, or whatever) then that's your best bet. It's also more dynamic because if/when you add additional types, it will update it for you (when cache is removed).
See here, last bullet point quoted:
If no discriminator map is provided, then the map is generated automatically. The automatically generated discriminator map contains the lowercase short name of each class as key.
There are entities Endpoint, EndpointServerConfig, and Server:
/**
* Server
*
* #ORM\Table(
* name="server",
* indexes={
* #ORM\Index(name="fk_server_server_type_idx", columns={"server_type_id"}),
* #ORM\Index(name="fk_server_cluster_idx", columns={"cluster_id"})
* }
* )
* #ORM\Entity
*/
class Server
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=32, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*
* #Groups({"export"})
*/
protected $name;
/**
* #var EndpointServerConfig[]
*/
protected $endpointServerConfigs;
}
/**
* EndpointServerConfig
*
* #ORM\Table(name="endpoint_server_config", indexes={
* #ORM\Index(name="fk_endpoint_server_config_server_idx", columns={"server_name"})}
* )
* #ORM\Entity
*/
class EndpointServerConfig
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var Server
*
* #ORM\ManyToOne(targetEntity="Server")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="server_name", referencedColumnName="name")
* })
*
* #Groups({"export"})
*/
protected $server;
/**
* #var Endpoint
*
* #ORM\OneToOne(targetEntity="Endpoint", mappedBy="endpointServerConfig")
*/
protected $endpoint;
}
/**
* Endpoint
*
* #ORM\Table(
* name="endpoint",
* indexes={
* ...
* #ORM\Index(name="fk_endpoint_endpoint_server_config_idx", columns={"endpoint_server_config_id"}),
* ...
* }
* )
* #ORM\Entity
* ...
*/
class Endpoint
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
}
Now I want to update an entity (e.g. Foo), that contains an Endpoint. Among other changes I want to remove the reference to the Server from the Endpoint's EndpointServerConfig. That means for the database: The endpoint_server_config.server needs to be set to NULL.
I load the Foo to a Zend\Form, disable the server and submit the changes. On the server side I unset the EndpointServerConfig#server over Foo:
/** #var Foo $myFoo */
if(! $myFoo->getEndpoint()->getEndpointServerConfig()->getServer() || ! $myFoo->getEndpoint()->getEndpointServerConfig()->getServer()->getName()) {
$myFoo->getEndpoint()->getEndpointServerConfig()->setServer(null);
}
$this->entityManager->persist($myFoo);
$this->entityManager->flush($myFoo);
It leads to an error:
An exception occurred while executing 'UPDATE server SET name = ? WHERE name = ?' with params ["", "someservername"]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`mydb`.`endpoint_server_config`, CONSTRAINT `fk_endpoint_server_config_server` FOREIGN KEY (`server_name`) REFERENCES `server` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION)
That means, Doctrine tries to UPDATE the Server, instead of just to remove the reference to it from the EndpointServerConfig. But why?
Only when I manually set endpoint_server_config.server_name to NULL (directly in the database), I can save the changes via form and Doctrine.
How to get it working?
EDIT
Just noticed, that I get the same problem on every update of the EndpointServerConfig. So not only on setServer(null), bu also when I try to set a new Server. In this case the attempts leads to the error:
An exception occurred while executing 'UPDATE server SET name = ? WHERE name = ?' with params ["newservername", "someservername"]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`mydb`.`endpoint_server_config`, CONSTRAINT `fk_endpoint_server_config_server` FOREIGN KEY (`server_name`) REFERENCES `server` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION)
Add following:-
cascade={"persist", "remove"}
on your relation.
#ORM\ManyToOne(targetEntity="Server", cascade={"persist", "remove"})
is here some doctrine expert, who can explain me, why these DQLs will not initialize tallyRevs field on Tally entity? I supposed, that when I fetch TallyRevs (owner side) and fetchJoin Tally entity to them, that field tallyRevs will be initialized. What am I doing wrong? I need to select TallyRev based on some criteria via DQL and since it is a bi-directional association, I would like it to be initialized from the other (Tally.tallyRevs) side also.
Screen of dump
<?php
/**
* #ORM\Entity
* #ORM\Table(name="v3_overview_calloff_tally")
*/
class Tally
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var TallyRev[]|Collection
* #ORM\OneToMany(targetEntity="STI\Model\Entity\V3\Overview\CallOff\TallyRev", mappedBy="tally")
*/
private $tallyRevs;
}
/**
* #ORM\Entity
* #ORM\Table(name="v3_overview_calloff_tallyrev")
*/
class TallyRev
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
* #ORM\Column(type="integer", nullable=false)
*/
private $revision;
/**
* #var Tally
* #ORM\ManyToOne(targetEntity="STI\Model\Entity\V3\Overview\CallOff\Tally", inversedBy="tallyRevs")
* #ORM\JoinColumn(name="tally_id", referencedColumnName="id", nullable=false)
*/
private $tally;
}
Here is some repository code:
$qb = $repository->createQueryBuilder();
$qb
->select('tallyRev')
->from(TallyRev::class, 'tallyRev', 'tallyRev.id')
// complicated filtering, this is just an example
->andWhere($qb->expr()->in('tallyRev.revision', ':rev'))
->setParameter('rev', $rev)
;
$tallyRevs = $qb->getQuery()->getResult();
$ids = array_keys($tallyRevs);
$qb2 = $repository->createQueryBuilder();
$qb2
->select('partial tallyRev.{id}')
->from(TallyRev::class, 'tallyRev', 'tallyRev.id')
->andWhere($qb2->expr()->in('tallyRev.id', ':ids'))
->setParameter('ids', $ids)
->leftJoin('tallyRev.tally', 'tally')
->addSelect('tally')
;
$qb2->getQuery()->getResult();
I know, that I can write DQL from the Tally side like this:
$qb
->select('tally')
->from(Tally::class, 'tally', 'tally.id')
->leftJoin('tally.tallyRev', 'tallyRev')
->addSelect('tallyRev')
->andWhere($qb->expr()->in('tallyRev.revision', ':rev'))
->setParameter('rev', $revs)
;
If you want to fetch join tally when you fetch tallyRev you should write something like this in the first qb, and delete qb2
->select(['tallyRev', 'tally'])
->from(TallyRev::class, 'tallyRev', 'tallyRev.id')
->join('tallyRev.tally', 'tally')
// complicated filtering, this is just an example
->andWhere($qb->expr()->in('tallyRev.revision', ':rev'))
->setParameter('rev', $rev)
;
is there any chance to check if the year is unique in datetime format?
I've set my unique validator from doctrine validator for unique entity, but it checks for complete date, so if i keep the same year and change the date, it will pass the validation.
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Movie
*
* #UniqueEntity("name", message="This movie is already in the base")
* #UniqueEntity("releaseDate", message="Best movie from this year is already in the base")
* #ORM\Table(name="movie")
* #ORM\Entity(repositoryClass="AppBundle\Repository\MovieRepository")
*/
class Movie
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #var \DateTime
*
* #ORM\Column(name="release_date", type="date", unique=true)
*/
private $releaseDate;
The data model:
The entities:
Pet:
/**
* #ORM\Entity
* #ORM\Table(name="pet")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="pet_type", type="string")
*/
abstract class Pet
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(
* targetEntity="Collar",
* mappedBy="pet",
* cascade={"persist", "remove"},
* orphanRemoval=TRUE
* )
* #ORM\JoinColumn(name="collars", referencedColumnName="id")
*/
protected $collars;
/**
* #ORM\Column(type="integer")
*/
protected $age;
}
Cat:
/**
* #ORM\Entity
* #ORM\Table(name="cat")
*/
class Cat extends Pet
{
/**
* #ORM\Column(type="decimal")
*/
private $purringDecibels;
}
Collar:
/**
* #ORM\Entity
* #ORM\Table(name="collar")
*/
class Collar
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Pet", inversedBy="collars")
* #ORM\JoinColumn(name="pet", referencedColumnName="id", nullable=false)
*/
private $pet;
/**
* #ORM\Column(type="string", length="255")
*/
private $color;
}
The problem:
The generated tables are fine except pet: It lacks the collars column, therefore the bidirectionality is broken. While the pet ids do get stored on the collar table, I cannot fetch collars from a pet (i.e. $pet->getCollars()) as it always returns an empty collection.
What am I missing out here?
PS:
The validate console helper says all (mapping & db) is OK.
Yes, I have my getters and setters (i.e. adders and removers)
I know about the infamous performance impact of the combination between a CTI and this kind of relationship.
You do not need to have a "collars" column on the pet table. It is a one-to-many relationship, so the only table that need to "know" about pets are the collar table.
A bidirectional relationship does not mean you need two columns on two tables.
Your map seems correct.