I have a class Post:
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post
{
const TYPE_TEXT = 1;
const TYPE_PHOTOS = 2;
const TYPE_VIDEO = 3;
/**
* #ORM\OneToMany(targetEntity="Photo", mappedBy="post")
*/
private $photos;
and other properties, methods, etc...
And I want to bring back only the posts that have any photos.
I have a DQL query like:
$qb = $this->createQueryBuilder('p');
$qb->select('p, postPhotos, etc...')
->leftJoin('p.photos', 'postPhotos')
->leftJoin('p.videos', 'postVideos')
etc...
if ($mediaType != null)
{
switch ($mediaType) {
case Post::TYPE_PHOTOS:
$qb->andWhere('postPhotos != :null')
->setParameter('null', null);
The "!= :null" doesn't work, nor does COUNT(postPhotos) (obviously for aggregation reasons).
Is there a way I can specify only to bring back the posts that have 1 or more photos?
Quick Answer: If you replace your usage of leftJoin with just join (or innerJoin) then you'll get what you want: only Posts that have at least 1 Photo.
Details
If you take a look at this helpful SO Q&A:
Different MySQL Join Methods
... you'll find some excellent venn diagrams showing the difference between a left and inner join. Then, if you look into Doctrine's Doctrine\ORM\QueryBuilder class, you'll find that they have three join methods:
join (which just calls innerJoin)
innerJoin
leftJoin
/**
* Creates and adds a join over an entity association to the query.
*
* The entities in the joined association will be fetched as part of the query
* result if the alias used for the joined association is placed in the select
* expressions.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
* </code>
*
* #param string $join The relationship to join.
* #param string $alias The alias of the join.
* #param string|null $conditionType The condition type constant. Either ON or WITH.
* #param string|null $condition The condition for the join.
* #param string|null $indexBy The index for the join.
*
* #return self
*/
public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
}
/**
* Creates and adds a join over an entity association to the query.
*
* The entities in the joined association will be fetched as part of the query
* result if the alias used for the joined association is placed in the select
* expressions.
*
* [php]
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
*
* #param string $join The relationship to join.
* #param string $alias The alias of the join.
* #param string|null $conditionType The condition type constant. Either ON or WITH.
* #param string|null $condition The condition for the join.
* #param string|null $indexBy The index for the join.
*
* #return self
*/
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
$parentAlias = substr($join, 0, strpos($join, '.'));
$rootAlias = $this->findRootAlias($alias, $parentAlias);
$join = new Expr\Join(
Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
);
return $this->add('join', [$rootAlias => $join], true);
}
/**
* Creates and adds a left join over an entity association to the query.
*
* The entities in the joined association will be fetched as part of the query
* result if the alias used for the joined association is placed in the select
* expressions.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
* </code>
*
* #param string $join The relationship to join.
* #param string $alias The alias of the join.
* #param string|null $conditionType The condition type constant. Either ON or WITH.
* #param string|null $condition The condition for the join.
* #param string|null $indexBy The index for the join.
*
* #return self
*/
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
$parentAlias = substr($join, 0, strpos($join, '.'));
$rootAlias = $this->findRootAlias($alias, $parentAlias);
$join = new Expr\Join(
Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
);
return $this->add('join', [$rootAlias => $join], true);
}
Changing your code to use innerJoin (or just join) will lead Doctrine to issue an INNER JOIN in the generated SQL which will return only records where "something" exists on both sides of the join, thus, any Post that has no Photos will not be included in the results.
Related
I've configured Laravel Scout and can use ::search() on my models.
The same models also use SoftDeletes. How can I combine the ::search() with withTrashed()?
The code below does not work.
MyModel::search($request->input('search'))->withTrashed()->paginate(10);
The below does work but does not include the trashed items.
MyModel::search($request->input('search'))->paginate(10);
Update 1
I found in the scout/ModelObserver that deleted items are made unsearchable. Which is a bummer; I wanted my users to be able to search through their trash.
Update 2
I tried using ::withoutSyncingToSearch, as suggested by #camelCase, which I had high hopes for, but this also didn't work.
$model = MyModel::withTrashed()->where('slug', $slug)->firstOrFail();
if ($model->deleted_at) {
$model->forceDelete();
} else {
MyModel::withoutSyncingToSearch(function () use ($model) {
$model->delete();
});
}
This caused an undefined offset when searching for a deleted item. By the way, I'm using the TNTSearch driver for Laravel Scout. I don't know if this is an error with TNTSearch or with Laravel Scout.
I've worked out a solution to your issue. I'll be submitting a pull request for Scout to hopefully get it merged with the official package.
This approach allows you to include soft deleted models in your search:
App\User::search('search query string')->withTrashed()->get();
To only show soft deleted models in your search:
App\User::search('search query string')->onlyTrashed()->get();
You need to modify 3 files:
Builder.php
In laravel\scout\src\Builder.php add the following:
/**
* Whether the search should include soft deleted models.
*
* #var boolean
*/
public $withTrashed = false;
/**
* Whether the search should only include soft deleted models.
*
* #var boolean
*/
public $onlyTrashed = false;
/**
* Specify the search should include soft deletes
*
* #return $this
*/
public function withTrashed()
{
$this->withTrashed = true;
return $this;
}
/**
* Specify the search should only include soft deletes
*
* #return $this
*/
public function onlyTrashed()
{
$this->onlyTrashed = true;
return $this;
}
/**
* Paginate the given query into a simple paginator.
*
* #param int $perPage
* #param string $pageName
* #param int|null $page
* #return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function paginate($perPage = null, $pageName = 'page', $page = null)
{
$engine = $this->engine();
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$perPage = $perPage ?: $this->model->getPerPage();
$results = Collection::make($engine->map(
$rawResults = $engine->paginate($this, $perPage, $page), $this->model, $this->withTrashed, $this->onlyTrashed
)); // $this->withTrashed, $this->onlyTrashed is new
$paginator = (new LengthAwarePaginator($results, $engine->getTotalCount($rawResults), $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]));
return $paginator->appends('query', $this->query);
}
Engine.php
In laravel\scout\src\Engines\Engine.php modify the following:
/**
* Map the given results to instances of the given model.
*
* #param mixed $results
* #param \Illuminate\Database\Eloquent\Model $model
* #param boolean $withTrashed // New
* #return \Illuminate\Database\Eloquent\Collection
*/
abstract public function map($results, $model, $withTrashed, $onlyTrashed); // $withTrashed, $onlyTrashed is new
/**
* Get the results of the given query mapped onto models.
*
* #param \Laravel\Scout\Builder $builder
* #return \Illuminate\Database\Eloquent\Collection
*/
public function get(Builder $builder)
{
return Collection::make($this->map(
$this->search($builder), $builder->model, $builder->withTrashed, $builder->onlyTrashed // $builder->withTrashed, $builder->onlyTrashed is new
));
}
And finally, you just need to modify your relative search engine. I'm using Algolia, but the map method appears to be the same for TNTSearch.
AlgoliaEngine.php
In laravel\scout\src\Engines\AlgoliaEngine.php modify the map method to match the abstract class we modified above:
/**
* Map the given results to instances of the given model.
*
* #param mixed $results
* #param \Illuminate\Database\Eloquent\Model $model
* #param boolean $withTrashed // New
* #return \Illuminate\Database\Eloquent\Collection
*/
public function map($results, $model, $withTrashed, $onlyTrashed) // $withTrashed, $onlyTrashed is new
{
if (count($results['hits']) === 0) {
return Collection::make();
}
$keys = collect($results['hits'])
->pluck('objectID')->values()->all();
$modelQuery = $model->whereIn(
$model->getQualifiedKeyName(), $keys
);
if ($withTrashed) $modelQuery->withTrashed(); // This is where the query will include deleted items, if specified
if ($onlyTrashed) $modelQuery->onlyTrashed(); // This is where the query will only include deleted items, if specified
$models = $modelQuery->get()->keyBy($model->getKeyName());
return Collection::make($results['hits'])->map(function ($hit) use ($model, $models) {
$key = $hit['objectID'];
if (isset($models[$key])) {
return $models[$key];
}
})->filter();
}
TNTSearchEngine.php
/**
* Map the given results to instances of the given model.
*
* #param mixed $results
* #param \Illuminate\Database\Eloquent\Model $model
*
* #return Collection
*/
public function map($results, $model, $withTrashed, $onlyTrashed)
{
if (count($results['ids']) === 0) {
return Collection::make();
}
$keys = collect($results['ids'])->values()->all();
$model_query = $model->whereIn(
$model->getQualifiedKeyName(), $keys
);
if ($withTrashed) $model_query->withTrashed();
if ($onlyTrashed) $model_query->onlyTrashed();
$models = $model_query->get()->keyBy($model->getKeyName());
return collect($results['ids'])->map(function ($hit) use ($models) {
if (isset($models[$hit])) {
return $models[$hit];
}
})->filter();
}
Let me know how it works.
NOTE: This approach still requires you to manually pause syncing using the withoutSyncingToSearch method while deleting a model; otherwise the search criteria will be updated with unsearchable().
this is my solution.
// will return matched ids of my model instance
$searchResultIds = MyModel::search($request->input('search'))->raw()['ids'];
MyModel::whereId('id', $searchResultIds)->onlyTrashed()->get();
I can't add facets to the query. I try
$query = new Query;
$query->facetBuilders = [ new \eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder];
services:
myservice:
class: mynamespace\FacetHandler
tags:
- {name: ezpublish.search.solr.content.facet_builder_visitor}
And I have got the error "Intentionally not implemented: No visitor available for: eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder"
Also I have tried tag "ezpublish.search.solr.content.facet_builder_visitor.aggregate"
What I do wrong?
you are required to hand over the field you want to apply the facet on.
In your case it might look like this:
$query = new Query;
$query->facetBuilders = [ new \eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder(
[
'fieldPaths' => 'article/title'
]
)];
"article" is the type-identifier of the class to filter by. I have yet to try if you can actually use it without a class-limitation.
"title" defines the field-identifier to use for the facet.
You may also use regex or sort (in addition to the fieldPaths-key to filter and sort the result.
The possible values for sort are listed as constants in the FieldFacetBuilder-class
Hope this helps.
Configure your field class as below
parameters:
ezpublish.search.solr.query.content.facet_builder_visitor.field.class: Your\Bundle\Query\Content\FacetBuilderVisitor\Field
Define your service as below:
ezpublish.search.solr.query.content.facet_builder_visitor.field:
class: "%ezpublish.search.solr.query.content.facet_builder_visitor.field.class%"
tags:
- {name: ezpublish.search.solr.query.content.facet_builder_visitor}
Implement your class
<?php
/**
*
*/
namespace Your\Bundle\Query\Content\FacetBuilderVisitor;
use EzSystems\EzPlatformSolrSearchEngine\Query\FacetBuilderVisitor;
use eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder;
use eZ\Publish\API\Repository\Values\Content\Search\Facet;
/**
* Visits the Field facet builder.
*/
class Field extends FacetBuilderVisitor
{
/**
* CHeck if visitor is applicable to current facet result.
*
* #param string $field
*
* #return bool
*/
public function canMap($field)
{
return $field === 'field_id';
}
/**
* Map Solr facet result back to facet objects.
*
* #param string $field
* #param array $data
*
* #return Facet
*/
public function map($field, array $data)
{
return new Facet\FieldFacet(
array(
'name' => 'field',
'entries' => $this->mapData($data),
)
);
}
/**
* Check if visitor is applicable to current facet builder.
*
* #param FacetBuilder $facetBuilder
*
* #return bool
*/
public function canVisit(FacetBuilder $facetBuilder)
{
return $facetBuilder instanceof FacetBuilder\FieldFacetBuilder;
}
/**
* Map field value to a proper Solr representation.
*
* #param FacetBuilder $facetBuilder;
*
* #return string
*/
public function visit(FacetBuilder $facetBuilder)
{
return array(
'facet.field' => 'field_id',
'f.field_id.facet.limit' => $facetBuilder->limit,
'f.field_id.facet.mincount' => $facetBuilder->minCount,
);
}
}
No more exception now ;) But does not work :'( https://doc.ez.no/display/DEVELOPER/Browsing%2C+finding%2C+viewing#Browsing,finding,viewing-PerformingaFacetedSearch
i'm trying to retrive data from several tables to make a json, but i'm stuck for the table having UniqueConstraint on 2 keys.
Here's my QueryBuilder sofar :
$qb = $this->_em->createQueryBuilder()
->select('partial s.{id, activity}, partial a.{id, title}, partial p.{id, evaluationType}')
->from('Innova\PathBundle\Entity\Step', 's')
->leftJoin('s.activity', 'a') //join on Activity entity
->leftJoin('a.parameters', 'p') // join on ActivityParameters entity
->andWhere('s.path = 2')
;
but i want to also join on Evaluation entity, which is :
/**
* #ORM\Table(
* name="claro_activity_evaluation",
* uniqueConstraints={
* #ORM\UniqueConstraint(
* name="user_activity_unique_evaluation",
* columns={"user_id", "activity_parameters_id"}
* )
* }
* )
*/
class Evaluation
{
/**
* #ORM\ManyToOne(targetEntity="Claroline\CoreBundle\Entity\User")
* #ORM\JoinColumn(onDelete="CASCADE", nullable=false)
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="Claroline\CoreBundle\Entity\Activity\ActivityParameters")
* #ORM\JoinColumn(name="activity_parameters_id", onDelete="CASCADE", nullable=false)
*/
protected $activityParameters;
/**
* #ORM\Column(name="attempts_count", type="integer", nullable=true)
*/
protected $attemptsCount;
}
the User entity :
/**
* #ORM\Table(name="claro_user")
* #ORM\Entity(repositoryClass="Claroline\CoreBundle\Repository\UserRepository")
*/
class User
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="first_name", length=50)
* #Assert\NotBlank()
*/
protected $firstName;
}
The ActivityParameters entity
/**
* #ORM\Entity
* #ORM\Table(name="claro_activity_parameters")
*/
class ActivityParameters
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var \Claroline\CoreBundle\Entity\Resource\Activity
*
* #ORM\OneToOne(
* targetEntity="Claroline\CoreBundle\Entity\Resource\Activity",
* mappedBy="parameters"
* )
* #ORM\JoinColumn(name="activity_id", onDelete="CASCADE", nullable=true)
*/
protected $activity;
/**
* #var string
*
* #ORM\Column(name="evaluation_type", nullable=true)
*/
protected $evaluationType;
/**
* #return string
*/
public function getEvaluationType()
{
return $this->evaluationType;
}
}
the Activity entity
/**
* #ORM\Table(name="claro_activity")
*/
class Activity
{
/**
* #var string
* #ORM\Column(length=255, nullable=true)
*/
protected $title;
/**
* #ORM\OneToOne(
* targetEntity="Claroline\CoreBundle\Entity\Activity\ActivityParameters",
* inversedBy="activity",
* cascade={"persist"}
* )
* #ORM\JoinColumn(name="parameters_id", onDelete="cascade", nullable=true)
*/
protected $parameters;
/**
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
I have no clue how to modify this querybuilder to retrieve also hte data from Evaluation entity. I want something like :
$qb = $this->_em->createQueryBuilder()
->select('partial s.{id, activity}, partial a.{id, title}, partial p.{id, evaluationType}, e')
->from('Innova\PathBundle\Entity\Step', 's')
->leftJoin('s.activity', 'a') //join on Activity entity
->leftJoin('a.parameters', 'p') // join on ActivityParameters entity
->andWhere('s.path = 2')
->leftJoin('?i dont know what?', 'e') // join on Evaluation entity
->andWhere('e.user = 3') //data for a specific user
;
Thank you for any help
I "seem" to have found a nearly working solution, reading this topic : i had to make a subquery :
$qb->select('e, partial p.{id, evaluationType}, partial a.{id, title}')
->from('Claroline\CoreBundle\Entity\Activity\Evaluation', 'e')
->leftJoin('e.activityParameters', 'p')
->leftJoin('p.activity', 'a')
->where(
$qb->expr()->in( //needs a subquery
'a.id',
$subq->select('a2.id')
->from('Innova\PathBundle\Entity\Step', 's')
->leftJoin(
's.activity',
'a2',
\Doctrine\ORM\Query\Expr\Join::WITH,
$subq->expr()->eq('s.path', '?1') //can't do a andWhere('s.path = :path'))
)
->getDQL() //needs a dql, not a querybuilder for the main query
)
)
->andWhere($qb->expr()->eq('e.user', '?2')) //can't do a andWhere('e.user = :user'))
->setParameter(1, $pid)
->setParameter(2, $uid)
;
I just would also like to retrieve the Step entity data (id, ...) in the query result. I can't add 's' to the main select : i have Error: 's' is used outside the scope of its declaration.
I am trying to add an index for a column over Magento data setup script.
/** #var Mage_Eav_Model_Entity_Setup $installer */
$installer = $this;
$installer->startSetup();
$installer
->getConnection()
->addKey(
$installer->getTable('enterprise_rma/rma'),
'IDX_EXPORT_DATE',
'export_date'
);
However our inspection tool complains:
The method Varien_Db_Adapter_Pdo_Mysql::addKey() has been deprecated with message: since 1.5.0.0
What I can use instead of addKey() in this case?
Look at the addKey function in the class Varien_Db_Adapter_Pdo_Mysql:
public function addKey($tableName, $indexName, $fields, $indexType = 'index', $schemaName = null)
{
return $this->addIndex($tableName, $indexName, $fields, $indexType, $schemaName);
}
It is just making a call to the addIndex function of the same class, this function is not deprecated, so you should use this one.
/**
* Add new index to table name
*
* #param string $tableName
* #param string $indexName
* #param string|array $fields the table column name or array of ones
* #param string $indexType the index type
* #param string $schemaName
* #return Zend_Db_Statement_Interface
* #throws Zend_Db_Exception|Exception
*/
public function addIndex($tableName, $indexName, $fields,
$indexType = Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX, $schemaName = null)
(My code come from Magento Enterprise 1.12)
The point of this question is to figure out what technique is better and heard different opinions from some skilled symfony2 coders.
An example will be ilustrated on challenges and "challenge rating" table where many people can rate certain challenge. (something like stackoverflow vote question system).
The tables look like this: (like_dislike is boolean(1= like, 0 = dislike)
The amount of data will be from 10-200+ rates for challenge.
Working with collections
Challenges entity
/**
* Challanges
*
* #ORM\Table(name="challanges")
* #ORM\Entity(repositoryClass="TB\ChallangesBundle\Entity\ChallangesRepository")
*/
class Challanges
{
/**
* #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=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255, unique=true)
*/
private $slug;
/**
* #var string
*
* #ORM\Column(name="description", type="text")
* #Assert\NotBlank()
*/
private $description;
/**
* #var \DateTime
* #ORM\Column(name="start_date", type="datetime", nullable=false)
* #Assert\DateTime()
*/
private $start_date;
/**
* #var \DateTime
* #ORM\Column(name="end_date", type="datetime", nullable=false)
* #Assert\DateTime()
*/
private $end_date;
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", fetch="EXTRA_LAZY")
* #ORM\JoinColumn(name="owner_id", referencedColumnName="id", nullable=false)
*/
protected $owner;
/**
* #ORM\OneToMany(targetEntity="TB\ChallangesBundle\Entity\ChallangeRating", mappedBy="challange", cascade={"persist", "remove"})
*/
protected $likes;
/**
* Constructor
*/
public function __construct()
{
$this->likes = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add likes
*
* #param \TB\ChallangesBundle\Entity\ChallangeRating $likes
* #return Challanges
*/
public function addLike(\TB\ChallangesBundle\Entity\ChallangeRating $likes)
{
$this->likes[] = $likes;
return $this;
}
/**
* Remove likes
*
* #param \TB\ChallangesBundle\Entity\ChallangeRating $likes
*/
public function removeLike(\TB\ChallangesBundle\Entity\ChallangeRating $likes)
{
$this->likes->removeElement($likes);
}
/**
* Get likes
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getLikes()
{
return $this->likes;
}
public function filterLikesInChallenge($like_dislike) {
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('like_dislike', $like_dislike));
return $this->likes->matching($criteria);
}
public function checkIfUserRatedAlready(\TB\UserBundle\Entity\User $user)
{
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('fan', $user));
return $this->likes->matching($criteria);
}
Challenge rating entity
<?php
namespace TB\ChallangesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ChallangeRating
*
* #ORM\Table(name="challange_rating")
* #ORM\Entity(repositoryClass="TB\ChallangesBundle\Entity\ChallangeRatingRepository")
*/
class ChallangeRating
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var boolean
*
* #ORM\Column(name="like_dislike", type="boolean")
*/
private $like_dislike;
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="fans")
*/
protected $fan;
/**
* #ORM\ManyToOne(targetEntity="TB\ChallangesBundle\Entity\Challanges", inversedBy="likes")
*/
protected $challange;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Get like_dislike
*
* #return boolean
*/
public function getLikeDislike()
{
return $this->like_dislike;
}
/**
* Set like_dislike
*
* #param boolean $like_dislike
* #return ChallangeRating
*/
public function setLikeDislike($like_dislike)
{
$this->like_dislike = $like_dislike;
return $this;
}
/**
* Set fan
*
* #param \TB\UserBundle\Entity\User $fan
* #return ChallangeRating
*/
public function setFan(\TB\UserBundle\Entity\User $fan = null)
{
$this->fan = $fan;
return $this;
}
/**
* Get fan
*
* #return \TB\UserBundle\Entity\User
*/
public function getFan()
{
return $this->fan;
}
/**
* Set challange
*
* #param \TB\ChallangesBundle\Entity\Challanges $challange
* #return ChallangeRating
*/
public function setChallange(\TB\ChallangesBundle\Entity\Challanges $challange = null)
{
$this->challange = $challange;
return $this;
}
/**
* Get challange
*
* #return \TB\ChallangesBundle\Entity\Challanges
*/
public function getChallange()
{
return $this->challange;
}
}
Okay and now i would like:
Display challenge details
Display rate feature (like, dislike) also with numbers of likes and dislikes
Display the list of all users that have rated this challenge
Controller
So classic beginning with obtaining $challenge
// this will take care of point number 1 (display challenge details) (1.)
$challange = $challangesRepo->findOneBy(array('slug'=>$slug));
// display numbers of likes and dislikes for certain challenge (2.)
But now comes a decision...
Question 1
Should i obtain the likes via quering the DB from querybuilder? (classic select count)
OR
Should i use collections and criteria like this ? :
$challangeLikes = $challange->filterLikesInChallenge(1);
$challangeDislikes = $challange->filterLikesInChallenge(0);
**What is better for memory usage? what is better for DB performances? **
If i am not wrong the following two queries are executed by these two methods:
SELECT
t0.id AS id1,
t0.like_dislike AS like_dislike2,
t0.fan_id AS fan_id3,
t0.challange_id AS challange_id4
FROM
challange_rating t0
WHERE
(
t0.like_dislike = ? AND t0.challange_id = ?
)
Parameters: [1, 12]
SELECT
t0.id AS id1,
t0.like_dislike AS like_dislike2,
t0.fan_id AS fan_id3,
t0.challange_id AS challange_id4
FROM
challange_rating t0
WHERE
(
t0.like_dislike = ? AND t0.challange_id = ?
)
And now i can pass the number of likes,dislikes to the view as follow:
'challangeLikes'=>$challangeLikes->count(),
'challangeDislikes'=>$challangeDislikes->count(),
Question 2
What if i want to know if certain user rated this challenge already?
Again...
*Should i use classic querybuilder style with select count *
OR
I should use a method like:
$ratedAlreadyCol = $challange->checkIfUserRatedAlready($user)->first();
That will execute actually another query ? something like classic select count but the collection will do this for me? So it's not a search in some big memory array with allll likes but it's a query to DB ?
SELECT
t0.id AS id1,
t0.like_dislike AS like_dislike2,
t0.fan_id AS fan_id3,
t0.challange_id AS challange_id4
FROM
challange_rating t0
WHERE
(
t0.fan_id = ? AND t0.challange_id = ?
)
Parameters: [25, 12]
Question 3 - probably the most important for performances
I want to display all "fans - people who rated the certain challenge"...
Again...
Should i create a separate querybuilder method in repository with selecting all ratings for certain challenge with inner join to the users table (so i can display profile image and username)
OR
Should i just get all ratings and in twig loop through it like:
$challangeLikesCollection = $challange->getLikes();
{% for bla bla
BUT
If i will do it this way... doctrine will execute a select query to the user table for every "fan" in loop... and when there will be let's say... 200 fans... that's not good right?
BONUS QUESTION
Can somehow please provide his way of dealing with these situations? any suggestions?
Or do you use any other technique?
i care a lot about memory usage and DB load time because this will be used everywhere and every user will have such a list with different challenges. The list will consist of let's say 15 challenges and to connect all the likes,dislikes to every challenge in the list etc etc... performances...
Thank you for your explanations, tips and hints that will help me and other readers to move on another level!
I would do the following:
Denormalize a little and add number of likes and number of dislikes fields to Challenge and update these values in addLike and removeLike
Rename like_dislike to like as it's a boolean field and 1 means like 0 means dislike
Query the list of users with a separate query and use array hydration and INDEXBY username (it must be unique or user id) or maybe create a custom hydrator
SELECT u.username, u.photo FROM User u INNER JOIN u.ratings WITH r.fan = :fan INDEX BY u.username
or something like that. And you can check if the current user's username is in the array or not.
I think this could be performant enough.
Some explanation:
INDEX BY means that the result collection or array key (index) will be the value of a field (this field has to be unique). When you use INDEX BY the result set will contain known keys so you can reach (and e.g. check for existence) individual results directly in constant time (you don't have to search through the whole result set).
Doctrine uses PDO underneath, hydration means how the PDO result set will be processed and transformed into something else. The default object hydration means the result set will be transformed into an object graph, it is a very expensive operation. There are other hydration modes which are less expensive, but you loose some felxibility. E.g. if you use array hydration the result will be an array of arrays so you can't modify it (I mean persist back to the database) so it's just for reading and as the result not entity objects you can't use it's methods, e.g. custom getters. You can create custom hydrators if you want.