Symfony 3 doctrine show content from relationship on repo - php

I have two Symfony entities SrvrsServers and NcBackupEvents:
NCbrtBundle:SrvrsServers (id, name, description, etc)
Relationship code:
/**
* One SrvrsServers has Many NcBackupEvents.
* #ORM\OneToMany(targetEntity="NcBackupEvents", mappedBy="SrvrsServers")
*/
private $ncBackupEvents;
public function __construct() {
$this->ncBackupEvents = new ArrayCollection();
}
NCbrtBundle:NcBackupEvents (id, date, status, etc)
This is on NcBackupEvents:
/**
* #var \SrvrsServers
*
* #ORM\ManyToOne(targetEntity="SrvrsServers")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="srvrs_servers_id", referencedColumnName="id")
* })
*/
private $srvrsServers;
The relationship is OneToMany from SrvrsServers to NcBackupEvents. On the repo of NcBackupEvents i'm able to pull information about itself. This works and I think the SrvrsServers object gets pulled, but I am not 100% sure and also do not know how to access it:
class NcBackupEventsRepository extends \Doctrine\ORM\EntityRepository
{
public function findByServerBackup($parameters)
{
$dql = 'SELECT n FROM NCbrtBundle:NcBackupEvents n
WHERE n.backupmethod LIKE :backupmethod
AND n.backupType LIKE :backup_type
AND n.log LIKE :log';
$query = $this->getEntityManager()
->createQuery($dql)
->setParameter('backupmethod',
'%'.$parameters['backupmethod'].'%')
->setParameter('backup_type',
'%'.$parameters['backup_type'].'%')
->setParameter('log', '%'.$parameters['log'].'%');
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
}
How can I QUERY from Controller using the SrvrsServers->name field for instance? I have tried many things and nothing seems to work. This should be very simple, but I do not understand how to get it done. I have tried to follow the official docs and can not get it to work for some reason. Can someone point me the way to do this? Please keep present that I am a newbie.
EDIT: SAMPLE Controller.
/**
* #Route("/")
*/
public function indexAction()
{
$paramaters = array('backupmethod' => 't-py',
'backup_type' => '', 'log' => 'fuc');
$em = $this->getDoctrine()
->getRepository('NCbrtBundle:NcBackupEvents')
->findByServerBackup($paramaters);
foreach($em as $value){
print_r(gettype($value));
print_r($value->getSrvrsServers()->getName());
}
return $this->render('NCbrtBundle:Default:index.html.twig');
}
EDIT2: I want to include the server name on the $paramaters variable from the SrvrsServers entity

Related

Symfony Doctrine relationship empty in PhpUnit test

I have a Symfony app with a User entity with a many-to-many relation to a Cat entity. I also have a PhpUnit test which checks that deleting a cat (that belongs to 2 users) from 1 user doesn't actually delete the cat:
public function testDeletingACatBelongingToTwoUsersOnlyDeletesTheAssociationNotTheCat()
{
$cat = $this->createCat();
// Associate with user 1
$user1 = new User();
$user1->setEmail('test#example.com');
$user1->setPassword('pwdpwd');
$user1->addCat($cat);
$this->em->persist($user1);
// Associate with user 2
$user2 = new User();
$user2->setEmail('another#example.com');
$user2->setPassword('pwdpwd');
$user2->addCat($cat);
$this->em->persist($user2);
$this->em->flush();
// Sanity check:
$this->assertCount(1, $user1->getCats()); // PASS
$this->assertCount(1, $user2->getCats()); // PASS
$this->assertCount(2, $cat->getUsers()); // FAIL (0)
// ... perform the test (not shown here)
}
private function createCat(): Cat
{
$cat = new Cat();
$cat->setName($this->name);
$this->em->persist($cat);
$this->em->flush();
return $cat;
}
My question is, why does $cat->getUsers() return 0 in my test? At runtime it doesn't, it returns the correct value. It's only in the test that it returns 0.
Here are the relevant excerpts from my entities, auto-generated by Symfony:
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* #ORM\ManyToMany(targetEntity=Cat::class, inversedBy="users")
*/
private $cats;
/**
* #return Collection|Cat[]
*/
public function getCats(): Collection
{
return $this->cats;
}
public function addCat(Cat $cat): self
{
if (!$this->cats->contains($cat)) {
$this->cats[] = $cat;
}
return $this;
}
public function removeCat(Cat $cat): self
{
$this->cats->removeElement($cat);
return $this;
}
}
/**
* #ORM\Entity(repositoryClass=CatRepository::class)
*/
class Cat
{
/**
* #ORM\ManyToMany(targetEntity=User::class, mappedBy="cats")
*/
private $users;
/**
* #return Collection|User[]
*/
public function getUsers(): Collection
{
return $this->users;
}
}
The reason is, that collections are not synchronized with the database and synchronization between owning and inverse side is not automatically done either.
The category entries of your user entity probably will be persisted to the database (although I'm missing some cascade statements, but what do I know). When the category is created, it's collection of users is empty (obviously), then users add the category to the many-to-many relation in database.
BUT, the collection is a plain collection. If you loaded the category from the database, it would be a lazy-loaded PersistentCollection (or something alike), which would - only at the moment of access - fetch the items from the database (definition of lazy loading). Your test code has the plain collection (since you created the object yourself).
Not quite sure, if it'll work, but you could try refreshing the cat ($em->refresh($cat);) I'm not quite certain though, if that will replace the collection. Alternatively, you could make your User::addCat that it also calls $cat->addUser($this) (which you might have to add, beware the infinite recursion, which already should be prevented by the "contains" checks.).

Symfony: Dynamic route for multiple entities

Currently working with Symfony 5.2
I am trying to create a dynamic route for multiple enties which have mostly same properties (some still have other fields too, but all have the same default properties).
Example (minified)
Entity News:
- id, title, author
Entity Event:
- id, title, author
I am now trying to create a dynamic route to fetch News or Events from my the repository.
class ContentController extends AbstractController {
/**
* #Route("/{type}", name="get_content")
*/
public function get(string $type) { //type is the name of the entity e.g. 'news' or 'event'
//fetch from repo
$results = $this->getDoctrine()->getRepository(/* entity class */)->findAll();
}
}
I already came up with some ideas, but not sure if they are good practise.
1) use full namespace for the entity (how do I check if the class exists? error handling?)
$results = $this->getDoctrine()->getRepository('App\\Entity\\News')->findAll();
$results = $this->getDoctrine()->getRepository('App\\Entity\\' . ucfirst($type))->findAll();
2) provide a route for each entity and call a generic function (not exactly what I want because I have like 10+ entities for this)
class ContentController extends AbstractController {
/**
* #Route("/news", name="get_news")
*/
public function get_news() {
$results = getContent(News::class);
}
/**
* #Route("/event", name="get_event")
*/
public function get_event() {
$results = getContent(Event::class);
}
public function getContent($class) {
return $this->getDoctrine()->getRepository($class)->findAll();
}
}
Mabye some of you have better ideas/improvements and can help me out a bit.
You can define available for fetching entities manually.
class ContentController extends AbstractController
{
/**
* #Route("/{type}", name="get_content")
*/
public function get(string $type)
{ //type is the name of the entity e.g. 'news' or 'event'
//fetch from repo
$results = $this->getDoctrine()->getRepository($this->getEntityClassFromType($type))->findAll();
}
private function getEntityClassFromType(string $type): string
{
//define your entities manually
foreach ([News::class, Event::class] as $class) {
$parts = explode('\\', $class);
$entity = array_pop($parts);
if ($type === lcfirst($entity)) {
return $class;
}
}
throw new NotFoundHttpException();
}
}
Or check your entities dynamically using $entityManager->getMetadataFactory()->hasMetadataFor($className);

