How to combine whereHas() in Eloquent with optional Route parameters in Laravel 9? - laravel-9

What's the best way to structure route and controller in Laravel 9 for below scenario of books as part of a library filterable by type, genre, author and book UUID?
Example paths:
https://example.com/library/books/science-fiction/frank-herbert/dune-f2b69b83-8ae7-4094-9a36-b0c079764d0c
https://example.com/library/magazines/interior/dwell/edition-nov22-56d67e3c-4835-4e7b-b05a-746ebf3e0c31
Routes
Show all books from library:
Route::get('/library/', [BookController::class, 'bookByLibrary']);
Show all books from library of type:
Route::get('/library/{type}/', [BookController::class, 'booksByType']);
Show all books from library of type and genre:
Route::get('/library/{type}/{genre}/', [BookController::class, 'booksByGenre']);
Show all books from library of type, genre and author:
Route::get('/library/{type}/{genre}/{author}/', [BookController::class, 'booksbyAuthor']);
Show all detail of one specific book:
Route::get('/library/{type}/{genre}/{author}/{booktitle}/{uuid}', [BookController::class, 'booksByUuid']);
Using UUID allows to select the book avoiding having to use library, type, genre, author and booktitle to filter.
I do understand the route can be written with optional parameters as follows:
Route::get('/library/{type?}/{genre?}/{author?}/{booktitle?}/{uuid?}',
['BookController::class, 'getAllBooks']);
But how to structure the query in controller?
How to structure this query knowing that each parameters can be null? My attempt:
public function getAllBooks(Request $request, $type=null, $genre=null, $author=null, $booktitle=null, $uuid=null)
{
Books::query()
->when($request->type, fn() => $query->where('type', $type))
->when($request->genre, fn() => $query->where('genre', $genre))
->when($request->author, fn() => $query->where('author', $author))
->when($request->uuid, fn() => $query->where('uuid', $uuid))
->get();
});
Does this use case lend itself installing an existing package to filter instead of building this from scratch, like Spatie's laravel-query-builder?

Related

KeystoneJS and naming?

I'm new to Keystone JS and NodeJS.
This is the Part I totally do not understand;
Example 'Post' as defined as 'Post', but there are no 'posts', but when I call/ search for Post, in example (and my practices), it was 'posts'.
Exp:
keystone.set('nav', {
posts: ['posts', 'post-categories'],
enquiries: 'enquiries',
users: 'users',
});
Similar 'PostCategory' => 'post-categories', 'Enquiry'=>'enquiries' etc.
But when I making new Routes=>View for my custom post type, I must use:
locals.data = {
food: []
};
At this, its 'food' not 'foods'.
Keystone automatically uses the plural form of your model names in the Admin Panel, instead of its singular name. It's still referred to by its singular name (Food, PostCategory, Enquiry, etc.) throughout your code, but the admin panel uses the plural forms if referring to multiple documents of a model.
When working with local, you can name the properties of that object anything you want. Doesn't have to be locals.data.food; it can be whatever you want.
Also, the plural form of food is food. So nothing will change when using the plural form of a Food model in your Admin Panel.

Create a ActiveDataProvider based on ActiveRecord Relation in Yii2

I have a many-to-many relation setup using a junction table in MySQL. Table Article is related to Activity via table Article_Activity.
In model Article I have a relation setup like this
public function getActivities()
{
return $this->hasMany(Activity::className(), ['id' => 'activity_id'])
->viaTable('article_activity', ['article_id' => 'id']);
}
When rendering a view for one Article I would like to display a GridView of all Activities related to that Article.
The way most people seem to this is to create a ActiveDataProvider and insert a query into it that fetches related data but that feel a bit redundant since I have the relation setup in the model and there should be a way to get a dataprovider from that.
My question is: Is there a way to get a yii\data\ActiveDataProvider or yii\db\Query based on a instantiated models relation that can be used to display all related records in a GridView?
You can actually call it like this:
$dataProvider = new ActiveDataProvider([
'query' => $article->getActivities(),
]);
If you call the get method directly you get a yii\db\ActiveQueryInterface which is what you need to provide as query to the ActiveDataProvider.
When you call the activities attribute like $article->activities the ActiveQueryInterface is executed and you get the records from the query results.
Based on you Article model you have the activities relation that get multiple Activity related to you Article
a normal dataProvider like
$dataProvider = new ActiveDataProvider([
'query' => Article::find()->joinWith(['activities'])->
where(['your_column'=> $your_value]),
]);
return activeRecord whit also the related activities
you can refer to the related models inside the dataProvider
$models = $dataProvider->models; // this is a collection of model related to the dataProvider query
// so the firts model is $model=models[0]
then for each of this you can obtain acyivities
$activities = $model->activities;
or value
$my_activity_field = $model->activities->my_activity_field

NHibernate QueryOver projection on many-to-one

I am trying to get a QueryOver working using a Projection on a many-to-one.
The class "Post" has a property many-to-one "Creator".
Using
session.QueryOver(Of Post).
Select(Projections.
Property(of Post)(Function(x) x.Creator).
WithAlias(Function() postAlias.Creator)).
TransformUsing(Transformers.AliasToBean(Of Post)()).
List()
works BUT each creator is retrieved by a single query rather than using a join like it is done when not using a select/projection. So if there are 5 posts with 5 different creators, 6 queries will be run 1 for the list of posts and 5 for the creators.
I tried to get it working using a JoinAlias but nothing really did the job.
I already searched for a solution, but all solutions I found did use the Linq-Provider which does not really fit since the actual "field list" is passed via a parameter.
Does anyone know if there is a solution to this other than the linq provider?
There is a solution, we can use projections for many-to-one and then custom result transformer.
DISCLAIMER: I can read VB syntax but do not have enough courage to write... I expect that you can read C# and convert it into VB....
So we can have projection like this:
// aliases
Post root = null;
Creator creator = null;
// projection list
var columns = Projections.ProjectionList();
// root properties
columns.Add(Projections.Property(() => root.ID).As("ID"));
columns.Add(Projections.Property(() => root.Text).As("Text"));
// reference properties
columns.Add(Projections.Property(() => creator.ID).As("Creator.ID"));
columns.Add(Projections.Property(() => creator.FirstName).As("Creator.FirstName"));
// so our projections now do have proper ALIAS
// alias which is related to domain model
// (because "Creator.FirstName" will be use in reflection)
var query = session.QueryOver<Post>(() => root)
.JoinAlias(() => root.Creator, () => creator)
.Select(columns)
Now we would need smart Transformer, our own custome one (plugability is power of NHibernate). Here you can find one:
public class DeepTransformer
And we can continue like this
var list = query
.TransformUsing(new DeepTransformer<Post>())
.List<Post>()
Check also this:
Fluent NHibernate - ProjectionList - ICriteria is returning null values
NHibernate AliasToBean transformer associations

How to convert Doctrine2 entity graph to array

I have the following entities
Product Entity
Category Entity
Tag Entity
Brand Entity
What I am trying to do is when I get the Product by id, I want to convert it to array including all associated entities including their associated entries.
The result array needs to be serializable.
Use doctrine query builder and the HYDRATE_ARRAY hydration mode?
Edit: Sorry for not including examples, I was on my mobile at the time. Check out the blog post I wrote on some good practices with doctrine that semi-relates to this.
For a code example the repository method I would write to cover this would be as follows (I would avoid using abbreviations like p c etc. as it makes your code a lot less readable (at least while you are getting started with doctrine...)
<?php
namespace Vendor\Prefix\Repository;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function find($id)
{
// $em is the entity manager
$qb = $em->createQueryBuilder();
$qb
->select('product', 'category', 'tag', 'brand', 'category_child')
// Or SomeBundle:Product if you're on symfony
->from('Vendor\Prefix\Entity\Product', 'product')
// You need to explicitly fetch join all your
// associations...and select them
->leftJoin('product.Brand', 'brand')
->leftJoin('product.Tags', 'tag')
->leftJoin('product.Categories', 'category')
->leftJoin('category.Children', 'category_child')
// Use prepared statments...its a good habit
->where($qb->expr()->eq('product.ID', ':id'))
->setParameter('id', $id)
;
$query = $qb->getQuery();
// Potential Hydration Modes
// --------------------------------
// Doctrine\ORM\Query::HYDRATE_OBJECT
// Doctrine\ORM\Query::HYDRATE_ARRAY
// Doctrine\ORM\Query::HYDRATE_SCALAR
// Doctrine\ORM\Query::HYDRATE_SINGLE_SCALAR
// Doctrine\ORM\Query::HYDRATE_SIMPLEOBJECT
// Hydrate the result as an array to get the requested format
// When you use array hyrdation doctrine does it according
// to your entity graph
return $query->getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);
}
}
I think you need to use JOIN in DQL syntax:
$results = $entityManager->executeQuery('SELECT p FROM Product p JOIN p.Brand b JOIN p.Tags t JOIN p.Categories c')->getArrayResult();
var_dump($results);
Also, I think you should avoid class members starting with an uppercase letter. It could introduce a bit of confusion with the entity name.

