Hello i am trying to order doctrine collection by multiple fields
tried something like this
/**
* #var Collection
* #ORM\OrderBy({"date" = "ASC","TimeBegin" = "ASC"})
* #ORM\OneToMany(targetEntity="Schedule", mappedBy="event")
*/
protected $schedules;
This code isn't working
Date field is in format "1927-12-01"
timeBegin "00:13:01"
This is my query
public function getAppointmentDetails(int $eventId): ?Event
{
$eventAlias = 'event';
/** #var EventQueryBuilder $queryBuilder */
$queryBuilder = $this->createQueryBuilder($eventAlias);
$queryBuilder->select($eventAlias)
->whereEventId($eventId)
->withRoom()
->withService()
->withSchedulesAndInstructorsOrderedByDateAndTime();
$appointmentDetails = $queryBuilder->getQuery()->getOneOrNullResult();
return $appointmentDetails;
}
and my method withSchedulesAndInstructorsOrderedByDateAndTime
/**
* With Schedules And Instructors Ordered by Date and Time
* #return EventQueryBuilder
*/
public function withSchedulesAndInstructorsOrderedByDateAndTime() : EventQueryBuilder
{
$this->join($this->getRootAliases()[0] . '.schedules', 'sc');
$this->join('sc' . '.instructors', 'in');
return $this;
}
Thing is is if i add orderBy my instructor collection will be empty
As the documentation states:
To keep the database impact low, these implicit ORDER BY items are only added to an DQL Query if the collection is fetch joined in the DQL query.
So you need to do a fetch join by adding Schedule to your select :
/**
* With Schedules And Instructors Ordered by Date and Time
* #return EventQueryBuilder
*/
public function withSchedulesAndInstructorsOrderedByDateAndTime() : EventQueryBuilder
{
$this->addSelect('sc');
$this->join($this->getRootAliases()[0] . '.schedules', 'sc');
$this->join('sc' . '.instructors', 'in');
return $this;
}
Related
I'm facing a litte trouble with an ArrayCollection Count. I don't know if it is possible to do what I want.
I have 2 entities:
class Instancia{
/**
* #ORM\Column(name="name", type="string")
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Resultado", inversedBy="instancias", fetch="EXTRA_LAZY")
* #ORM\JoinColumn(name="resultado", referencedColumnName="id")
*/
private $resultado;
}
class Resultado{
public function __construct() {
$this->instancias = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="App\Entity\Instancia", mappedBy="resultado", fetch="EXTRA_LAZY")
*/
private $instancias;
/**
* #ORM\Column(name="numInstancias", type="integer")
*/
private $numInstancia;
function setNumInstancias(){
$this->numInstancias = $this->instancias->count(); /*return the count of all instancias*/
/* 1) Want to count with a Distinct instancias->name*/
/* 2)or want to count with a substr filter in the instancias->name*/
}
}
What I need is that Resultado->numInstancias count do a distinct by the field name in the Instancia Entity.
In a Repository it would by like the following, but I need to do in the Entity
$qb = $em->createQueryBuilder();
$qb->select('COUNT(I)')
->from('App\Entity\Instancia', 'I')
->where('I.resultado = xxx')
->groupBy('I.name');
Thanks for your help!!
Yes, this is possible. You can write a function that counts the Instancias by filtering them. Not sure exactly how everything you have is set up so some changes might be required:
public function countInstancias()
{
return count($this->getInstnacias()->filter(function (Instancia $i) {
return $i->getName() === 'some name';
}));
}
Put this in your Resultado entity. I use something similar to get users from certain groups.
Could someone assist me with this. I'm having a trouble with creating a query or how to add changes to createAction to achieve the following. On clicking create it checks if a payroll period is valid, because in the payroll week table it is populated with a one week while the user enters a two week period.
Payroll period: payrollperiodid, start date, enddate and state
Payroll Week: id, startDAte, enddate, numofdays, normal hours.
for eg. user enters startdate: 16-07-2017 enddate: 29-07-2017 in payroll period then in the payroll week table period 1 startdate: 16-07-2017 endDate:22-07-2017 period 2 startdate:23-07-2017 enddate:29-07-2017.
Thus the period would be considered valid else error, and then also on create once the period is valid checks if it exists in the payroll period table else error. But i'm not sure how to add the part that ensures that the user enters a period of 2weeks. >7days <=14days, I wouldn't want to use hard numbers how could i achieve this
public function createAction(Request $request,$startDate, $endDate)
{
$entity = new Payrollperiod();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('comtwclagripayrollBundle:Payrollperiod')->findByPayrollPeriod(array('startDate'=>$startDate,'endDate'=> $endDate));
if ($entity){
$this->addFlash('error','ERROR! Payroll Period exist');
return $this->redirect($this->generateUrl('payrollperiod_create'));
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('payrollperiod_show', array('payrollperiodid' => $entity->getpayrollperiodid())));
}
return array(
'entity' => $entity,
'form' => $form->createView(), );
}
public function findByPayrollPeriod($startDate, $endDate)
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM comtwclagripayrollBundle:PayrollWeek
WHERE startDate = :startDate or endDate = :endDate'
)
->setParameter('startDate', $startDate)
->setParameter('endDate', $endDate)
->getResult();
}
****Updates****
<?php
namespace com\twcl\agripayrollBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
//use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Payrollperiod
*
* #ORM\Table(name="PayrollPeriod")
* #ORM\Entity
*
*/
class Payrollperiod
{
/**
* #var integer
*
* #ORM\Column(name="payrollperiodid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $payrollperiodid;
/**
* #var \DateTime
*
* #ORM\Column(name="startDate", type="datetime", nullable=false)
* #Assert\Type("DateTime")
*/
private $startdate;
/**
* #var \DateTime
*
* #ORM\Column(name="endDate", type="datetime", nullable=false)
* #Assert\Type("DateTime")
*
*/
private $enddate;
/**
* #var integer
*
* #ORM\Column(name="State", type="integer", nullable=false)
*
*/
private $state;
public function getPayrollperiodid() {
return $this->payrollperiodid;
}
public function getStartdate() {
return $this->startdate;
}
public function getEnddate() {
return $this->enddate;
}
public function getState() {
return $this->state;
}
public function setPayrollperiodid($payrollperiodid) {
$this->payrollperiodid = $payrollperiodid;
}
public function setStartdate(\DateTime $startdate) {
$this->startdate = $startdate;
}
public function setEnddate(\DateTime $enddate) {
$this->enddate = $enddate;
}
public function setState($state) {
$this->state = $state;
}
/**
* Render a payrollPeriodID as a string.
*
* #return string
*/
public function __toString()
{
return (string) $this->getPayrollperiodid();
}
/**
* #Assert\Callback
*/
public function validatePayrollPeriod(Payrollperiod $Payrollperiod,ExecutionContextInterface $context)
{
$conflicts = $this->getDoctrine()
->getRepository('comtwclagripayrollBundle:Payrollperiod')
->findbyPayrollPeriod($Payrollperiod->getstartDate(), $Payrollperiod->getendDate())
;
if (count($conflicts) > 0) {
$context->buildViolation('Start date and end date exists')
->atPath('startdate')
->addViolation();
}
}
}
public function findbyPayrollPeriod(\DateTime $startDate, \DateTime $endDate)
{
$qb = $this->createQueryBuilder('e');
return $qb->andWhere('e.startDate = :startDate AND e.endDate = :endDate')
->setParameter('startDate', $startDate)
->setParameter('endDate', $endDate)
->getQuery()
->execute()
;
}
But i'm still not getting the error message, am I missing something
I think you can solve the issue, the following way
//create new trait
<?php
namespace yourBundlePath\Form\Type\Utility;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
*
* #package Daim\CoreBundle\Form\Type\Utility
*/
trait ContainerTrait
{
/**
* #var ContainerInterface
*/
private $containerObject;
/**
* #param ContainerInterface $container
* #return ContainerInterface
*/
public function setContainer(ContainerInterface $container)
{
return $this->containerObject = $container;
}
/**
* #return ContainerInterface
*/
public function getContainer()
{
return $this->containerObject;
}
}
//form
use yourBundlePath\Form\Type\Utility\ContainerTrait;
class yourFormClass
{
//call after the class declaration
use ContainerTrait;
$builder->addEventListener(
FormEvents::SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$em = $this->getContainer()->get('doctrine');
$startDate = $form->get('startDate')->getData();
$endDate = $form->get('endDate')->getData();
$entity = $em->getRepository('comtwclagripayrollBundle:Payrollperiod')->findByPayrollPeriod(array('startDate'=>$startDate,'endDate'=> $endDate));
if ($entity){
$form->get('startDate')->addError(
new FormError(
'ERROR! Payroll Period exist'
)
);
}
}
);
Also you can refer the url: https://symfony.com/doc/current/form/dynamic_form_modification.html
One can solve this in various ways. If this is a common problem (and/ or you prefer a global solution) use the Class Constraint Validator. If you don't mind a 'local' solution look at Callback Constraint.
Both are explained in the documentation pages. Another reference is this SO question.
All that is left is how to calculate the difference between dates for that I'd suggest using PHP's DateTime::diff as something like:
$days = $startDate->diff($endDate)->days;
if ($days <= 7 && $days > 14) {
// build my constraint error message because the period is invalid.
}
Update #1
So let me first say after our comment spam, maybe start somewhere a little lighter. It seems your diving in right in the middle without any foundation on Symfony and/ or even PHP. Symfony has excellent tutorials and examples, but if you can't apply those your going to have a hard time.
The callback validate is only to check the difference between the two dates. An entity in general should not talk to the database but just itself / related entity classes.
class Payrollperiod {
...
/**
* #Assert\Callback
*/
public function validatePayrollPeriod(ExecutionContextInterface $context) {
$days = $this->startdate->diff($this->enddate)->days;
if ($days <= 7 && $days > 14) {
$context->buildViolation('There have to be at least 7 and a maximum of 13 days for your payroll period.')
->atPath('enddate') // this is where the message is bound to, can be either start or end date depending on where you prefer.
->addViolation();
}
}
}
Your findbyPayrollPeriod seems valid, as long as it is in your PayrollperiodRepository class file. And you do want to have a single equals check and not see if ranges overlap etc.
This function could also be handled using doctrine's unique constraints on multiple columns eg (user, startdate) and (user, enddate). This should give you an error when you attempt to add it as it then requires a unique value for the two. Even without the findbyPayrollPeriod function.
In your controller your repository line has multiple problems.
You are using an array for arguments not two arguments as the function has.
You are overwriting your form data entity because you are using the same variable name.
And your $startdate and $enddate appear like magic. They are from the entity, so use the getters.
And as a side note you might not want to redirect on the flash, but just continue as normal (so you don't loose your form data).
All in all you would get something partially like:
...
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entityExists = $em->getRepository('comtwclagripayrollBundle:Payrollperiod')
->findByPayrollPeriod($entity->getStartdate(), $entity->getEnddate());
// If the entity does not exist we can add it and redirect to the new page.
if (!$entityExists) {
// Add our form entity to the database.
$em->persist($entity);
$em->flush();
// redirectToRoute is a controller helper function and is a tiny bit shorter.
return $this->redirectToRoute('payrollperiod_show', array(
'payrollperiodid' => $entity->getPayrollperiodid()
));
}
// Form is valid but we didn't return anything so far.
// So there is an entity with the same period start or end.
// Add a flash and show the form again.
$this->addFlash('error', 'A payroll period is already present
with the same start or end date.');
}
return ...
I am stuck at this case, I reproduced it in an example from symfony documentation, here it how it looks:
FIXTURES
/**
* #ORM\Entity
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category", fetch="EAGER")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
public function products(): Collection
{
return $this->products;
}
public function id()
{
return $this->id;
}
}
and related Product class
/**
* #ORM\Entity
*/
class Product
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
public function __construct($category)
{
$this->category = $category;
}
public function id()
{
return $this->id;
}
public function category()
{
return $this->category;
}
}
TEST
Now I have this snippet of test code where I want to fetch Category and be able to get its Products:
$cat = new Category();
$prod = new Product($cat);
$this->entityManager->persist($prod);
$this->entityManager->persist($cat);
$this->entityManager->flush();
$crepo = $this->getEntityManager()->getRepository(Category::class);
$c = $crepo->findAll()[0];
var_dump(get_class($c->products()), $c->products()->count())
What I am getting is products of class PersistentCollection which is expected, but the count is 0 while there should be 1 product.
I can see that in the database I have proper category and product records with proper foreign key set.
WORKAROUND
I am debugging PersistentCollection for products and can see that its flag is set to initialized = true. With this I am able to force this to work by calling
$c->products()->setInitialized(false);
$c->products()->initialize();
But afaik this is not how it should work, should it ?
I managed to found an answer. It basically works but not when run in the same process. If I split the script in two - first one persists, second retrieves the data then the products collection will contain products related to category.
This is because when it is done in single process doctrine does not know that the category in question has products, and since it retrieves the same object it just saved and that was created few lines above, the entity manager won't populate the collection using database but will use the one from the category object. And the category object does not have products in products collection, since there is no call like $this->products()->add($category) neither in Product constructor or anywhere else. Only forcing to reinitialize the collection works since then it really retrieves products from database
Simple example, we've got
/**
* #ORM\Column(name="api_keyID", type="integer", nullable=false)
*/
private $api_keyID;
/**
* #return integer
*/
public function getApi_keyID()
{
return $this->api_keyID;
}
/**
* #param integer $api_keyID
* #return object
*/
public function setApi_keyID($data)
{
$this->api_keyID = $data;
return $this;
}
Look at method name and column name. When i try
//...
->findOneByApi_keyID($some);
I'm getting an error like
Entity 'entity\path' has no field 'apiKeyID'. You can therefore not call 'findOneByApi_keyID' on the entities' repository
So doctrine\symfony eats underscore? О.о And i cannot use it in column name?
is the way out
$repository->findBy(array('is_enabled' => true));
Founded here
Magic Doctrine2 finders when field has underscore?
I'm trying to do a bidirectional association between 2 entities. The problem is that from Book I can get their Owner, but from Owner I can't get the books owned.
Here is the important part of the code:
Acme\BookBundle\Entity\Book;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="owned_books")
* #ORM\JoinColumn(name="owner_id", referencedColumnName="id")
*/
protected $owner;
/**
* Get owner
*
* #return Acme\UserBundle\Entity\User
*/
public function getOwner()
{
return $this->owner;
}
Acme\UserBundle\Entity\User;
/**
* #ORM\OneToMany(targetEntity="Acme\BookBundle\Entity\Book", mappedBy="owner")
*/
protected $owned_books;
public function __construct()
{
$this->owned_books = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get owned_books
*
* #return Doctrine\Common\Collections\Collection
*/
public function getOwnedBooks()
{
return $this->owned_books;
}
Then, to get the data:
This Works
$book = $this->getDoctrine()
->getRepository('BookBundle:Book')
->find(1);
$owner = $book->getOwner()->getFirstName();
This Doesn't work ( Gives Fatal error: Call to undefined method Doctrine\ORM\PersistentCollection::getName() )
$owner = $this->getDoctrine()
->getRepository('UserBundle:User')
->find(1);
$books = $owner->getOwnedBooks()->getName();
Does anyone know what I'm doing wrong? Thank you in advance.
$owner->getOwnedBooks() is a collection of Owners. Try to loop through the collection with a foreach loop.
$books = $owner->getOwnedBooks();
foreach ($books as $book) {
echo $book->getName() . ' <br/>';
}
The error message is pretty clear: you're trying to get the name of a collection of book, instead of trying to get the name of a single book.