I get myself as friend when executing method un repository - symfony

I am checking for friends that has accepted my friend request and thus are my real friends. But when displaying my friends I get myself as friend and not the person I have for friend sometimes. This appears when for example my id is second in the table.
I have tried checking if the userid is in either of the columns. Also tried with having two way friendship like A friend with B but B friend with A too. I couldnt get the repository work for the second method so I sticked to this one.
My repository:
public function personalFriends($userId){
$em = $this->getEntityManager();
$result = $em->createQuery('SELECT friends FROM AppBundle\Entity\Friends friends
INNER JOIN AppBundle\Entity\User myuser WHERE (friends.friendsWithMe = :userId OR friends.afriendof = :userId) AND friends.friends = 1');
$result->setParameter('userId', $userId);
return $result->getResult();
}
My controller:
public function indexAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
$userId = $user->getId();
$posts = $this->getDoctrine()->getRepository(UserPosts::class)->findUserPosts($userId,1);
$friends = $this->getDoctrine()->getRepository(Friends::class)->personalFriends($userId);
return $this->render('default/index.html.twig',
['formed' => null,
'posts' => $posts,
'posted'=>null,
'search' => null,
'friends' => $friends
]);
}
My view :
<div class="card card-body">
{{ app.user.username|capitalize }}'s friends:
{% if friends %}
<div>
{% for friend in friends %}
{{ friend.afriendof.username }}
{% endfor %}
</div>
{% else %}
<div>
You have no friends.
</div>
{% endif %}
</div>
Friends Entity:
class Friends
{
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="myfriends", fetch="EAGER")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $friendsWithMe;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="friendof", fetch="EAGER")
* #ORM\JoinColumn(name="friend_id", referencedColumnName="id", nullable=false)
*/
private $afriendof;
/**
* #var integer
*
* #ORM\Column(name="request_sent", type="smallint")
*/
private $requestSent;
/**
* #var integer
*
* #ORM\Column(name="friends", type="smallint")
*/
private $friends;
I want to get only the friends and not myself. I get that I have to change the friend.afriendof.username with friend.friendswithme.username, but how to know when to change between those two.

Related

Symfony 5 Many to Many with extra fields form builder

