Laravel Scout - searchableAs index - php

I have a Thread that I am indexing to Algolia and i want to be able to only allow user to search by the title and body of the thread.
So in my thread model:
/**
* Get the index name for the model.
*
* #return string
*/
public function searchableAs()
{
return 'title';
}
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
unset($array['created_at'],$array['updated_at']);
return $array;
}
So the first method is to index a specific column of the thread table and the second method is to choose which columns to place it in algolia.
I guess my question is what is searchableAs() for as I am confused with the explanation by the doc?

Related

How to join two entity methods as one

Based on a private messaging app, my user entity currently have two methods to retrieve messages :
One to get the messages sent by the user
/**
* #ORM\OneToMany(targetEntity="App\Entity\MessagePrive", mappedBy="emetteur")
*/
private $messagesPrivesEmis;
/**
* #return Collection|MessagePrive[]
*/
public function getMessagesPrivesEmis(): Collection {
return $this->messagesPrivesEmis;
}
and another one to get the messages received from other users
/**
* #ORM\OneToMany(targetEntity="App\Entity\MessagePrive", mappedBy="recepteur")
*/
private $messagesPrivesRecus;
/**
* #return Collection|MessagePrive[]
*/
public function getMessagesPrivesRecus(): Collection {
return $this->messagesPrivesRecus;
}
The first method get the messages where emetteur is equal to user id, while the second get the messages where recepteur is equal to user id. Both are Symfony default methods
Is it possible to "merge" those two methods so it get all messages sent and received by the user in one single query?
Or should I resort to custom DQL?
public function getMerged(): Collection {
return new ArrayCollection(
array_merge(this->messagesPrivesEmis->toArray(), $this->messagesPrivesRecus->toArray())
);
}

Doctrine Constraint on a Many-to-Many Relation

