On Symfony2 I have an entity called Product which is managed by Doctrine2 and being related with a single category and location.
Class Product {
protected $id;
protected $title;
// n additional fields
protected $productCategory;
protected $productDepot;
}
What I need to achieve is, select category and location COUNT's in a single query.
So I'm executing the following COUNT select with some groupings on the following DQL.
$this->em->getRepository('MyCoreBundle:Product')
->createQueryBuilder('P')
->addSelect('IDENTITY(P.productCategory) AS category_id')
->addSelect('IDENTITY(P.productDepot) AS location_id')
->addSelect("COUNT(P.id) AS tag_count")
->addGroupBy('P.productCategory')
->addGroupBy('P.productDepot')
->getQuery()
->getScalarResult();
However when it is executed, the whole result is as follows:
$result = [
0 => [
P_id => ...,
P_title => ...,
P_stock => ....,
/*
* Many many additional fields of the entity
*/
category_id => 15,
location_id => 40,
tag_count => 20
],
...
];
However obviously I just need category_id, location_id and tag_count fields. Since the table is a huge one (like everyone has one, nowadays), I'm trying to minimize footprint of the query as much as possible.
What I tried so far is adding a partial field of id (as follows) however it didn't make sense.
$this->em->getRepository('MyCoreBundle:Product')
->createQueryBuilder('P')
->addSelect('partial P.{id}')
->addSelect('IDENTITY(P.productCategory) AS category_id')
->addSelect('IDENTITY(P.productDepot) AS location_id')
->addSelect("COUNT(P.id) AS tag_count")
->addGroupBy('P.productCategory')
->addGroupBy('P.productDepot')
->getQuery()
->getScalarResult();
Am I trying to achieve something impossible, or do you have any idea to get just the aggregation fields (or with just an additonal ID field) of an an entity?
Related
I created a form like that
$builder->add('employees', EntityType::class, [
'class' => ActivityEmployee::class,
'choice_label' => function (ActivityEmployee $employee) {
return sprintf('%s %s', $employee->getEmployee()->getName(), $employee->getEmployee()->getLastName());
},
'multiple' => true,
])
As a result it presents already existing data fine. It shows me all employees with relation to edited activity.
However as choices there should be all employess to choose (employee entity) and as selected data only employess in activityEmployee relation like right now.
I tried to add a query_builder option to provide lists of all employess, but I can only use EntityRepository which means ActivityEmployeesRepository not EmployeesRepository per se.
A can't figure out how to implement it. Basically such relation can be done by CollectionType of custom activityEmployeeType but I'd like to use multi-select for selecting employees.
I can use another approach to not mapping my employees field to entity like that
$currentEmployees = [];
foreach ($activity->getEmployees() as $activityEmployee) {
$currentEmployees[] = $activityEmployee->getEmployee();
}
$builder->add('employees', EntityType::class, [
'class' => Employee::class,
'choice_label' => function (Employee $employee) {
return sprintf('%s %s', $employee->getName(), $employee->getLastName());
},
'mapped' => false,
'multiple' => true,
'data' => $currentEmployees,
]);
It works fine, but I need to deal with updating relation by myself. Which is ok, however I wonder how to achieve such thing in first approach.
Implementation details matter. As far as I can understand you have the following entities:
Activity (entity)
- employees (OneToMany -> ActivityEmployee)
ActivityEmployee (entity)
- activity (ManyToOne -> Activity)
- employee (ManyToOne -> Employee)
Employee (entity)
- activities (OneToMany -> ActivityEmployee) - this one might be missing, actually.
Now you apparently don't hide any implementation details. Meaning, your Activity::getEmployees() returns []ActivityEmployee.
I would have done it like this:
class Activity {
/** #ORM\OneToMany(targetEntity=ActivityEmployee::class) */
private $activityEmployees;
/** #return Employee[] */
public function getEmployees() :Collection {
return $this->activityEmployees->map(function(ActivityEmployee $ae) {
return $ae->getEmployee();
});
}
public function addEmployee(Employee $employee) {
// check, if the employee is already registered, add only then!
if(!$this->getEmployees()->contains($employee)) {
$this->activityEmployees->add(new ActivityEmployee($this, $employee));
}
}
public function removeEmployee(Employee $employee) {
foreach($this->activityEmployees as $activityEmployee) {
if($activityEmployee->getEmployee() === $employee) {
$this->activityEmployees->removeElement($activityEmployee);
}
}
}
}
This way, you hide away how Activity handles the employees and to the outside world (and specifically the PropertyAccessor, that the form component uses) it appears as if Activity has a property employees which are actually Employee[].
If you implement it like this, your first form should actually just work (obviously exchanging ActivityEmployee for Employee) - under the assumption that I didn't make some major mistake. Of course I would also add methods like getActivityEmployees when I would actually specificially need the relation objects.
This whole thing certainly is less beautiful if your many-to-many can contain duplicates.
IF your ActivityEmployee actually has NO other properties besides activity and employee, you could obviously replace the whole thing with a #ORM\ManyToMany and just work with Employee[] instead of the ActivityEmployee[]. However, I assume you have some additional columns like created or something.
I'm sorry for the title but I don't know how to define it good. I'd like my app to sort data (in Doctrine) depending on the sort option which user select it in form dropdown list.
The data I mentioned above are stored inside my Doctrine Entity which I called it Flashcards and the Flashcards Entity contains properties which them must be sorted by option that user select in dropdown. Flashcards Entity looks like the following (I gave only a few properties for simplicity):
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Words", inversedBy="flashcards")
* #ORM\JoinColumn(nullable=false)
*/
private $words;
/**
* #ORM\Column(type="datetimetz")
*/
private $creation_date;
Now the controller's code for form is usual, like the Symfony Doc say:
// code for form inside FlashcardController
$flashcard = new Flashcards();
$form = $this->createForm(FlashcardType::class, $flashcard);
And inside FlashcardController render() method I call createView() method on $form object.
The form code is placed inside FlashcardType class and contains code for mentioned above dropdown list and its options. It looks like this (for simplicity I gave only the methods):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sortBy', ChoiceType::class, [
'label' => 'Sort by',
'choices' => [
'Date increase' => 1,
'Date decrease' => 2,
'Word alphabetically' => 3,
'Word not alphabetically' => 4
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Flashcards::class
]);
}
So as you see the dropdown options include sorting by: date increase, date decrease, word alphabetically, word not alphabetically. User choose one of them in view. So manipulating data by user does NOT change value of this data represented by properties. Changing sort option in dropdown should change Doctrine query to sort by user-changed value. I have no idea how to achieve this in Symfony. Could you give me plese some tips how to make it?
Thank you in advance for answers!
If I understood correctly you have a few options:
First: wire javascript / jquery to select input and submit the form when user changes order(it should reload the form applying the filter, you can set any data user used in the 'previous' page using RequestStack to get the form fields from request / query depending on the method)
Second: get all the nodes you display record and try to manipulate the order based on the select value every time user changes the value
Tell me if this is what you meant else correct me and I'll try to provide the answer
I'm having a huge problem with ORM QueryBuilder. What I need to do is:
I need to fetch order with count of its products and plenty of associated entities (associated with order), but I assume they're not relevant here. I also need to order result by that count.
Could anyone give me an example of how this can be achieved? I would like to avoid "inline" DQLs if possible.
You can get data via Doctrine Query Builder.
You are supposed to left join products from Order and then group by order id. You can have COUNT(product.id) in your select statement and use the alias in order by clause to make your orders sorted. Below is a small code snippet from Repository.
/**
* #return \Doctrine\ORM\Query
*/
public function getHotelAndRoomType()
{
$qb = $this->createQueryBuilder('order')
->select('partial order.{id, orderId} as order, count(product.id) as total_products_in_order')
->leftJoin('AppBundle:Product', 'product', 'WITH', 'product.order = order.id')
->groupBy('order.id')
->orderBy('total_products_in_order', 'DESC')
;
return $qb->getQuery()->execute();
}
Note : Code not tested.
I'm trying to find the right way to handel the next tabel setup.
Products is my main table and contains an id.
UsersCarts has an field product_id and with belongsTo it also loads the products.
So far no problem. I also have an table ProductsNettoDeals where I store possible netto prices. This table has it's own id an an product_id for the hasOne relation when loading products in the productcontroller.
When I load the UsersChart it loads it belongsTo products, but I also want the association with the table ProductsNettoDeals, but this table is connected with products through the id (products.id = products_netto_deals.product_id). I cant find the right syntax or association for the querybuilder. What I have so far:
// src/Model/Table/UsersCartsTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\Validation\Validator;
class UsersCartsTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
$this->belongsTo('Products');
$this->belongsTo('ProductsNettoDeals');
}
public function validationDefault(Validator $validator)
{
return $validator
->notEmpty('user_id', 'A user id is required')
->notEmpty('product_id', 'A product id is required');
}
public function findCart(Query $query, array $options)
{
$query
->where([
'user_id' => $options['user_id'],
//'active' => '1'
])
->contain(['Products','ProductsNettoDeals'])
//->select(['product_id','created','products.name','products.price','products.delivery_time']);
;
return $query;
}
}
In this setup I always have the problem that the matching of the id's are wrong. With the products it's good. The users_carts.product_id links with the products.id, but the products_netto_deals.product_id should link with product.id and not with the users_carts.id. I tryed foreign key and bind, but both are an part of an solution.
Beside this problem, when connecting the association ProductsNettoDeals I get an error when activating the commented active => '1'. This field indicates if an record is active and should be loaded. The same field exist in the products_netto_deals tabel to indicate if an netto price is active.
Error: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'active' in where clause is ambiguous
Can anyone point me in the right direction for the correct way of the association and the error warning. Thanks in forward.
CakePHP uses joins for fetching associations, in order to speed ud the information retrieval process. This means that when having column names repeated across different table, you will need to prefix the column names in the query clause"
Instead of:
$query->where(['active' => true]);
Do:
$query->where(['UsersCarts.active' => true])
I have a simple question. I saw a Symfony2-Tutorial where the blogger used everywhere simple entities without relations and was also using doctrine native-query to do joins and query the different entities.
What is better, to use doctrine native query or build every time relationships ?
Greetings Michael
I think that it depends on how the resultset will be handle. For example, suppose the following relationship:
a company has a lot of employees (1:N)
Just figure out the impact if this company has more than 10K employees when it does a select query in every request loading all employees data to be showed. In this scenario could be a good practice to create a lazy association in models:
<?php
/**
* #Entity
*/
class Company
{
/**
* #OneToMany(targetEntity="Employee", fetch="EXTRA_LAZY")
*/
public $employees;
}
At this case, doctrine only will triggered the required data from database, because it knows that all the data will be accessed gradually as you request it. You can read more about lazy associations in doctrine's docs.
How about the native queries?
Native queries can map arbitrary SQL code to objects, such as highly vendor-optimized SQL or stored-procedures. Fast scalar results and less ram usage. Take note that a complex model relationship could be too heavy for server to be manipulated. For example, look this structure based on Class Table Inheritance:
There is a super class called Product and there are more than 200 different sub-products that extends from Product. Each sub-product is storaged in its respective table.
<?php
abstract class Product
protected $name;
Some sub-products as example:
<?php
class Candy extends Product
/** specific property for this product */
private $sugarLevel;
Another one:
<?php
class IceCream extends Product
/** specific property for this product */
private $temperature;
Now, you need to assess every product in you depot. Normally, the first idea to get this resulst is doing:
$assess = array();
$products = $em->getRepository('models\Product');
foreach ($products as $p)
{
//summarize each product by type
$assets[$p->getType()] = $assets[$p->getType()] + 1;
}
echo "There are " . $assets['candy'] " candies in stock";
This is really heavy process because we are quering 200 tables just to determinate the existence of each product. This could be easy mitigated with a simple native query:
$query = $em->createNativeQuery('SELECT p.type, count(p.type) as total FROM Product p group by p.type', $rsm);
$result = $query->getArrayResult();
print_r($result);
// [0] => array('type' => 'candy', 'total' => 545),
// [1] => array('type' => 'icecream', 'total' => 344),
//...
// [199] => array('type' => 'foo', 'total' => 878),