I'm losing a lot of time on a (maybe) basic feature that everyone needs in his project.
The fact is that I'm stuck with form builder (a real daemon in this framework, I don't understand why it's so hard to use).
I got three entities :
Recette
Ingredient
RecettesIngredients
Both Recette and Ingredient do an One-To-Many with RecettesIngredients. It's a many-to-many with extra fields.
I've done a form builder RecetteType that does :
->add('recettesIngredients', CollectionType::class, [
'entry_type' => RecettesIngredientsType::class,
'allow_add' => true,
'allow_delete' => true,
'label' => 'XXX'
])
I've done a form builder (he works fine) RecettesIngredientsType that does :
->add('ingredients', EntityType::class, [
// looks for choices from this entity
'class' => Ingredient::class,
'choice_label' => 'nom',
// used to render a select box, check boxes or radios
'multiple' => true,
'expanded' => true
])
When I try to render it:
{{ form_row(form.recettesIngredients) }}
If I don't give an object to my form builder :
I just got nothing to display but a blank page without error.
If I give an object to my form builder :
I have an error:
"Unable to transform value for property path "ingredients": Expected a Doctrine\Common\Collections\Collection object."
If I remove the "multiple" option, I got a list of ingredients but without checkboxes to select them.
My question is simple :
How can I display a form in a Many-To-Many with extra field relation context properly?
I made your exemple following the symfony documentation.
The Entities following your exemple:
/**
* #ORM\Entity(repositoryClass=RecetteRepository::class)
*/
class Recette
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=RecetteIngredient::class, mappedBy="recette")
*
*/
private $ingredients;
/**
* #ORM\Entity(repositoryClass=IngredientRepository::class)
*/
class Ingredient
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=RecetteIngredient::class, mappedBy="ingredient")
*/
private $recette;
/**
* #ORM\Entity(repositoryClass=RecetteIngredientRepository::class)
*/
class RecetteIngredient
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $quantity;
/**
* #ORM\ManyToOne(targetEntity=Recette::class, inversedBy="ingredients")
* #ORM\JoinColumn(nullable=false)
*/
private $recette;
/**
* #ORM\ManyToOne(targetEntity=Ingredient::class, inversedBy="recette")
* #ORM\JoinColumn(nullable=false)
*/
private $ingredient;
My FormTypes are from your exemple.
I created two Ingredient in my DB
And made this controller code to test
$recette = new Recette();
$recette->setName("Crèpes");
$recetteIngredient = new RecetteIngredient();
$recetteIngredient->setQuantity(5);
$recette->addIngredient($recetteIngredient);
$form = $this->createForm(RecetteType::class, $recette);
return $this->render('demo.html.twig',['form'=>$form->createView()]);
The thing that I changed is the render. Since the number of Ingredient is dynamic, Symfony (and php server's code) has no idea the number of related entities you want to add or delete and you can not just do a form_row() on the collection type.
Here's my code grabed from the doc (I didn't changed html attributes name, it's just a demo.
{{ form_start(form) }}
{{ form_row(form.name) }}
<ul id="email-fields-list"
data-prototype="{{ form_widget(form.ingredients.vars.prototype)|e }}"
data-widget-tags="{{ '<li></li>'|e }}"
data-widget-counter="{{ form.ingredients|length }}">
{% for ingredient in form.ingredients %}
<li>
{{ form_errors(ingredient) }}
{{ form_widget(ingredient) }}
</li>
{% endfor %}
</ul>
<button type="button"
class="add-another-collection-widget"
data-list-selector="#email-fields-list">Add another ingredient</button>
{{ form_end(form) }}
<script
src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
crossorigin="anonymous"></script>
<script>
jQuery(document).ready(function () {
jQuery('.add-another-collection-widget').click(function (e) {
var list = jQuery(jQuery(this).attr('data-list-selector'));
// Try to find the counter of the list or use the length of the list
var counter = list.data('widget-counter') || list.children().length;
// grab the prototype template
var newWidget = list.attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/__name__/g, counter);
// Increase the counter
counter++;
// And store it, the length cannot be used if deleting widgets is allowed
list.data('widget-counter', counter);
// create a new list element and add it to the list
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});
});
</script>
Here's the result

Symfony / Twig - Recursion decrease db queries

I have my category tree in twig template:
{% for category in categories %}
<li>
<div class="li">{{ category.name|trans({}, 'categories')|raw }}</div>
{% if category.children is not empty %}
<ul>
{% include "default/_menu_links.html.twig" with {'categories':category.children} only %}
</ul>
{% endif %}
</li>
{% endfor %}
It creates +- 53 database queries if I have 6 categories and 7 subcategories in each single category.
Is there a way to decrease that number?
I'm using useResultCache(true) in doctrine, but looks like it's not loading from the cache(at least not in dev mode).
How are you handling category trees?
UPDATE:
Entity:
...
/**
* One Category has Many Subcategories.
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent", cascade={"persist"}))
*/
private $children;
/**
* Many Subcategories have One Category.
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children", cascade={"persist"})
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
...
/**
* Add child
*
* #param \App\Entity\Product\Category $child
*
* #return Category
*/
public function addChild(\App\Entity\Product\Category $child): Category
{
$this->children[] = $child;
return $this;
}
/**
* Remove child
*
* #param \App\Entity\Product\Category $child
*/
public function removeChild(\App\Entity\Product\Category $child)
{
$this->children->removeElement($child);
}
/**
* Get children
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChildren(): Collection
{
return $this->children;
}
/**
* Set parent
*
* #param \App\Entity\Product\Category $parent
*
* #return Category
*/
public function setParent(\App\Entity\Product\Category $parent = null): Category
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* #return \App\Entity\Product\Category
*/
public function getParent()
{
return $this->parent;
}
...
According to my comment: you have to JOINyour subcategories. I'm assuming you are currently doing something like this to retrieve your categories:
public function getCategories()
{
return $this->getEntityManager()->createQueryBuilder()
->select("category")
->from("App:Category", "category")
->where("category.parent IS NULL")
->getQuery()->getResult();
}
So if you are now iterating over this array of categories and trying to access the children property, a sub query will be fired for each children which causes this high amount of database queries.
Instead you should JOIN them like this:
public function getCategories()
{
return $this->getEntityManager()->createQueryBuilder()
->select("category", "subcat")
->from("App:Category", "category")
->leftJoin("category.children", "subcat")
->where("category.parent IS NULL")
->getQuery()->getResult();
}
If you now iterate over your categories and access the children property, no extra query will be fired!
Using the query above and this snippet in twig will result only in one database query:
{% for category in categories %}
<p>cat={{ category.id }}</p>
{% for subcat in category.children %}
<p>subcat={{ subcat.id }}</p>
{% endfor %}
<hr>
{% endfor %}

