Doctrine Distinct Count [closed] - php

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
What is the best practice for retrieving the DISTINCT COUNT on an entity collection?
In this example entity (Customer), I have a oneToMany relationship with Orders.
I want to count how many sales & products the customer has ordered:
> select * from orders;
+----------+----------+----------+
| customer | sale_ref | prod_ref |
+----------+----------+----------+
| 1 | sale_1 | prod_1 |
| 1 | sale_1 | prod_2 |
| 1 | sale_2 | prod_1 |
| 1 | sale_3 | prod_3 |
+----------+----------+----------+
> select count(prod_ref) from order where customer = 1;
+-----------------+
| count(prod_ref) |
+-----------------+
| 4 |
+-----------------+
> select count(distinct(sale_ref)) from order where customer = 1;
+-----------------+
| count(prod_ref) |
+-----------------+
| 3 |
+-----------------+
Here is the code
use Doctrine\ORM\Mapping as ORM;
class Customer
{
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\OneToMany(targetEntity="Orders", mappedBy="customer", cascade={"persist", "remove"}, fetch="EXTRA_LAZY")
*/
protected $orders;
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getOrders(): \Doctrine\Common\Collections\Collection
{
return $this->orders;
}
/**
* #return int
*/
public function getOrdersProductCount(): int
{
return $this->orders->count();
}
}
class Orders
{
/**
* #var Customer $customer
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="orders")
*/
protected $customer;
/**
* Non-unique sales reference
* #var string $salesRef
* #ORM\Column(name="sales_ref", type="string")
*/
protected $salesRef;
/**
* Unique product reference
* #var string $productRef
* #ORM\Column(name="product_ref", type="string")
*/
protected $productRef;
/**
* #return Customer
*/
public function getCustomer(): Customer
{
return $this->customer;
}
/**
* #return string
*/
public function getProductRef(): string
{
return $this->productRef;
}
/**
* #return string
*/
public function getSalesRef(): string
{
return $this->salesRef;
}
}
Using the Customer->getOrdersProductCount() works perfectly fine for retrieving the product count and is said to be "good practice" as it doesn't hit the database with full loading of the collection:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/extra-lazy-associations.html
If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection Collection#count()
However, in this example, a Customer can have multiple products for a sale - where the salesRef is non-unique. What is the best method for retrieving a DISTINCT COUNT of the salesRef?
This could/should be handled in the entity repository class:
class OrdersRepository
{
public function getSalesCount($customer): int
{
return (int)$this->createQueryBuilder('o')
->select('COUNT(DISTINCT(o.salesRef))')
->where('o.customer = :customer')
->setParameter('customer', $customer)
->setMaxResults(1)
->getQuery()
->getSingleScalarResult();
}
public function getProductCount($customer): int
{
return (int)$this->createQueryBuilder('o')
->select('COUNT(o.productRef)')
->where('o.customer = :customer')
->setParameter('customer', $customer)
->setMaxResults(1)
->getQuery()
->getSingleScalarResult();
}
}
This works BUT I need to load the entityManager/CustomerRepository in order to access these methods - whereas at least I can retrieve the product count from within the entity....
How could I access the distinct sales count from within the Customer entity - if at all?
I have considered using the Collection#filter() method and/or looping through the Orders entity to create an array with the salesRef as the key and then using array_unique#count() but this doesn't seem "right" - I suspect I know the answer (use the entity repository) but I would prefer to be able to access the sales count from within the Customer entity - what is the best practice/method?

I think this should do it and would be a more portable way of doing it. I did not test it though.
$qb = $this->createQueryBuilder('o');
return (int)$qb
->select($qb->expr()->countDistinct('o.salesRef'))
->where('o.customer = :customer')
->setParameter('o.customer', $customer)
->setMaxResults(1)
->getQuery()
->getSingleScalarResult();
Reference is here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/query-builder.html#the-querybuilder
Hope this helps

Related

