How to perform nested reference query in doctrine mongodb - php

This describes my current schema:
/**
* #MongoDB\Document(repositoryClass="St\AppBundle\Repository\TaxiStateRepository", requireIndexes=true)
* #MongoDB\Index(keys={"location"="2d"})
*/
class TaxiState
{
/**
* #MongoDB\ReferenceOne(targetDocument="Taxi", simple=true, inversedBy="taxiState")
* #MongoDB\Index
*/
protected $taxi;
..
}
/**
* #MongoDB\Document(repositoryClass="St\AppBundle\Repository\TaxiRepository", requireIndexes=true)
*/
class Taxi
{
/**
* #MongoDB\ReferenceOne(targetDocument="Driver", simple=true)
* #MongoDB\Index
*/
protected $driver;
..
}
/**
* #MongoDB\Document(repositoryClass="St\AppBundle\Repository\DriverRepository", requireIndexes=true)
*/
class Driver
{
/**
* #MongoDB\EmbedOne(targetDocument="DriverAccount")
* #MongoDB\Index
*/
protected $driverAccount;
..
}
/** #MongoDB\EmbeddedDocument */
class DriverAccount
{
/**
* #MongoDB\String
* #Assert\NotBlank()
* #Assert\Choice(choices = {"enabled","disabled"}, message="please chose a valid status"); * #MongoDB\Index
*/
protected $status;
I basically want to run a query that filters out disabled driver accounts.. something like this:
return $this->createQueryBuilder()
->field('taxi.driver.driverAccount.status')->equals("enabled")
->getQuery()
->getSingleResult();
it complains that it doesn't have an index taxi.driver etc.. I spent all day looking at by directional reference documentation in doctrine but the examples are so sparse.. help?
For reference.. this was the query that worked right before i introduced that crazy line:
return $this->createQueryBuilder()
->field('status')->equals('available')
->field('taxi')->notIn($taxiObj)
->field('location')->near((float)$location->getLongitude(), (float)$location->getLatitude())
->distanceMultiplier(self::EARTH_RADIUS_KM)
->maxDistance($radius/111.12)
->getQuery()
->execute();

Just in case you were wondering how I "resolved" this (it's a very hacky answer.. but oh well you do what you gotta do right?) this is what I got:
/**
* Find near enabled taxi without rejected request taxi
*
* #param Document\Location $location
* #param int $radius
* #param array $taxis
* #return Document\TaxiState
*/
public function findEnabledNearTaxiWithoutRejectRequest(Document\Location $location, $radius = self::SEARCH_RADIUS, $taxis = array(), $logger)
{
$taxiObj = array_map(function ($item) {
return new \MongoId($item);
}, $taxis);
//ST-135 change to near, spherical, distanceMultiplier and maxDistance in KM
$allTaxiStates = $this->createQueryBuilder()
->field('status')->equals('available')
->field('taxi')->notIn($taxiObj)
->field('location')->near((float)$location->getLongitude(), (float)$location->getLatitude())
->distanceMultiplier(self::EARTH_RADIUS_KM)
->maxDistance($radius/111.12)
->getQuery()
->execute();
$this->addIdsOfDisabledTaxiStates($taxiObj, $allTaxiStates);
if (count($taxiObj) > 0) {
$logger->info("There are ".count($taxiObj)." taxis excluded while looking for taxis to respond to a requst: ".$this->getMongoIdsStr($taxiObj));
}
return $this->createQueryBuilder()
->field('status')->equals('available')
->field('taxi')->notIn($taxiObj)
->field('location')->near((float)$location->getLongitude(), (float)$location->getLatitude())
->distanceMultiplier(self::EARTH_RADIUS_KM)
->maxDistance($radius/111.12)
->getQuery()
->getSingleResult();
}
/**
* Get the Mongo Ids of disabled taxi States
*
* #param $ids existing array of ids we want to append to (passed by reference)
* #param $taxiStates array of Document\TaxiState
* #return array of MongoIds of disabled taxi states
* #author Abdullah
*/
private function addIdsOfDisabledTaxiStates(&$ids, $taxiStates)
{
foreach ($taxiStates as $taxiState) {
if ($taxiState->getTaxi()->getDriver()->getDriverAccount()->getStatus() != DriverAccountModel::STATUS_ENABLED) {
$mongoId = new \MongoId($taxiState->getTaxi()->getId());
array_push($ids, $mongoId);
}
}
return $ids;
}

Related

PHPIDS on php 8.1 Deprecated Errors [duplicate]

This question already has answers here:
Reference: Return type of ... should either be compatible with ..., or the #[\ReturnTypeWillChange] attribute should be used
(2 answers)
Closed last year.
i got a problem with PHPIDS on a PHP 8.1 Server.
Here the Errors:
PHP Deprecated: Return type of IDS\Report::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in phpids\ib\IDS\Report.php on line 205
PHP Deprecated: Return type of IDS\Report::getIterator() should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in phpids\lib\IDS\Report.php on line 218
I know Deprecated Errors are not that bad, but i want that everything works Perfect.
I Searched in Google several things but no solution found.
Hopefully you find the problem.
i use the actual PHPIDS version from Github (https://github.com/PHPIDS/PHPIDS)
i know this version is not the newest but i didn't found a newer one.
thx for the help
and here the Script Report.php
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (https://phpids.org)
*
* PHPIDS is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* #category Security
* #package PHPIDS
* #author Mario Heiderich <mario.heiderich#gmail.com>
* #author Christian Matthies <ch0012#gmail.com>
* #author Lars Strojny <lars#strojny.net>
* #license http://www.gnu.org/licenses/lgpl.html LGPL
* #link http://php-ids.org/
*/
namespace IDS;
/**
* PHPIDS report object
*
* The report objects collects a number of events and thereby presents the
* detected results. It provides a convenient API to work with the results.
*
* Note that this class implements Countable, IteratorAggregate and
* a __toString() method
*
* #category Security
* #package PHPIDS
* #author Christian Matthies <ch0012#gmail.com>
* #author Mario Heiderich <mario.heiderich#gmail.com>
* #author Lars Strojny <lars#strojny.net>
* #copyright 2007-2009 The PHPIDS Group
* #license http://www.gnu.org/licenses/lgpl.html LGPL
* #link http://php-ids.org/
*/
class Report implements \Countable, \IteratorAggregate
{
/**
* Event container
*
* #var Event[]|array
*/
protected $events = array();
/**
* List of affected tags
*
* This list of tags is collected from the collected event objects on
* demand when IDS_Report->getTags() is called
*
* #var string[]|array
*/
protected $tags = array();
/**
* Impact level
*
* The impact level is calculated on demand by adding the results of the
* event objects on IDS\Report->getImpact()
*
* #var integer
*/
protected $impact = 0;
/**
* Centrifuge data
*
* This variable - initiated as an empty array - carries all information
* about the centrifuge data if available
*
* #var array
*/
protected $centrifuge = array();
/**
* Constructor
*
* #param array $events the events the report should include
*
* #return Report
*/
public function __construct(array $events = null)
{
foreach ((array) $events as $event) {
$this->addEvent($event);
}
}
/**
* Adds an IDS_Event object to the report
*
* #param Event $event IDS_Event
*
* #return self $this
*/
public function addEvent(Event $event)
{
$this->clear();
$this->events[$event->getName()] = $event;
return $this;
}
/**
* Get event by name
*
* In most cases an event is identified by the key of the variable that
* contained maliciously appearing content
*
* #param string|integer $name the event name
*
* #throws \InvalidArgumentException if argument is invalid
* #return Event|null IDS_Event object or false if the event does not exist
*/
public function getEvent($name)
{
if (!is_scalar($name)) {
throw new \InvalidArgumentException('Invalid argument type given');
}
return $this->hasEvent($name) ? $this->events[$name] : null;
}
/**
* Returns list of affected tags
*
* #return string[]|array
*/
public function getTags()
{
if (!$this->tags) {
$this->tags = array();
foreach ($this->events as $event) {
$this->tags = array_merge($this->tags, $event->getTags());
}
$this->tags = array_values(array_unique($this->tags));
}
return $this->tags;
}
/**
* Returns total impact
*
* Each stored IDS_Event object and its IDS_Filter sub-object are called
* to calculate the overall impact level of this request
*
* #return integer
*/
public function getImpact()
{
if (!$this->impact) {
$this->impact = 0;
foreach ($this->events as $event) {
$this->impact += $event->getImpact();
}
}
return $this->impact;
}
/**
* Checks if a specific event with given name exists
*
* #param string|integer $name the event name
*
* #throws \InvalidArgumentException if argument is illegal
* #return boolean
*/
public function hasEvent($name)
{
if (!is_scalar($name)) {
throw new \InvalidArgumentException('Invalid argument given');
}
return isset($this->events[$name]);
}
/**
* Returns total amount of events
*
* #return integer
*/
public function count()
{
return #count($this->events);
}
/**
* Return iterator object
*
* In order to provide the possibility to directly iterate over the
* IDS_Event object the IteratorAggregate is implemented. One can easily
* use foreach() to iterate through all stored IDS_Event objects.
*
* #return \Iterator the event collection
*/
public function getIterator()
{
return new \ArrayIterator($this->events);
}
/**
* Checks if any events are registered
*
* #return boolean
*/
public function isEmpty()
{
return empty($this->events);
}
/**
* Clears calculated/collected values
*
* #return void
*/
protected function clear()
{
$this->impact = 0;
$this->tags = array();
}
/**
* This method returns the centrifuge property or null if not
* filled with data
*
* #return array
*/
public function getCentrifuge()
{
return $this->centrifuge;
}
/**
* This method sets the centrifuge property
*
* #param array $centrifuge the centrifuge data
*
* #throws \InvalidArgumentException if argument is illegal
* #return void
*/
public function setCentrifuge(array $centrifuge = array())
{
if (!$centrifuge) {
throw new \InvalidArgumentException('Empty centrifuge given');
}
$this->centrifuge = $centrifuge;
}
/**
* Directly outputs all available information
*
* #return string
*/
public function __toString()
{
$output = '';
if (!$this->isEmpty()) {
$output .= vsprintf(
"Total impact: %d<br/>\nAffected tags: %s<br/>\n",
array(
$this->getImpact(),
implode(', ', $this->getTags())
)
);
foreach ($this->events as $event) {
$output .= vsprintf(
"<br/>\nVariable: %s | Value: %s<br/>\nImpact: %d | Tags: %s<br/>\n",
array(
htmlspecialchars($event->getName()),
htmlspecialchars($event->getValue()),
$event->getImpact(),
implode(', ', $event->getTags())
)
);
foreach ($event as $filter) {
$output .= vsprintf(
"Description: %s | Tags: %s | ID %s<br/>\n",
array(
$filter->getDescription(),
implode(', ', $filter->getTags()),
$filter->getId()
)
);
}
}
$output .= '<br/>';
if ($centrifuge = $this->getCentrifuge()) {
$output .= vsprintf(
"Centrifuge detection data<br/> Threshold: %s<br/> Ratio: %s",
array(
isset($centrifuge['threshold']) && $centrifuge['threshold'] ? $centrifuge['threshold'] : '---',
isset($centrifuge['ratio']) && $centrifuge['ratio'] ? $centrifuge['ratio'] : '---'
)
);
if (isset($centrifuge['converted'])) {
$output .= '<br/> Converted: ' . $centrifuge['converted'];
}
$output .= "<br/><br/>\n";
}
}
return $output;
}
}
As of PHP 8.1, you have to fix the return type of the functions count() and getIterator() to match with interfaces.
public count(): int (see Countable)
public getIterator(): Traversable (see IteratorAggregate)
class Report implements \Countable, \IteratorAggregate
{
protected array $events;
public function count(): int
{
return count($this->events);
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->events);
}
}

Typo3 Performance in Extbase Model

so, I have a performance issue with my extbase plugin.
The scenario is such:
I have 4 database tables with data for artists, artworks, exhibitions and publications.
Exhibitions can have artists, artworks and publications as mm relations.
publications and artwork can have artists as mm relations.
In my Model Classes I have those relations as ObjectStorage and use simple findAll() Method for my List View.
So if I get to the Exhibition List View I get every Exhibition and their related Artists and all related Artworks/Publications/Exhibitions of that Artist.
The performance is really bad and a not cached page needs almost a full minute to load.
And this is all because of the heavy data load from the db.
I only need the MM relations on the first level and not further. Is there anyway to config this?
Classes\Domain\Model\Exhibition.php
class Exhibition extends AbstractEntity
{
/**
* #var string
*/
protected $name = '';
/**
* #var string
*/
protected $location = '';
/**
* artists
*
* #var ObjectStorage<\Vendor\Project\Domain\Model\Artist>
* #TYPO3\CMS\Extbase\Annotation\ORM\Lazy
*/
protected $artists = null;
/**
* artworks
*
* #var ObjectStorage<\Vendor\Project\Domain\Model\Artwork>
* #TYPO3\CMS\Extbase\Annotation\ORM\Lazy
*/
protected $artworks;
/**
* publications
*
* #var ObjectStorage<\Vendor\Project\Domain\Model\Publication>
* #TYPO3\CMS\Extbase\Annotation\ORM\Lazy
*/
protected $publications;
/**
* Fal media items
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
* #TYPO3\CMS\Extbase\Annotation\ORM\Lazy
*/
protected $falMedia;
public function __construct(string $name = '', string $description = '', string $location = '')
{
$this->setName($name);
$this->setLocation($location);
}
/**
* #param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return string
*/
public function getLocation(): string
{
return $this->location;
}
/**
* #param string $location
*/
public function setLocation(string $location): void
{
$this->location = $location;
}
/**
* Returns the artist
*
* #return ObjectStorage<\Vendor\Project\Domain\Model\Artist> $artists
*/
public function getArtists()
{
return $this->artists;
}
/**
* Sets the artist
*
* #param ObjectStorage<\Vendor\GKG\Domain\Model\Artist> $artists
* #return void
*/
public function setArtists(ObjectStorage $artists)
{
$this->artists = $artists;
}
/**
* #param $artworks
*/
public function setArtworks($artworks)
{
$this->artworks = $artworks;
}
/**
* #return ObjectStorage
*/
public function getArtworks()
{
return $this->artworks;
}
/**
* Sets the publications
*
* #param ObjectStorage<\Vendor\Project\Domain\Model\Publication> $oublications
* #return void
*/
public function setPublications(ObjectStorage $publications)
{
$this->publications = $publications;
}
/**
* Returns the publications
*
* #return ObjectStorage<\Vendor\Project\Domain\Model\Publication> $publications
*/
public function getPublications()
{
return $this->publications;
}
/**
* Get the Fal media items
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage
*/
public function getFalMedia()
{
return $this->falMedia;
}
}
This gets all the Artist data in which all related data is fetched as well (artwork, exhibition and publication). And this is a heavy overload :/
I tried to write my own query and only select the values I need in my frontend:
Classes\Domain\Repository\ExhibitionRepository.php
public function findExhibitions()
{
$languageAspect = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance
(\TYPO3\CMS\Core\Context\Context::class)->getAspect('language');
$sys_language_uid = $languageAspect->getId();
$query = $this->createQuery();
$query->getQuerySettings()->setRespectSysLanguage(true);
$query->statement( "
SELECT DISTINCT e.name, e.uid, e.location, e.fal_media, a.name, a.uid
FROM tx_project_domain_model_exhibition e
LEFT JOIN tx_project_exhibitions_artists_mm
ON tx_project_exhibitions_artists_mm.uid_local = e.uid
LEFT JOIN tx_project_domain_model_artist a
ON tx_project_exhibitions_artists_mm.uid_foreign = a.uid
WHERE e.sys_language_uid = $sys_language_uid
GROUP BY tx_project_exhibitions_artists_mm.uid_local
" );
return $query->execute();
}
But with this approach I’m not able to get the relation data to my assigned view variable inside my Controller. Only the exhibition related stuff.
Classes\Controller\ExhibitionController.php
public function indexAction()
{
$queryResult = $this->exhibitionRepository->findExhibitions();
$this->view->assign('exhibitions', $queryResult);
}
Any insight and advice on how to tackle this problem and only get the needed Data and not everything from the ObjectStorage?
Thanks in advance and best regards
It looks like your query cost is very high with all the JOINS and without a condition on a indexed column.
You could check it with an EXPLAIN PLAN(MySQL) / EXECUTION PLAN(MS).
Set condition with an index to boost Performance
Rather using the Comparing operations leads to Performance.
https://docs.typo3.org/m/typo3/book-extbasefluid/main/en-us/6-Persistence/3-implement-individual-database-queries.html
Another performance boost is to use Ajax, just search for "TYPO3 Ajax":
get an identifier and display it in the Frontend:
SELECT DISTINCT e. uid, e.name FROM tx_project_domain_model_exhibition
On identifier click (name or whatever) make a call of the AjaxController.php:
...
$this->exhibitionRepository->findByUid($uid);
...
or make your own repository comparing
...
$query->matching(
$query->logicalAnd(
$query->equals('uid', $uid),
$query->equals('name', $name)
)
);
...
show the data on a Modal Window,Accordion or a new View.

Doctrine Query Base with grouping and latest record

I have a table "table_b" that contain the following details
I would like to use doctrine to query an output with a specific condition. Currently I'm using the block below to query.
$table_a= $em->getRepository('table_a')->findOneBy(['id'=>1]);
foreach($table_a->table_b as $records){
echo $records->name. " : " . $records->value;
}
It will output the entire ArrayCollection. Is there a way to query the record base on latest 'Date Created', that is base on the grouping of column 'Foreign Key Table 2'.
If you want to use native Doctrine query methods, you should use findOneBy with the order byparameter.
findOneBy(['id' => 1], ['DateCreated' => 'desc'])
Then, you says the result is an ArrayCollection, so using the ArrayCollection::first() method, you'll get the last created element
EDIT
Imagine you have a Group entity and a Member entity. groups table is your table_a and members table is your table_b.
Entity description should be something like that :
class Group
{
...
/**
* #ORM\OneToMany(targetEntity="Group", mappedBy="member", cascade={"persist", "remove", "merge"})
* #ORM\OrderBy({"dateCreated"="DESC"})
*/
protected $members;
...
public function __construct()
{
$members = new ArrayCollection();
}
// members handling accessors
/**
* #return ArrayCollection
*/
public function getMembers()
{
return $this->members;
}
/**
* #param $members
*
* #return $this
*/
public function setMembers($members)
{
$this->members = new ArrayCollection();
return $this->addMembers($members);
}
/**
* #param $members
*
* #return $this
*/
public function addMembers($members)
{
foreach ($members as $member)
{
$this->addMember($member);
}
return $this;
}
/**
* #param Member $member
*
* #return $this
*/
public function addMember(Member $member)
{
$this->members->add($member);
$member->setGroup($this);
return $this;
}
/**
* #param Member $member
*
* #return $this
*/
public function removeMember(Member $member)
{
if ($this->members->contains($member))
{
$this->members->removeElement($member);
}
return $this;
}
/**
* #param $members
*
* #return $this
*/
public function removeMembers($members)
{
foreach ($members as $member)
{
$this->removeMember($member);
}
return $this;
}
}
And Member entity :
class Member
{
/**
* #ORM\ManyToOne(targetEntity="Group", inversedBy="members")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $group;
/**
* #ORM\Column(type="datetime", name="date_created")
*/
protected $dateCreated;
/**
* #return Group
*/
public function getGroup()
{
return $this->group;
}
/**
* #param Group $group
*
* #return $this
*/
public function setGroup(Group $group)
{
$this->group = $group;
return $this;
}
}
Now, we have one group with a dateCreated ordered collection of members.
Example 1 : You want to get the last member created for a given group
$group = $em->getRepository(Group::class)->findOneBy(['id' => 1]);
$lastMember = $group->getMembers()->first();
Example 2 : You want to get all members created on 2014-01-30 :
$members = $group->getMembers()->filter(function (Member $member) {
return ($member->getDateCreated->format('Y-m-d') == '2014-01-30');
});
That's all folk !
PS : I haven't test this code
In TableRepository:
public function getLatestRecord()
{
return $this->getEntityManager()
->createQuery('SELECT t FROM MyBundle:MyEntity t GROUP BY t.table2NameField ORDER BY t.created DESC')
->setMaxResults(1)
->getOneOrNullResult();
}

Why I got NULL instead the related "paises"?

I've FabricanteDistribuidor.php entity with this code:
/**
* #ORM\Entity
* #ORM\Table(name="nomencladores.fabricante_distribuidor", schema="nomencladores")
* #ORM\Entity(repositoryClass="AppBundle\Entity\Repository\FabricanteDistribuidorRepository")
* #UniqueEntity(fields={"nombre"}, message="El nombre ya está registrado")
*/
class FabricanteDistribuidor
{
use IdentifierAutogeneratedEntityTrait;
use NamedEntityTrait;
// ... some other fields
/**
* #ORM\ManyToMany(targetEntity="Sencamer\AppBundle\Entity\Pais")
* #ORM\JoinTable(name="negocio.fabricante_distribuidor_pais", schema="negocio",
* joinColumns={#ORM\JoinColumn(name="fabricante_distribuidor", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pais_id", referencedColumnName="id")}
* )
*/
protected $paises;
/**
* Set paises
*
* #param \AppBundle\Entity\Pais $pais
* #return FabricanteDistribuidor
*/
public function setPaises(\AppBundle\Entity\Pais $pais)
{
$this->paises[] = $pais;
return $this;
}
/**
* Get paises
*
* #return string
*/
public function getPaises()
{
return $this->paises;
}
}
Then in the controller I'm trying to get one records and it associated like in this case will be all the paises as follow:
public function obtenerDetallesFabricanteAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AppBundle:FabricanteDistribuidor')->find($request->query->get('id'));
if ($request->isXmlHttpRequest()) {
$response['entities'] = array();
$dataResponse = array();
// ... some other fields
$dataResponse['paises'] = $entity->getPaises();
$response['entities'][] = $dataResponse;
return new JsonResponse($response);
}
}
In the JSON response I get everything fine but paises is set to NULL and the relation table fabricante_distribuidor_pais has value for the fabricante I'm seek, why? What I'm doing wrong in the ManyToMany relationship?
I watch in dev.log and join is never made:
doctrine.DEBUG: SELECT t0.direccion AS direccion1, t0.telefono AS
telefono2, t0.fax AS fax3, t0.correo AS correo4, t0.id AS id5,
t0.nombre AS nombre6 FROM nomencladores.fabricante_distribuidor t0
WHERE t0.id = ? ["1"] []
Why?
Solution and some concerns around it
After read and read and do a intensive research through Stackoverflow, Google and so on I get the solution, it working, I do not know if is the best of if it's right so you tell me:
FabricanteDistribuidor.php
class FabricanteDistribuidor
{
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Pais", mappedBy="fabricanteDistribuidor", cascade={"persist"})
*/
private $paises;
/**
* Set paises
*
* #param \AppBundle\Entity\Pais $pais
* #return FabricanteDistribuidor
*/
public function setPaises(\Sencamer\AppBundle\Entity\Pais $pais)
{
$this->paises[] = $pais;
return $this;
}
/**
* Get paises
*
* #return Doctrine\Common\Collections\Collection
*/
public function getPaises()
{
return $this->paises;
}
}
Pais.php
class Pais
{
use IdentifierAutogeneratedEntityTrait;
use NamedEntityTrait;
use ActiveEntityTrait;
/**
* #ORM\ManyToMany(targetEntity="Sencamer\AppBundle\Entity\FabricanteDistribuidor", inversedBy="paises", cascade={"persist"})
* #ORM\JoinTable(name="negocio.fabricante_distribuidor_pais", schema="negocio",
* joinColumns={#ORM\JoinColumn(name="fabricante_distribuidor", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pais_id", referencedColumnName="id")}
* )
*/
protected $fabricanteDistribuidor;
/**
* Add fabricanteDistribuidor
*
* #param AppBundle\Entity\FabricanteDistribuidor $fabricanteDistribuidor
*/
public function addfabricanteDistribuidor(\AppBundle\Entity\FabricanteDistribuidor $fabricanteDistribuidor)
{
$this->fabricanteDistribuidor[] = $fabricanteDistribuidor;
}
/**
* Get fabricanteDistribuidor
*
* #return Doctrine\Common\Collections\Collection
*/
public function getfabricanteDistribuidor()
{
return $this->fabricanteDistribuidor;
}
}
Then in my controller I iterate over the request looking for each pais I want to add and flush it when the object is persisted:
if ($fabricanteDistribuidorForm->isValid()) {
try {
$em->persist($fabricanteDistribuidorEntity);
$em->flush();
$formDataPais = $request->get('fabricanteDistribuidor')['pais'];
foreach ($formDataPais as $paisId) {
$pais = $em->getRepository('AppBundle:Pais')->find($paisId);
$fabricanteDistribuidorEntity->setPaises($pais);
$em->flush();
}
$response['entities'][] = $dataResponse;
} catch (Exception $ex) {
$response['success'] = FALSE;
$response['error'] = $ex->getMessage();
}
} else {
return $this->getFormErrors($fabricanteDistribuidorForm); ;
}
That way all works fine and data is persisted in the right way. Now around this solution I have another issue and a concern. The issue is that I'm trying to get now the related paises from FabricanteDistribuidoras follow and I'm doing something wrong since I can't get their names, so what is wrong in my code?
public function obtenerDetallesFabricanteAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AppBundle:FabricanteDistribuidor')->find($request->query->get('id'));
if ($request->isXmlHttpRequest()) {
$response['entities'] = array();
$dataResponse = array();
// rest of columns ....
if ($entity->getPaises() instanceof Pais) {
$paises = array();
foreach ($entity->getPaises() as $pais) {
$paises[] = $pais->getNombre();
}
$dataResponse['paises'] = $paises;
}
$response['entities'][] = $dataResponse;
return new JsonResponse($response);
}
}
The concern is around the Pais class as you notice I added the inversed side $fabricanteDistribuidor so, do I have to insert this any time I want to insert a new Pais or is just to tell Doctrine how to deal with proxies inside it? I've not clear yet how owning/inversed side works yet maybe due to this I did thing as my code shown. Any advice around this too?
my n-m realtions are like this:
/**
* #ORM\ManyToMany(targetEntity="Sencamer\AppBundle\Entity\Pais")
* #ORM\JoinTable(name="negocio.fabricante_distribuidor_pais", schema="negocio",
* joinColumns={#ORM\JoinColumn(name="fabricante_distribuidor_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pais_id", referencedColumnName="id")}
* )
*/
check if in your join sentence, the "fabricante_distribuidor" is a "id".
And remember, you need to put in your constructor to set the "paises" like arrayCollection:
public function __construct() {
$this->paises = new \Doctrine\Common\Collections\ArrayCollection();
}
and in the n-m relationship is a good practice create addPaises and not setPaises:
public function addPais(\AppBundle\Entity\Pais $pais){
$this->paises[] = $pais;
return $this;
}
I think, somewhere in your code you add the "paises" to your "fabricante.distribuidor", isn't it?
I hope that helps you

Symfony2 - Overriding default Doctrine query when working with entities

For my project, I have a workspace (kind of a user) with many projects and I am wondering if there is a way to override the default Doctrine query for when I call $workspace->getProjects() to only fetch the active projects only (not the archived one). This way I won't have to filter my collection and it will reduce the size of the returned data from the database.
/**
* Acme\DemoBundle\Entity\Workspace
*
* #ORM\Table()
* #ORM\Entity
*/
class Workspace {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
private $id;
/**
* #var ArrayCollection $projects
*
* #ORM\OneToMany(targetEntity="Project", mappedBy="workspace")
*/
private $projects;
/**
* Add projects
*
* #param Project $projects
* #return Workspace
*/
public function addProject( Project $projects ) {
$this->projects[] = $projects;
return $this;
}
/**
* Remove projects
*
* #param Project $projects
*/
public function removeProject( Project $projects ) {
$this->projects->removeElement( $projects );
}
/**
* Get projects
*
* #return Collection
*/
public function getProjects() {
return $this->projects;
}
You will have to write your own method in the Entity Repository Class.
http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes
To do that, you can use criteria.
Here is an exemple from the doctrine doc, to adapt to your needs
use Doctrine\Common\Collections\Criteria;
$criteria = Criteria::create()
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
->orderBy(array("username" => Criteria::ASC))
->setFirstResult(0)
->setMaxResults(20)
;
$birthdayUsers = $object-getUsers()->matching($criteria);
You can create some listener, and catch doctrine events: http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/event-listeners.html
And modify your query before execute as you wish;
Example from Doctrine docs, for custom hook on delete:
<?php
class UserListener extends Doctrine_EventListener
{
/**
* Skip the normal delete options so we can override it with our own
*
* #param Doctrine_Event $event
* #return void
*/
public function preDelete( Doctrine_Event $event )
{
$event->skipOperation();
}
/**
* Implement postDelete() hook and set the deleted flag to true
*
* #param Doctrine_Event $event
* #return void
*/
public function postDelete( Doctrine_Event $event )
{
$name = $this->_options['name'];
$event->getInvoker()->$name = true;
$event->getInvoker()->save();
}
/**
* Implement preDqlDelete() hook and modify a dql delete query so it updates the deleted flag
* instead of deleting the record
*
* #param Doctrine_Event $event
* #return void
*/
public function preDqlDelete( Doctrine_Event $event )
{
$params = $event->getParams();
$field = $params['alias'] . '.deleted';
$q = $event->getQuery();
if ( ! $q->contains( $field ) )
{
$q->from('')->update( $params['component'] . ' ' . $params['alias'] );
$q->set( $field, '?', array(false) );
$q->addWhere( $field . ' = ?', array(true) );
}
}
/**
* Implement preDqlDelete() hook and add the deleted flag to all queries for which this model
* is being used in.
*
* #param Doctrine_Event $event
* #return void
*/
public function preDqlSelect( Doctrine_Event $event )
{
$params = $event->getParams();
$field = $params['alias'] . '.deleted';
$q = $event->getQuery();
if ( ! $q->contains( $field ) )
{
$q->addWhere( $field . ' = ?', array(false) );
}
}
}
I think you can put your logic inside the getter method itself
public function getProjects() {
$filtered_projects = array()
foreach($this->projects as $project)
{
// Your logic here
// e.g.
// if($project->getIsActive()){
// $filtered_projects[] = $project;
// }
}
return $filtered_projects;
}

Categories