We have an Entity 'User' that has a Many-to-Many Relation to different 'Types'. The MySQL Relation is handled via a Cross Table.
class User {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Type")
* #ORM\JoinTable(name="user_type",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")}
* )
*/
protected $types;
}
class Type {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User")
* #ORM\JoinTable(name="user_type",
* joinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")}
* )
*/
protected $users;
/**
* #ORM\Column(type="datetime")
*/
protected $publishedAt;
}
How can I limit the Responses in this many-to-many relation to only show Types that have already been published?
That is the factual SQL should include a WHERE that filters the corresponding items that have not been published yet. Can I do that with an annotation or something like that?
I know I can do this by filtering the returned collection. But is there anything more efficient than that?
This question is kind of a douplicate.
It has been answered here: php - Doctrine2 association mapping with conditions - Stack Overflow
One of the comments tells, that this results in an error for Many-to-Many Relations. As of Doctrine 2.5 this no longer is the case.
So what you can do in doctrine is hand over a query condition when you request the entities of the relation:
So you do not change the Annotation, but the getter for the Entity:
class User {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Type")
* #ORM\JoinTable(name="user_type",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")}
* )
*/
protected $types;
public function getTypes()
{
$criteria = Criteria::create()->where(Criteria::expr()->lte('publishedAt', date('Y-m-d')));
return $this->types->matching($criteria);
}
}
This will result in a Lazy-Fetch (depending on your settings) of the required items. All the doctrine magic still works, like caching and the like. So the collection will only be fetched, if it has not been fetched...
You can use Criteria.
Add a function to your User class called eg getPublishedTypes.
public function getPublishedTypes()
{
return $this->getTypes()->matching(TypeRepository::createPublishedCriteria());
}
And in your TypeRepository add the function createPublishedCriteria.
static public function createPublishedCriteria()
{
return Criteria::create()
->andWhere(Criteria::expr()->lte('publishedAt', date('Y-m-d')); //your logic
}
Note: function has to be static
You can also use that criteria later on in your query builder with following line
$qb->addCriteria(self::createPublishedCriteria()).
Another solution with bad practise could be collection filtering. Add in your User class:
public function getPublishedTypes()
{
return $this->getTypes()->filter(function(Type $type) {
return $type->getPublishedAt < date('Y-m-d');
}
This version is not that great, because it produces way more queries (bad with large data in your database).

Am I doing eager loading correctly? (Eloquent)

I have a method that needs to pull in information from three related models. I have a solution that works but I'm afraid that I'm still running into the N+1 query problem (also looking for solutions on how I can check if I'm eager loading correctly).
The three models are Challenge, Entrant, User.
Challenge Model contains:
/**
* Retrieves the Entrants object associated to the Challenge
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
Entrant Model contains:
/**
* Retrieves the Challenge object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
and User model contains:
/**
* Retrieves the Entrants object associated to the User
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
The method I am trying to use eager loading looks like this:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* #return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
I feel like I followed what the documentation recommended: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
but not to sure how to check to make sure I'm not making N+1 queries opposed to just 2. Any tips or suggestions to the code are welcome, along with methods to check that eager loading is working correctly.
Use Laravel Debugbar to check queries your Laravel application is creating for each request.
Your Eloquent query should generate just 3 raw SQL queries and you need to make sure this line doesn't generate N additional queries:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()
when you do ->with('entrants.user') it loads both the entrants and the user once you get to ->get(). When you do ->load('user') it runs another query to get the user. but you don't need to do this since you already pulled it when you ran ->with('entrants.user').
If you use ->loadMissing('user') instead of ->load('user') it should prevent the redundant call.
But, if you leverage Collection methods you can get away with just running the 1 query at the beginning where you declared $challenges:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
You don't need to use ->load('user') because $challenge->entrants is already populated with entrants and the related users. so you can just leverage the Collection method ->sortByDesc() to sort the list in php.
also, You don't need to run ->all() because that would convert it into an array of models (you can keep it as a collection of models and still foreach it).

PHP streamline add and remove array element

Is there a way in PHP or Laravel 5.2 to streamline the following:
iterate through array. In my example, a collection of industries
add an item. In my example In my example, 'app_id' and padded uuid
remove original item. In my case the original uuid element
Ideal Outcome:
Can I streamline function paddUuids($item) and public function getIndustries()
Context (why do I want to do this):
Using native uuid with CSS doesn't work as class and ID because the first character cannot be numeric so I need to add alpha prefix to uuid. Second, Angular 1.5 seems to struggles with Uuid (even as string) or Uuid with hyphens i.e. keep getting duplicate index error.
class SegmentsController extends Controller
{
use UuidTrait;
/**
* #var Industry
*/
protected $industry;
/**
* SegmentsController constructor.
*/
public function __construct(Industry $industry)
{
$this->industry = $industry;
}
/**
* Remove uuid hypens and add prefix to use with Angular and as CSS class
* #param $item
* #return mixed
*/
function paddUuids($item)
{
$item['app_id'] = $this->paddUuid($item['uuid']);
unset($item['uuid']);
return $item;
}
/**
* Return Iterate through industries
* #return array
*/
public function getIndustries()
{
return array_map (array($this, 'paddUuids'), $this->industry->get()->toArray() );
}
}

Symfony Entity - Order joined Entities in a ManyToMany join

So let's say we use a User and a Ticket class. They are normal entities, nothing fancy.
The User class contains this lines:
/**
* #ORM\ManyToMany(targetEntity="Ticket", mappedBy="collaborateurs")
**/
private $tickets;
The Ticket class contains this:
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="tickets")
* #ORM\JoinTable(name="users_to_tickets")
**/
private $collaborateurs;
To get all ticket's a user has I can just call the getTickets() function created by Symfony. As far as good. The Ticket class has a few additional fields like updated which is a DateTime field or status which is an integer. I would like to sort those tickets by status DESC and updated DESC
I know I could just make a function in the repository like findTicketsByUserOrderedByFooBar($user), but I'm wondering if there isn't a better way.
If you always want your tickets to be in that order you can set and orderBy on the association.
/**
* #ORM\ManyToMany(targetEntity="Ticket", mappedBy="collaborateurs")
* #ORM\OrderBy({"status" = "DESC", "updated" = "DESC"})
**/
private $tickets;
You can add an Helper method to your User entity and sort/filter DIRECTLY on the ArrayCollection with doctrine2 criteria. Something like this:
/**
* this return a ArrayCollection
*/
public function getTicketsByUserOrderedByFooBar()
{
$criteria = Criteria::create()
->orderBy(array('foo'=>'DESC','bar' =>'ASC'))
return $this->tickets->matching($criteria);
}
/**
* this return a ArrayCollection
*/
public function getTicketsByUserOrderedBytitle()
{
$criteria = Criteria::create()
->orderBy(array('title'=>'DESC'))
return $this->tickets->matching($criteria);
}
See also this
Hope this help.
Creating a function the way you suggested would be the suggested approach.

Categories