Laravel 6.x Advanced String search locally [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I have Laravel 6.x and need an advanced search for mariaDB. I would need a search that searches all columns if they contain a specific string.
Example:
+----+-------+--------------+
| id | name | lastname |
+----+-------+--------------+
| 1 | peter | peterjackson |
+----+-------+--------------+
| 2 | petery| hans |
+----+-------+--------------+
| 3 | hans | han |
+----+-------+--------------+
| 4 | petty | bun |
+----+-------+--------------+
Search querys:
peter: 1
peters: /
pet: 1,2,3
I tried already TNT-search but it only searches if the whole string is the same. So pet would only trigger at id=2.
Example for TNT-Search (Laravel Scout):
People::search("pet")->get()
*no records*
People::search("peter")->get()
record id 1 (id 2 not included)
Algolia search isn't an option because I can't outsource data into other data-centers.
This is untested and could certainly be improved, but this should do what you want:
Add the below methods to your model, or better yet your base model which is extended by all your other models:
/**
* An array containing the names of all of this model's columns
* #var []
*/
private $_columnNames = [];
/**
* Get an array of info for the columns of the given connection and table
* #return array
*/
public function columnInfo()
{
return DB::connection($this->connection)->select(DB::raw('SHOW FULL COLUMNS FROM '.$this->table.';'));
}
/**
* Get an array of all the column names for this db model
* #return array
*/
public function getColumnNames()
{
if (!$this->_columnNames) {
$this->_columnNames = Arr::pluck($this->columnInfo(), 'Field');
}
return $this->_columnNames;
}
/**
* Get all records where any table column is like the given value
* #param string $value
* #param array $selectColumns An array of columns to return
* #return \Illuminate\Database\Query\Builder
*/
public static function whereAnyColumnLike($value, $selectColumns)
{
$queryColumns = (new self)->getColumnNames();
$selectColumns = $selectColumns ?: $queryColumns;
$query = self::select($selectColumns);
foreach($queryColumns as $key => $column) {
$function = $key === 0 ? 'where' : 'orWhere';
$query->$function($column, 'LIKE', $value);
}
return $query;
}
Then you can call SomeModel::whereAnyColumnLike('%pet%')->get();

Symfony Doctrine : fetch a collection persisted as array

I have two entities Modules and Orders where One order have Many modules and I'm wondering how to fetch an array collection of modules persisted as follow:
Table: Orders
id | modules | user_id | ... | created_at |
----------------------------------------------------
1 | [2,6,5] | 12 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
As you can see my modules are persisted as array. So after that how can I make Doctrine (with Symfony) to get my modules
I think you need a ManyToOne relationShip ... as I know, we never store an array in database.
in your example order can have many modules and module can have just one order ...
in this case order called owning side and module called invers side ...
and module keep id of order ...
look at this example
Table: Orders
id | user_id | ... | created_at |
----------------------------------------------------
1 | 12 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
Table: Modules
id | order_id | ... | created_at |
----------------------------------------------------
1 | 1 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
2 | 1 | ... | 2018-07-29 00:00:00 |
----------------------------------------------------
you must write your code like this...
Order Class
class Order implements OrderInterface
{
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="Module", mappedBy="order", cascade={"persist"})
*/
protected $modules;
/**
* Don't forget initial your collection property
* Order constructor.
*/
public function __construct()
{
$this->modules = new ArrayCollection();
}
/**
* #return Collection
*/
public function getModules(): Collection
{
return $this->modules;
}
/**
* #param ModuleInterface $module
*/
public function addModule(ModuleInterface $module): void
{
if ($this->getModules()->contains($module)) {
return;
} else {
$this->getModules()->add($module);
$module->setOrder($this);
}
}
/**
* #param ModuleInterface $module
*/
public function removeModule(ModuleInterface $module): void
{
if (!$this->getModules()->contains($module)) {
return;
} else {
$this->getModules()->removeElement($module);
$module->removeOrder($this);
}
}
}
Module Class
class Module implements ModuleInterface
{
/**
* #var OrderInterface
*
* #ORM\OneToMany(targetEntity="Order", mappedBy="modules", cascade={"persist"})
*/
protected $order;
/**
* #param OrderInterface $order
*/
public function setOrder(OrderInterface $order)
{
$this->order = order;
}
public function getOrder(): OrderInterface
{
return $this->order;
}
}
when you persist order object by doctrine... doctrine handle this and create items

symfony 3 doctrine one to one uni-direction returning unwanted fields when querying