How do I express this LINQ query using the NHibernate ICriteria API?

My current project is using NHibernate 3.0b1 and the NHibernate.Linq.Query<T>() API. I'm pretty fluent in LINQ, but I have absolutely no experience with HQL or the ICriteria API. One of my queries isn't supported by the IQueryable API, so I presume I need to use one of the previous APIs -- but I have no idea where to start.
I've tried searching the web for a good "getting started" guide to ICriteria, but the only examples I've found are either far too simplistic to apply here or far too advanced for me to understand. If anyone has some good learning materials to pass along, it would be greatly appreciated.
In any case, the object model I'm querying against looks like this (greatly simplified, non-relevant properties omitted):
class Ticket {
IEnumerable<TicketAction> Actions { get; set; }
}
abstract class TicketAction {
Person TakenBy { get; set; }
DateTime Timestamp { get; set; }
}
class CreateAction : TicketAction {}
class Person {
string Name { get; set; }
}
A Ticket has a collection of TicketAction describing its history. TicketAction subtypes include CreateAction, ReassignAction, CloseAction, etc. All tickets have a CreateAction added to this collection when created.
This LINQ query is searching for tickets created by someone with the given name.
var createdByName = "john".ToUpper();
var tickets = _session.Query<Ticket>()
.Where(t => t.Actions
.OfType<CreateAction>()
.Any(a => a.TakenBy.Name.ToUpper().Contains(createdByName));
The OfType<T>() method causes a NotSupportedException to be thrown. Can I do this using ICriteria instead?
try something like this. It's uncompiled, but it should work as long as IEnumerable<TicketAction> Actions and Person TakenBy is never null. If you set it to an empty list in the ticket constructor, that will solve a problem with nulls.
If you add a reference to the Ticket object in the TicketAction, you could do something like this:
ICriteria criteria = _session.CreateCriteria(typeof(CreateAction))
.Add(Expression.Eq("TakenBy.Name", createdByName));
var actions = criteria.List<CreateAction>();
var results = from a in criteria.List<>()
select a.Ticket;
In my experience, nhibernate has trouble with criteria when it comes to lists when the list is on the object side - such as is your case. When it is a list of values on the input side, you can use Expression.Eq. I've always had to find ways around this limitation through linq, where I get an initial result set filtered down as best as I can, then filter again with linq to get what I need.
OfType is supported. I'm not sure ToUpper is though, but as SQL ignores case it does not matter (as long as you are not also running the query in memory...). Here is a working unit test from the nHibernate.LINQ project:
var animals = (from animal in session.Linq<Animal>()
where animal.Children.OfType<Mammal>().Any(m => m.Pregnant)
select animal).ToArray();
Assert.AreEqual("789", animals.Single().SerialNumber);
Perhaps your query should look more like the following:
var animals = (from ticket in session.Linq<Ticket>()
where ticket.Actions.OfType<CreateAction>().Any(m => m.TakenBy.Name.Contains("john"))
select ticket).ToArray();