Symfony 3.4 - updating entity after soft-deleting related entity row

I've got an entity called Logs that has a ManyToOne relation to an HourlyRates entity. Both Logs and HourlyRates have date properties. When adding a log with a specific date, an hourlyRate is assigned to it if the log-date fits within the rate's time range. I'm using the Doctrine Extensions Bundle, so the data in each entity can be soft-deleted.
What needs to be done:
After soft-deleting an HourlyRate the related Log has to be updated, so that the nearest existing past HourlyRate takes the place of the deleted one.
I tried to use preSoftDelete, postSoftDelete, preRemove and postRemove methods inside an HourlyRate entity listener. The code was being executed and the setters were working properly, but the database hasn't been updated in any of said cases. An "EntityNotFoundException" was being thrown everytime.
My second approach was to use the preRemove event along with setting the cascade option to "all" by using annotations in the HourlyRate class. As a result, soft-deleting an hourlyRate caused soft-deleting of the related log.
The Log entity:
class Log
{
use SoftDeleteableEntity;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\HourlyRate", inversedBy="logs")
* #ORM\JoinColumn(nullable=false)
*/
private $hourlyRate;
public function setHourlyRate(?HourlyRate $hourlyRate): self
{
$this->hourlyRate = $hourlyRate;
return $this;
}
}
The HourlyRate entity:
class HourlyRate
{
use SoftDeleteableEntity;
//other code
/**
* #ORM\OneToMany(targetEntity="App\Entity\Log", mappedBy="hourlyRate", cascade={"all"})
*/
private $logs;
}
The HourlyRate entity listener:
class HourlyRateEntityListener
{
public function preRemove(HourlyRate $hourlyRate, LifecycleEventArgs $args)
{
$entityManager = $args->getObjectManager();
/** #var HourlyRateRepository $HRrepo */
$HRrepo = $entityManager->getRepository(HourlyRate::class);
foreach ($hourlyRate->getLogs() as $log)
{
$rate = $HRrepo->findHourlyRateByDate($log->getDate(), $log->getUser(), $hourlyRate);
$log->setHourlyRate($rate);
}
}
}
The repository method:
class HourlyRateRepository extends ServiceEntityRepository
{
public function findHourlyRateByDate(?\DateTimeInterface $datetime, User $user, ?HourlyRate $ignore = null): ?HourlyRate
{
$qb = $this->createQueryBuilder('hr')
->where('hr.date <= :hr_date')
->andWhere('hr.user = :user')
->orderBy('hr.date', 'DESC')
->setMaxResults(1)
->setParameters(array('hr_date' => $datetime, 'user' => $user));
//ignore the "deleted" hourlyRate
if($ignore){
$qb->andWhere('hr.id != :ignored')
->setParameter('ignored', $ignore->getId());
}
return $qb->getQuery()
->getOneOrNullResult()
;
}
}
Thank you in advance for any of your help.
EDIT:
Okay so after a whole week of trials and errors i finally managed to achieve the result I wanted.
I removed the One-To-Many relation between the hourlyRates and the logs from the entities, but left the $hourlyRate property inside the Log class. Then I got rid of the HourlyRateEntityListener and the preRemove() method from the LogEntityListener. Instead, I implemented the postLoad() method:
class LogEntityListener
{
public function postLoad(Log $log, LifeCycleEventArgs $args)
{
$entityManager = $args->getObjectManager();
$HRrepo = $entityManager->getRepository(HourlyRate::class);
/** #var HourlyRateRepository $HRrepo */
$rate = $HRrepo->findHourlyRateByDate($log->getDate(), $log->getUser());
$log->setHourlyRate($rate);
}
}
This approach allows me to set the proper hourlyRate for each log without involving the database. Idk if this solution is acceptable though.

