Creating more comprehensive associations with Querybuilder in Symfony - symfony

I'm building a query that would be for creating a list of Posts that have a Project that is associated to the user, and within that structure hit the right criteria for "tierAccess."
My query builder:
$qb = $this->em->createQueryBuilder();
foreach($subs as $sub)
{
if($sub->getDisabled() == true)
{
continue;
}
$qb->select('p')
->from('App\Entity\ProjectPost', 'p')
->where('project = '.$sub->getProject()->getId())
->andwhere('p.Published = true')
->andwhere('p.TierAccess = '.$sub->getProjectTier()->getId())
->orderBy('p.PostTime', 'DESC');
$query = $qb->getQuery();
$object[] = $query->execute();
}
What I am aiming to do is add posts that the user subscription will allow for, and within that subscription making sure their access to this post is allowed (ie: tierAccess).
I then return the object variable to pass along to my Twig template file.
The error I'm receiving is:
[Semantical Error] line 0, col 45 near 'project = 3 AND': Error: 'project' is not defined.
My ProjectPost entity:
class ProjectPost
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $PostTitle;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $PostHero;
/**
* #ORM\Column(type="string", length=255, nullable=false)
*/
private $PostType;
/**
* #ORM\Column(type="text")
*/
private $PostBody;
/**
* #ORM\ManyToOne(targetEntity=Project::class, inversedBy="projectPosts")
* #ORM\JoinColumn(nullable=false)
*/
private $Project;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $TierAccess = [];
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $PostTime;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="projectPosts")
* #ORM\JoinColumn(nullable=true)
*/
private $PostBy;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $PostCategories = [];
/**
* #ORM\Column(type="boolean")
*/
private $Published;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $PostCover;
/**
* #ORM\Column(type="boolean")
*/
private $PostSupporter = 0;
}

The basic mistake is this one:
->where('p.Project = '.$sub->getProject()->getId())
Notice that you declare p to be the alias of Post, and then you don't use it. And even if you define the property as Project, you were trying to use it as project.
Nevertheless, the whole thing is rather suspect. Executing a query within a loop usually points to something wrong with the design.
A simpler approach, using WHERE IN instead of a loop and multiple selects:
// get the "subs" ids in an array:
$subsIds = array_map(fn($s) => $s->getProject()->getId(), $subs);
qb->select('p')
->from('App\Entity\ProjectPost', 'p')
->where('p.Project IN :subsIds')
->andwhere('p.Published = true')
->andwhere('p.TierAccess = '.$sub->getProjectTier()->getId())
->orderBy('p.PostTime', 'DESC')
->setParameter('subsIds', $subsIds)
;
$result = $qb->getQuery()->getResult;

Related

Symfony retrieve Unique values from EAV model

I'm trying to make product filters but I can't generate a correct query
А quick look at the base
db visually
here are my entities:
AttributeType:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=AttributeValue::class, mappedBy="attributeType")
*/
private $attributeValue;
public function __construct()
{
$this->attributeValue = new ArrayCollection();
}
AttributeValue:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Product::class, inversedBy="attributeValues")
*/
private $product;
/**
* #ORM\Column(type="string", length=100)
*/
private $value;
/**
* #ORM\ManyToOne(targetEntity=AttributeType::class, inversedBy="attributeValue")
*/
private $attributeType;
For example AttributeType(Color) has AttributeValue(Red, Blue, Green) & i retrieve hundred of red, blue, green AttributeValue for a single Color option
that query returns options with all value(not unique):
return $this->createQueryBuilder('at')
->innerJoin('at.attributeValue', 'attribute_value')
->addSelect('attribute_value')
->getQuery()
->getResult();
I tried to modify the request like this:
return $this->createQueryBuilder('at')
->innerJoin('at.attributeValue', 'attribute_value')
->addSelect('attribute_value.value')->distinct()
->getQuery()
->getResult();
(there were other attempts, but they were all not even close)
How do I get unique values for each option?
I will be grateful for any help
And thx for your time.
I get unique values for each option
public function findOptionsWithUniqueValue()
{
$result = $this->getEntityManager()->createQueryBuilder()
->addSelect('attribute_type.name, attribute_value.value')
->distinct()
->from(AttributeType::class,'attribute_type')
->from(AttributeValue::class, 'attribute_value')
->andWhere('attribute_type.id = attribute_value.attributeType')
->getQuery()
->getResult()
;
$out = [];
while( $a = array_shift($result)) {
$out[$a['name']][] = $a['value'];
}
return $out;
}