I have got two classes which are being associated using one to one uni-direction
{
id: 1,
name: "onetooneuniparent name",
onetooneunichild: {
id: 1,
name: "onetooneunichild name",
__initializer__: null,
__cloner__: null,
__isInitialized__: true
}
}
the above is the result when I do query like following
http://localhost:8000/onetooneRead?id=1
I want to know where and why the following come from
__initializer__: null,
__cloner__: null,
__isInitialized__: true
my expected result is just this
{
id: 1,
name: "onetooneuniparent name",
onetooneunichild: {
id: 1,
name: "onetooneunichild name"
}
}
OnetoOneUniParent.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="onetooneuniparent")
*/
class OnetoOneUniParent{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",name="name")
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="OnetoOneUniChild",cascade={"persist"})
* #ORM\JoinColumn(name="child_id", referencedColumnName="id")
*/
private $onetooneunichild;
<.... getter and setter here ...>
}
OnetoOneUniChild.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="onetooneunichild")
*/
class OnetoOneUniChild{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",name="name")
*/
private $name;
<.... getter and setter here ...>
This is the method in controller
/**
* #Route("/onetooneRead")
* #Method("GET")
*/
public function onetooneReadAction(Request $request){
$logger = $this->get('logger');
$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
$logger->info('onetoone Read');
$id = $request->query->get("id");
$em = $this->getDoctrine()->getManager();
$onetooneuniparent = $em->getRepository('AppBundle:OnetoOneUniParent')->find($id);
$onetooneuniparentJson = $serializer->serialize($onetooneuniparent, 'json');
$response = new JsonResponse();
$response->setContent($onetooneuniparentJson);
return $response;
}
This is what is inside in MySQL
mysql> select * from onetooneuniparent;
+----+----------+------------------------+
| id | child_id | name |
+----+----------+------------------------+
| 1 | 1 | onetooneuniparent name |
| 2 | 2 | onetooneuniparent name |
| 3 | 3 | onetooneuniparent name |
+----+----------+------------------------+
3 rows in set (0.00 sec)
mysql> select * from onetooneunichild;
+----+-----------------------+
| id | name |
+----+-----------------------+
| 1 | onetooneunichild name |
| 2 | onetooneunichild name |
| 3 | onetooneunichild name |
+----+-----------------------+
3 rows in set (0.00 sec)
Those functions are part of the Doctrine proxy coding, since you are using Lazy Loading Doctrine needs to keep track of the child entity if it needs to be loaded or not. Part of that keeping track is these functions (I believe it is in this portion of Doctrine)
There may be a way around this which would be to avoid using lazy loading. To do that you can utilize EAGER loading if you always want the child to load with the parent. Alternatively if you only want to use EAGER for this one query and not every time you would have to switch to DQL as documented here or you could use the JOIN comma (second example down) here

Query multiple, nested Relationships in Laravel/Eloquent

I'm trying to build a simple news feed for posts in Laravel with Eloquent.
I basically want to retrieve all Posts...
where I am author
where people I follow are author (followable)
where people I follow have commented on
where people with same field_id are author
where poeple with same school_id are author
in one query.
As I never worked intensivly with joined/combined SQL queries, any help on this is greatly appreciated!
My Tables
users table
+----+
| id |
+----+
posts table
+----+-----------+-------------+
| id | author_id | author_type |
|----|-----------|-------------|
| | users.id | 'App\User' |
+----+-----------+-------------+
comments table
+----+----------------+------------------+-----------+-------------+
| id | commentable_id | commentable_type | author_id | author_type |
|----|----------------|------------------|-----------|-------------|
| | posts.id | 'App\Post' | users.id | 'App\User' |
+----+----------------+------------------+-----------+-------------+
schoolables table
+---------+-----------+----------+
| user_id | school_id | field_id |
+---------+-----------+----------+
followables table
+-------------+---------------+---------------+-----------------+
| follower_id | follower_type | followable_id | followable_type |
|-------------|---------------|---------------|-----------------|
| users.id | 'App\User' | users.id | 'App\User' |
+-------------+---------------+---------------+-----------------+
My Models
class Post extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function author()
{
return $this->morphTo();
}
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Comment extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function author()
{
return $this->morphTo();
}
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function commentable()
{
return $this->morphTo();
}
}
class User extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function posts()
{
return $this->morphMany(Post::class, 'author')->orderBy('created_at', 'desc');
}
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function comments()
{
return $this->morphMany(Comment::class, 'author')->orderBy('created_at', 'desc');
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function schoolables()
{
return $this->hasMany(Schoolable::class);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function following()
{
return $this->morphMany(Followable::class, 'follower');
}
}
You can try to use left joins for this query, but it would become a complex thing, because you have to make all the joins with leftJoins and then a nested orWhere clause on all the
$posts = Post::leftJoin('..', '..', '=', '..')
->where(function($query){
$query->where('author_id', Auth::user()->id); // being the author
})->orWhere(function($query){
// second clause...
})->orWhere(function($query){
// third clause...
.....
})->get();
I don't think this will be manageable, so I would advice using UNIONS, http://laravel.com/docs/5.1/queries#unions
So it would be something like..
$written = Auth::user()->posts();
$following = Auth::user()->following()->posts();
After getting the different queries, without getting the results, you can unite them..
$posts = $written->union($following)->get();
Hopefully this will direct you in the right direction
Okay, so you want to retrieve all posts where any of the 5 conditions above apply. The trick to writing such queries is to break them up into smaller, more manageable pieces.
$query = Post::query();
So let's say you are $me.
The ids of users you are following can be obtained with
$followingUserIds = $me
->following()
->where('followable_type', User::class)
->lists('followable_id');
The ids of users in the same fields as you can be obtained with
$myFieldIds = $me->schoolables()->lists('field_id');
$sharedFieldUserIds = Schoolable::whereIn('field_id', $myFieldIds)->lists('user_id');
Similarly, users in the same school as you can be obtained with
$mySchoolIds = $me->schoolables()->lists('school_id');
$sharedSchoolUserIds = Schoolable::whereIn('school_id', $mySchoolIds)->lists('user_id');
Let's define each of those conditions:
Where I am author
$query->where(function($inner) use ($me) {
$inner->where('posts.author_type', User::class);
$inner->where('posts.author_id', $me->id);
});
Where people I follow are author (followable)
$query->orWhere(function($inner) use ($followingUserIds) {
$inner->where('posts.author_type', User::class);
$inner->whereIn('posts.author_id', $followingUserIds);
});
where people I follow have commented on
This one is actually slightly tricky: we need to use the ->whereHas construct, which finds posts with at least 1 comment matching the subquery.
$query->orWhereHas('comments', function($subquery) use ($followingUserIds) {
$subquery->where('comments.author_type', User::class);
$subquery->whereIn('comments.author_id', $followingUserIds);
});
The remaining two are simple.
where people with same field_id are author
$query->orWhere(function($inner) use ($sharedFieldUserIds) {
$inner->where('posts.author_type', User::class);
$inner->whereIn('posts.author_id', $sharedFieldUserIds);
});
and you can see where this is going
where poeple with same school_id are author
$query->orWhere(function($inner) use ($sharedSchoolUserIds) {
$inner->where('posts.author_type', User::class);
$inner->whereIn('posts.author_id', $sharedSchoolUserIds);
});
To get the matching posts, you just need to do
$posts = $query->get();
While constructing the query from scratch works in this particular case, it will create a fairly brittle structure if your requirements ever change. For added flexibility, you probably want to build query scopes on the Post and Comments models for each of those components. That will mean that you only need to figure out one time what it means for a post to be authored by someone a user follows, then you could simply do Post::authoredBySomeoneFollowedByUser($me) to get the collection of posts authored by someone you follow.

How can I add field on the fly in symfony2 entity

I would like to know if exist a way to add fields on the fly to any entity on Symfony2. I'm searching on the big internet and I didn't find anything. When I said "a way", I mean if exist a Doctrine Extension with that behavior, a bundle that implement it, design pattern, etc.
My idea is something similar to Translatable behavior of Doctrine Extensions. Supouse I have a Address entity, so I would like to add some attributes on the fly like street, number, intersections, and others but at the begining I didn't know what fields could exist.
I'm thinking something as 2 entities: Address and AddressFieldValues. Address will have specifics attributes like id, foreing keys of relationships with others classess and will be used to inject the dynamic attributes (a collections of field-values). AddressFieldValue will have the reals fields-values of Address, with the following attributes: id, address_id, field_name, field_value.
So, entity Address could be like this:
/**
* Address
*
* #ORM\Entity(repositoryClass="AddressRepository")
* #ORM\Table(name="address")
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(
* targetEntity="AddressFieldValues",
* mappedBy="object",
* cascade={"persist", "remove"}
* )
*/
private $field_value;
public function __construct()
{
$this->field_value = new ArrayCollection();
}
public function getFieldValue()
{
return $this->field_value;
}
public function addFieldValue(AddressFieldValues $fv)
{
if (!$this->field_value->contains($fv)) {
$this->field_value[] = $fv;
$fv->setObject($this);
}
}
public function getId()
{
return $this->id;
}
}
and AddressFieldValues entity could be like this:
/**
* #ORM\Entity
* #ORM\Table(name="address_field_values",
* uniqueConstraints={#ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "object_id", "field"
* })}
* )
*/
class AddressFieldValues
{
/**
* #var integer $id
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string $field
*
* #ORM\Column(type="string", length=32)
*/
protected $field;
/**
* #ORM\ManyToOne(targetEntity="Address", inversedBy="field_value")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;
/**
* #var string $content
*
* #ORM\Column(type="text", nullable=true)
*/
protected $content;
/**
* Convenient constructor
*
* #param string $field
* #param string $value
*/
public function __construct($field, $value)
{
$this->setField($field);
$this->setContent($value);
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set field
*
* #param string $field
*/
public function setField($field)
{
$this->field = $field;
return $this;
}
/**
* Get field
*
* #return string $field
*/
public function getField()
{
return $this->field;
}
/**
* Set object related
*
* #param string $object
*/
public function setObject($object)
{
$this->object = $object;
return $this;
}
/**
* Get related object
*
* #return object $object
*/
public function getObject()
{
return $this->object;
}
/**
* Set content
*
* #param string $content
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string $content
*/
public function getContent()
{
return $this->content;
}
}
So, if I have the following values on table: address_field_values
id | object | field | content
1 | 1 | street | 1st Ave
2 | 1 | number | 12345
3 | 1 | intersections | 2sd Ave and 4th Ave
4 | 2 | street | 1st Ave
5 | 2 | number | 12347
6 | 2 | intersections | 2sd Ave and 4th Ave
7 | 3 | street | 1st Ave
8 | 3 | number | 12349
9 | 3 | intersections | 2sd Ave and 4th Ave
For now address table only have the following values:
| id |
| 1 |
| 2 |
| 3 |
I could like to inject those fields-values to a Address object on the fly, to do something like this:
// if I need get de Address with id = 2
$addressRepository = $em->getRepository('Address');
$address = $addressRepository->find(2);
sprintf('The address is: "%s", #"%s" between "%s".', $address->getStreet(), $address->getNumber(), $address->getIntersections());
// then it should show: The address is 1st Ave, #12347 between 2sd Ave and 4th Ave.
//
// or if I need add a new Address, do something like this:
$address = new Address();
$address->setStreet('1st Ave');
$address->setNumber('12351');
$address->setIntersections('2sd Ave and 4th Ave');
$em->persist($address);
$em->flush();
then it save the address and address_field_values, and the tables have the following values:
// address
| id |
| 1 |
| 2 |
| 3 |
| 4 |
// address_field_values
id | object | field | content
1 | 1 | street | 1st Ave
2 | 1 | number | 12345
3 | 1 | intersections | 2sd Ave and 4th Ave
4 | 2 | street | 1st Ave
5 | 2 | number | 12347
6 | 2 | intersections | 2sd Ave and 4th Ave
7 | 3 | street | 1st Ave
8 | 3 | number | 12349
9 | 3 | intersections | 2sd Ave and 4th Ave
10 | 4 | street | 1st Ave
11 | 4 | number | 12351
12 | 4 | intersections | 2sd Ave and 4th Ave
So, any ideas how can I do that?
Remember, I have as requirement in my bussiness logic that I didn't know what fields could have a Address at beginig so I need to inject the fields on the fly. I use Address as example but this behavior can be used for any entity.
Thanks in advance
I think that your request is similar to a collection in a form (Doctrine2 documentation).
In the documentation, a collection of Tags entities with name property) is linked to a Task entity. In your case, the entity AddressFieldValue will have the field and content properties and the collection of AddressFieldValue entities will be added to Address entity.
So, by using this documentation and replacing Task by Address and Tag by AddressFieldValue it should works.

Categories