How to handle Doctrine relationships with Twig

I have two entities, Customers and Locations. They are in a ManyToOne relationship (one customer can have multiple locations).
This is how I defined the relationship:
class Customers {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=45)
*/
private $name;
}
And the entity Locations:
class Locations {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customers", inversedBy="id")
* #ORM\JoinColumn(name="customers_id", referencedColumnName="id")
*/
private $customers_id;
/**
* #ORM\Column(type="string", length=90)
*/
private $name;
}
I want to be able to click on a User and render in a Twig template all the locations associated to him. This is how I'm doing that now, but I'm not sure it's the proper way.
First the controller:
/**
* #Route("/showLocations/{id}", name = "show_locations")
* #Method("GET")
**/
public function showLocationsAction($id) {
$repository = $this->getDoctrine()->getRepository('AppBundle:Locations');
$locations = $repository->findBy(array('customer_id' => $id ));
$repository = $this->getDoctrine()->getRepository('AppBundle:Customers');
$customer = $repository->findOneById($id);
if(!empty($locations)) {
return $this->render("AppBundle:Default:showLocations.html.twig", array('locations' => $locations, 'customer' => $customer)); }
else return new Response ("There are no locations to show");
}
This is the twig template:
<p>Locations associated with {{customer.name}}</p>
<table id="table_id" class="display">
<thead>
<tr>
<th>Locations</th>
</tr>
</thead>
<tbody>
{% for locations in locations %}
<tr>
<td>{{ locations.name|e }}</td>
</tr>
{% endfor %}
</tbody>
Any suggestion? Thanks!
Looks fine so far. But naming for $customers_id should be $customer only since Doctrine will automatically fetch the related customer and hydrate it into an object.
Then you can fetch and display the customer with {{ location.customer.name }}
$repository = $this->getDoctrine()->getRepository('AtlasBundle:Customers');
$customer = $repository->findOneById($id);
Can be omitted totally then.

Symfony2 Doctrine2 trouble with optional one to many relation