Self referencing children not attached

I have a family tree like that:
class Family
{
/**
* #var integer
*
* #ORM\Column(type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var Family
*
* #ORM\ManyToOne(targetEntity="Family", inversedBy="children")
*/
private $parent;
/**
* #var string
*
* #ORM\Column(name="name", type="string")
*/
private $name;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Family", mappedBy="parent")
*/
private $children;
// [...]
}
I'm trying to findAll() and get the parent and children attached
$familyRepo = $this->em->getRepository(Family::class);
$families = $familyRepo->findAll();
foreach ($families as $family) {
dump($family->getParent()->getName());
}
I can see the parents name dumped and only one query executed, so they are well attached.
However if I try to show the children:
dump($family->getChildren()->count());
I'm seeing as much queries as there are families.
How can I get the children attached as the parents are ? (without more queries)
What am I forgetting ?
On the one-to-many relation for $children you can specify to fetch objects eagerly as follows:
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Family", mappedBy="parent", fetch="EAGER")
*/
private $children;
See also the docs for other params.
Following #dlondero's suggestion, I forced the deep fetch into the repository.
Here is how I did:
public function getRootNodes($eagerLevels = 5)
{
$qb = $this->createQueryBuilder('entity0')
->select('partial entity0.{id, name, parent}')
->where('entity0.parent IS NULL')
;
for ($i = 0; $i < $eagerLevels; $i++) {
$qb
->leftJoin('entity'.$i.'.children', 'entity'.($i+1))
->addSelect('partial entity'.($i+1).'.{id, name, parent}')
;
}
return $qb->getQuery()->getResult();
}
This partially fetches just what I need so no lazy loading happens.
I also made the level of deepness configurable.

Symfony 3 UniqueEntity validation on update