Symfony2: Prevent duplicate in database with form Many to One

I have a Parents form embedded into another form Student containing the data of the parents of a student with an association of Many to one.
When a new student registration are recorded his parents in another table in the database. Then if a new student who is brother of an existing need to register, meaning that parents are already registered in the database, should be prevented from parents to register again in the database, could only upgrade .
I'm told that this is solved using data transformers, but I do not know how to use it. If someone could help me I would appreciate it. Here I leave the code:
StudentType.php
//...
->add('responsible1', new ParentsType(),array('label' => 'Mother'))
->add('responsible2', new ParentsType(),array('label'=> 'Father'))
Entity Parents
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//National identity document
//we have removed "#UniqueEntity(fields={"NID"}, message="...")"
//so you can put any NID on the form and then check its existence to insert or not.
/**
* #var string
*
* #ORM\Column(name="NID", type="string", length=10)
* #Assert\NotBlank()
*/
private $nid;
//more properties...
/**
* #ORM\OneToMany(targetEntity="Student", mappedBy="$responsible1")
* #ORM\OneToMany(targetEntity="Student", mappedBy="$responsible2")
*/
private $students;
//...
public function addStudent(\Cole\BackendBundle\Entity\Student $students)
{
$this->students[] = $students;
return $this;
}
public function removeStudent(\Cole\BackendBundle\Entity\Student $students)
{
$this->students->removeElement($students);
}
public function getStudents()
{
return $this->students;
}
Entity Student
//...
/**
* #ORM\ManyToOne(targetEntity="Parents", inversedBy="students", cascade={"persist"})
*/
private $responsible1;
/**
* #ORM\ManyToOne(targetEntity="Parents", inversedBy="students", cascade={"persist"})
*/
private $responsible2;
//...
public function setResponsible1($responsible1)
{
$this->responsible1 = $responsible1;
return $this;
}
public function getResponsible1()
{
return $this->responsible1;
}
public function setResponsible2($responsible2)
{
$this->responsible2 = $responsible2;
return $this;
}
public function getResponsible2()
{
return $this->responsible2;
}
ParentsRepository.php
class ParentsRepository extends EntityRepository
{
public function findResponsible($nid)
{
return $this->getEntityManager()->createQuery(
'SELECT p FROM BackendBundle:Parents p WHERE p.nid=:nid')
->setParameter('nid',$nid)
->setMaxResults(1)
->getOneOrNullResult();
}
}
StudentController.php
/**
* Creates a new Student entity.
*
*/
public function createAction(Request $request)
{
$entity = new Student();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$responsible1 = $em->getRepository('BackendBundle:Parents')->findResponsible($entity->getResponsible1()->getNid());
$responsible2 = $em->getRepository('BackendBundle:Parents')->findResponsible($entity->getResponsible2()->getNid());
if($responsible1){
$entity->setResponsible1($responsible1->getId());
}
if($responsible2){
$entity->setResponsible2($responsible2->getId());
}
$entity->getResponsible1()->setUsername($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setUsername($entity->getResponsible2()->getNid());
$entity->getResponsible1()->setPassword($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setPassword($entity->getResponsible2()->getNid());
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('student_show', array('id' => $entity->getId())));
}
return $this->render('BackendBundle:Student:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
With the above code attempts to solve the problem but it gives me error to persist data to the database and will not let me add to the database, but if you use the following code to test the new student creates and assigns parents corresponding not create them again (assuming you were already created earlier).
$responsible1 = $em->getRepository('BackendBundle:Parents')->findResponsible(4); //The number corresponds to the id of the parent
$responsible2 = $em->getRepository('BackendBundle:Parents')->findResponsible(5);
$entity->setResponsible1($responsible1->getId());
$entity->setResponsible2($responsible2->getId());
I do not know if what I'm doing is right.I read something to use Data Transformers or event listener as PrePersist and Preupdate, but I don't know how to use this.
Thanks in advance for your answers.
Instead of
if($responsible1){
$entity->setResponsible1($responsible1->getId());
}
if($responsible2){
$entity->setResponsible2($responsible2->getId());
}
$entity->getResponsible1()->setUsername($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setUsername($entity->getResponsible2()->getNid());
$entity->getResponsible1()->setPassword($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setPassword($entity->getResponsible2()->getNid());
you can write
if($responsible1){
$entity->setResponsible1($responsible1);
}
if($responsible2){
$entity->setResponsible2($responsible2);
}
And it should work.
But I think a better solution will be to add an event listener to the FormEvents::SUBMIT event. This event allows you to change data from the normalized representation of the form data. So all you need to do is something like this:
public function onSubmit(FormEvent $event)
{
$student = $event->getData();
if ($student->getResponsible1()) {
$parentNid = $student->getResponsible1()->getNid();
// here you check the database to see if you have a parent with this nid
// if a parent exists, replace the current submitted parent data with the parent entity existing in your db
}
Hope this helps. Let me know if I have to give more details.
Judging from your relationship, you want to avoid that the same student is added twice to the Parents entity. There is a simple trick for that, ArrayCollaction class has a method named contains it returns true if a value or object is already found in the collection. A better in_array.
So, you need to check inside the adder if the $parent already contains the $student that is about to be added and act accordingly. Like shown below:
public function addStudent(\Cole\BackendBundle\Entity\Student $student)
{
if (!$this->students->contains($student)) {
$this->students[] = $students;
}
return $this;
}
Here's my thoughts, from the comments, you said you are using a national identity document(hopefull its an integer representation), make this the primary key of the parent table and make this unique, so when the second student who is the sibling of a another student enters the same details and submits, the database will throw an error, handle that error and continue on
edit: it may not even be required to make the national identity the primary key, just make it unique, you were supposed to do this regardless, you missed this one.
you can use symfony entity form type to load (ajax) the parent entity when the student enters the national identity

How to query database data with Doctrine and Symfony

I have a line like this
// $repository is my repository for location data
$locationObject = $repository->findOneBy(array('name' => $locationName));
Which selects the first record it can find from the Locations table. Which is fair enough.
However, I have some additional data in that table to make the query more precise. Specifically, an "item_name" column. In the Location class it is specified as such:
/**
* #ORM\ManyToOne(targetEntity="Item", inversedBy="locations", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="item_id", referencedColumnName="item_id", onDelete="CASCADE")
*/
protected $item;
So there is also an Item table with item_id, item_name, etc.
What I want to do is change the original findOneBy() to also filter by item name. So I want something like:
$locationObject = $repository->findOneBy(array('name' => $locationName, 'item' => $itemName));
But because $item is an object in the Locations class rather than a string or an ID obviously that wouldn't work. So really I want to somehow much against item->getName()...
I'm not sure how I can do this. Does anyone have any suggestions?
Thanks
I guess you must create a custom query with join. It's better you create a custom repository class for this entity and then creates a custom query build inside it.
Entity:
// src/AppBundle/Entity/Foo.php
/**
* #ORM\Table(name="foo")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FooRepository")
*/
class Foo
{
...
}
Your repository:
// src/AppBundle/Repository/FooRepository.php
use Doctrine\ORM\EntityRepository;
class FooRepository extends EntityRepository
{
public function findByYouWant($id)
{
// your query build
}
}
Controller:
// src/AppBundle/Controller/FooController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller
{
public function showAction()
{
// ... your code
$locationObject = $repository->findByYouWant($id);
}
}
You should add a method to your Location repository class, and create a query similiar to the one below:
class LocationRepository extends EntityRepository
{
public function findLocationByItemName($locationName, $itemName)
{
$qb = $this->createQueryBuilder('location');
$qb->select('location')
->innerJoin(
'MyBundle:Item',
'item',
Query\Expr\Join::WITH,
$qb->expr()->eq('location.item', 'item.item_id')
)
->where($qb->expr()->like('location.name', ':locationName'))
->andWhere($qb->expr()->like('item.name', ':itemName'))
->setParameter('locationName', $locationName)
->setParameter('itemName', $itemName);
$query = $qb->getQuery();
return $query->getResult();
}
}
You have to use a custom dql.You can construct it using the querybuilder.
//in your controller
protected function getEntities($itemName){
$em = $this->get('doctrine.orm.default_entity_manager');
$qb = $em->createQueryBuilder();
$qb->select('a')->from('YourBundleAlias:YourEntityName', 'a')->join('a.item','b')->where('b.item = :item')->setParameter('item', $itemName);
return $qb->getQuery()->execute();
}
This is as easy as:
$locationObject = $repository->findOneBy(array(
'name' => $locationName,
'item' => $itemObject
));
Using Doctrine2 in order to do a findBy on a related entity field you must supply an entity instance: $itemObject.

Categories