I have a problem with Doctrine2 and two relationed entities.
There is a user-entity that can (not must) have one or a collection of social-entity referenced which contains a social network link.
I do not control Doctrine and I'm still learning relationship.
I want to add a user with/without adding social network link.
After several researches and testing, I am still unable to find a solution.
Here is my user-entity
<?php
//...
/**
* User
*
* #ORM\Table(name="admin_users")
* #ORM\Entity(repositoryClass="UserRepository")
* #ORM\HasLifecycleCallbacks()
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=25, unique=true)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=40)
*/
private $password;
/**
*
* #var string
*
* #Assert\NotBlank
*/
private $plainPassword;
//...
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Social", mappedBy="user", cascade={"persist","remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $socials;
public function __construct()
{
$this->socials = new ArrayCollection();
}
//Some getters setters
/**
* Add socials
*
* #param Social $socials
* #return User
*/
public function addSocials(Social $socials)
{
$this->socials[] = $socials;
$socials->setUser($this);
return $this;
}
/**
* Remove socials
*
* #param Social $socials
*/
public function removeSocials(Social $socials)
{
$this->socials->removeElement($socials);
}
/**
* Get socials
*
* #return Collection
*/
public function getSocials()
{
return $this->socials;
}
}
Here is the social-entity
<?php
/**
* Social
*
* #ORM\Table(name="admin_social")
* #ORM\Entity(repositoryClass="SocialRepository")
*/
class Social
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=20, nullable=true)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="url", type="string", length=255, nullable=true)
*/
private $url;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="socials", cascade={"persist","remove"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
//getters setters
/**
* Set user
*
* #param User $user
* #return Social
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return User
*/
public function getUser()
{
return $this->user;
}
}
The userType code looks like this:
$builder
->add('username', 'text', array(
'attr'=> array('class' => 'span6',),
'label_attr' => array('class' => 'control-label'),
)
)
// ....
->add('sociaux', 'collection', array('type' => new SocialType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,))
;
Finally the controller code :
public function addAction()
{
$user = new User;
// Create the form
$form = $this->createForm( new UserType, $user );
// Gets the request
$request = $this->getRequest();
// Checks if the request have type POST
if ( $request->getMethod() == 'POST' ) {
// Links the request and the form
$form->bind( $request );
// Checks if all input values are correct
if ( $form->isValid() ) {
// Save user object in database
$em = $this->getDoctrine()->getManager();
// Persist entity user
$em->persist( $user );
$em->flush();
//...
}
}
//...
}
When I try to add a user without social-entity I have no error, but in the database I have in social table a row with null values. Please help.
UPDATE
In user-entity I added this :
if( !( $socials->getName() === null && $socials->getUrl() === null ) )
{
$this->socials[] = $socials;
$socials->setUser($this);
}
Now there is no row inserted in social table, but when I try editing the user, I have two collection field (duplicated).
See the screenshot
Here my template file (Twig) :
<div class="widget-body">
{{ form_start(form, { 'action': path('acme_admin_edit_user', {'id': userId}), 'attr': {'class': 'form-horizontal'} }) }}
<div class="control-group">
{{ form_errors(form.username) }}
{{ form_label(form.username) }}
<div class="controls">
{{ form_widget(form.username) }}
</div>
</div>
<!-- ... -->
<div id="acme_adminbundle_useredittype_socials" data-prototype="{{ form_row(form.socials.vars.prototype) | escape }}">
{% for social in form.socials %}
<div>
<label class="required text-primary lead">Lien n°{{ loop.index }}</label>
<div id="acme_adminbundle_useredittype_socials_{{ loop.index0 }}">
<div class="control-group">
{{ form_errors(social.name) }}
{{ form_label(social.name) }}
<div class="controls">
{{ form_widget(social.name) }}
</div>
</div>
<div class="control-group">
{{ form_errors(social.url) }}
{{ form_label(social.url) }}
<div class="controls">
{{ form_widget(social.url) }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="txt-center well">
<input type="submit" class="auto-margin btn btn-primary btn-large" />
</div>
{{ form_end(form) }}
</div>
Try removing:
#ORM\JoinColumn(nullable=true)
from your User class. #JoinColumn should be defined only on one side of relationship and since Social entity contains name and referencedColumnName it is unnecessary inside the User.

Cannot retrieve a user's categories using multiple joins in doctrine2 symfony2

I have created a database and a bundle that manages users, guests, categories:
A user can have many guests
A guest can belong to many categories
Many guests (of a user) can belong to a category
A user can have many categories
I have set the relationships as below:
User -> OneToMany -> Guests
Guests <-> ManyToMany <-> Categories
User -> OneToMany -> Categories
Guest entity
class Guest
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\SomethingBundle\Entity\User", inversedBy="guests")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="guests")
* #ORM\JoinTable(name="guests_categories")
*/
protected $categories;
User entity
class User implements UserInterface
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\SomethingBundle\Entity\Guest", mappedBy="user")
*/
protected $guests;
/**
* #ORM\OneToMany(targetEntity="Acme\SomethingBundle\Entity\Category", mappedBy="user")
*/
protected $categories;
Category entity
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\SomethingBundle\Entity\User", inversedBy="categories")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #ORM\ManyToMany(targetEntity="Guest", mappedBy="categories")
*/
protected $guests;
The user must be able to view ONLY his guests and categories - they are not shared or something. I have successfully retrieved a list of a user's guests and their categories with a DQL query.
public function indexAction()
{
$user = $this->get('security.context')->getToken()->getUser();
$userId = $user->getId();
$em = $this->getDoctrine()->getEntityManager();
$query = $em->createQuery( "SELECT g, u, c
FROM Acme\SomethingBundle\Entity\Guest g
JOIN g.user u
LEFT JOIN g.categories c
WHERE u.id = :userId
ORDER BY g.surname ASC");
$query->setParameter('userId', $userId);
$entities = $query->getResult();
return $this->render('AcmeSomethingBundle:Guest:index.html.twig', array(
'entities' => $entities
));
}
and a twig interface:
{% block body %}
{% for entity in entities %}
<div class="guestBox">
<div class="guestName" >{{ entity.surname }} {{ entity.name }}</div>
{% for category in entity.categories %}
{{ category.name }}
{% endfor %}
</div>
</div>
{% endfor %}
{% endblock %}
Where is the problem ? When I am trying to edit a guest entry (a form in twig), all the categories are listed in a multiple-select box, even those that haven't created by the current logged in user. I can't manage to get the user's specific categories :
public function editAction($id)
{
$user = $this->get('security.context')->getToken()->getUser();
$userId = $user->getId();
$em = $this->getDoctrine()->getEntityManager();
$query = $em->createQuery( "SELECT g, c, u
FROM AcmeSomethingBundle:Guest g
JOIN g.user u
LEFT JOIN g.categories c
WHERE u.id = :userId
AND g.id = :guestId");
$query->setParameter('guestId', $id)
->setParameter('userId', $userId);
$entity = $query->getSingleResult();
if (!$entity) {
throw $this->createNotFoundException('Wrong guest id');
}
$editForm = $this->createForm(new GuestType(), $entity);
$deleteForm = $this->createDeleteForm($id);
return $this->render('AcmeSomethingBundle:Guest:edit.html.twig', array(
'entity' => $entity,
'editForm' => $editForm->createView(),
'deleteForm' => $deleteForm->createView()
));
}
and a twig template:
{% block body %}
<form action="{{ path('guest_update', { 'id': entity.id }) }}" method="post" {{ form_enctype(editForm) }}>
{{ 'guest.form.categories'|trans }}
{{ form_errors(editForm.categories) }}
{{ form_widget(editForm.categories) }}
<button type="submit" class="btn guestSave">{{ 'guest.form.save'|trans }}</button>
</form>
{% endblock %}
Some more information: Symfony profiler shows many queries:
SELECT t0.id AS id1, t0.username AS username2, t0.salt AS salt3, t0.password AS password4, t0.email AS email5, t0.is_active AS is_active6 FROM users t0 WHERE t0.id = ?
Parameters: ['2']
SELECT g0_.id AS id0, g0_.name AS name1, g0_.surname AS surname2, g0_.email AS email3, g0_.address AS address4, g0_.phone AS phone5, g0_.mobile AS mobile6, g0_.description AS description7, g0_.created_at AS created_at8, g0_.updated_at AS updated_at9, g0_.is_activated AS is_activated10, u1_.id AS id11, u1_.username AS username12, u1_.salt AS salt13, u1_.password AS password14, u1_.email AS email15, u1_.is_active AS is_active16, c2_.id AS id17, c2_.name AS name18, c2_.description AS description19, g0_.user_id AS user_id20, c2_.user_id AS user_id21 FROM guest g0_ INNER JOIN users u1_ ON g0_.user_id = u1_.id LEFT JOIN guests_categories g3_ ON g0_.id = g3_.guest_id LEFT JOIN category c2_ ON c2_.id = g3_.category_id WHERE u1_.id = ? AND g0_.id = ?
Parameters: ['2', '33']
SELECT t0.id AS id1, t0.name AS name2, t0.description AS description3, t0.user_id AS user_id4 FROM category t0
Parameters: { }
Any help will be greatly appreciated.
Probably you should define in more specific way which categories will be passed to your GuestType form.
You can do that by passing option 'query_builder' to your form's builder. Eg. in GuestType will be something like that:
$builder->add(...)
->add('category', 'entity', array (
...
'label' => 'Some label',
'required' => false,
'query_builder' => function(CategoryRepository $repository) use($user) {
// should return doctrine's QueryBuilder object to create query which returns categories for specified $user
}))
->add(...);
Look here: http://symfony.com/doc/current/reference/forms/types/entity.html#query-builder

Resources