I am facing an issue with UniqueEntity validation.
I have a field "internal_asset_number" which should be unique and it's working fine on create. On update when i edit the existing current data with the same values, it shows "There is already an asset with that internal number!" but it shouldn't because it's the same entry.
The entity:
/**
* Asset
*
* #ORM\Table(schema="assets", name="asset", uniqueConstraints= {#ORM\UniqueConstraint(name="uk_asset_internal_asset_number_client_id", columns={"internal_asset_number", "client_id"})})
* #ORM\Entity(repositoryClass="Api\AssetBundle\Entity\AssetRepository")
* #UniqueEntity(fields={"internalAssetNumber"}, groups={"post", "put"}, message="There is already an asset with that internal number!")
*/
class Asset
{
/**
* #var guid
*
* #ORM\Column(name="id", type="string")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="client_id", type="string", length=255, nullable=false)
*/
private $clientId;
/**
* #var string
*
* #ORM\Column(name="internal_asset_number", type="string", length=255, nullable=true, unique=true)
*/
private $internalAssetNumber;
Update method:
public function putAssetAction(Request $request, $id)
{
$data = $this->deserializer('Api\AssetBundle\Entity\Asset', $request, 'put');
if ($data instanceof \Exception) {
return View::create(['error' => $data->getMessage()], 400);
}
$validator = $this->get('validator');
$errors = $validator->validate($data, null, 'put');
if (count($errors) > 0) {
$errorsResponse = [];
foreach ($errors as $error) {
$errorsResponse = $error->getMessage();
}
return View::create(array('error' => $errorsResponse), 400);
}
...
As #xabbuh commented, the problem is that the entity you persist after update is not retrieved through the entityManager so when you persist it the entity manager thinks it is a new entity.
Here is how to solve it:
$entityManager->merge($entity);
This will tell the entitymanager to merge your serialized entity with the managed one
Some more explanation on merge():
https://stackoverflow.com/a/15838232/5758328

symfony doctrine getArrayResult error 500

I'm trying to grab some data from an table which works fine as long as I don't use
->andwhere('s.client_id = :clientid')
->setParameter('clientid', $this->clientId)
I f use the two line above to locate only neccessary data, I end up in a error 500 :(:(
The entity looks like:
class SanitationType
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
/**
* #ORM\Column(type="string", length=20)
* #Gedmo\Translatable
*/
private $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Gedmo\Translatable
*/
private $description;
/**
* #ORM\Column(name="`enabled`", type="boolean")
*/
private $enabled;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $created_by;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $created;
..............
and my script to grab the data inside the controller looks like this:
$query = $em->createQueryBuilder()
->select('s')
->from('PrSensorBundle:SanitationType', 's')
->where('s.enabled = 1')
->andwhere('s.client_id = :client_id')
->setParameter('client_id', $this->clientId)
->orderBy('s.name', 'ASC')
->getQuery();
$results=$query->getArrayResult();
I don't see any error but it is not working at all :(:(
Do I forgot something?
Couple of things. First, it's not clear what $this->clientId refers to, but if you're trying to reference the id of the client object associated with a SanitationType object, then you'd need to have a public getClient() method on the SanitationType class and a getId() method on the Client class. So obtaining the client id from a SanitationType object $sanitationType then becomes:
$sanitationType->getClient()->getId()
Second, there is no client_id property in the SanitationType class. Doctrine sees the properties of a class as you've defined them. So in this case, to look up a SanitationType object(s) in the database by the id of a Client association(s), you would need to perform an inner join. Your query builder would look like this:
$query = $em->createQueryBuilder()
->select('s')
->from('PrSensorBundle:SanitationType', 's')
->innerJoin('s.client', 'sc')
->where('s.enabled = 1')
->andwhere('sc.id = :client_id')
->setParameter('client_id', $this->clientId)
->orderBy('s.name', 'ASC')
->getQuery();
$results=$query->getArrayResult();

Symfony2 setting other rows to 0 after/before flush

Here's what I'm having trouble with.
I've a Table which contains a column called shown_on_homepage and only one row should be set to 1, the rest should all be set to 0. I'm trying to add a new row to the database and this one should be set to 1, setting the one that previously had a 1 to 0.
In MySQL I know this can be achieved by issuing an update before the insert:
UPDATE table_name SET shown_on_homepage = 0
Here's my Entity:
class FeaturedPerson {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="content", type="string", length=2500, nullable=false)
*/
private $content;
/**
* #var \DateTime
*
* #ORM\Column(name="date_updated", type="datetime")
*/
private $dateUpdated;
/**
* #var bool
*
* #ORM\Column(name="shown_on_homepage", type="boolean", nullable=false)
*/
private $isShownOnHomepage;
//...
public function getIsShownOnHomepage() {
return $this->isShownOnHomepage;
}
public function setIsShownOnHomepage($isShownOnHomepage) {
$this->isShownOnHomepage = $isShownOnHomepage;
return $this;
}
}
And for the Controller I've:
$featured = new FeaturedPerson();
$featured->setContent('Test content.');
$featured->setDateUpdated('01/02/2013.');
$featured->setIsShownOnHomepage(TRUE);
$em = $this->getDoctrine()->getManager();
$em->persist($featured);
$em->flush();
It does add the new row, but the one that had a shown_on_homepage set to 1 still has it. I've researched but I couldn't find a way to achieve this, I hope you can help me.
You could execute a query prior to your existing code in your controller:
$queryBuilder = $this->getDoctrine()->getRepository('YourBundleName:FeaturedPerson')->createQueryBuilder('qb');
$result = $queryBuilder->update('YourBundleName:FeaturedPerson', 'd')
->set('d.isShownOnHomepage', $queryBuilder->expr()->literal(0))
->where('d.isShownOnHomepage = :shown')
->setParameter('shown', 1)
->getQuery()
->execute();
Change 'YourBundleName' to your bundle name.

Resources