How to retrieve only the relation ID, not the whole entity in MikroORM? - mikro-orm

I have the following basic test entities:
#Entity()
class Author {
#PrimaryKey()
public id!: number;
#Property()
public name!: string;
}
#Entity()
class Book {
#PrimaryKey()
public id!: number;
#Property()
public title!: string;
#ManyToOne({joinColumn: 'authorID'})
public author!: Author;
}
What i'm trying, is to select only a single Book record, with its 'author', but I care only about its ID, I don't want to actually load the entity.
If I simply call this, it won't work (no author data loaded at all):
em.getRepository(Book).findOne({id: 1}, {fields: ['id', 'title', 'author.id']});
'author.id' doesn't do the trick, the SQL doesn't even contain the 'authorID' field.
If I add 'author' to the fields list as well, it works, author is loaded (only with the ID), but as a separate entity, with a separate, additional SQL statement! That's what I'm trying to avoid.
em.getRepository(Book).findOne({id: 1}, {fields: ['id', 'title', 'author', 'author.id']})
#1. SQL
select `b0`.`id`, `b0`.`title`, `b0`.`authorID` from `book` as `b0` where `b0`.`id` = 1 limit 1
#2. SQL (this wouldn't be neccessary as I want only the ID)
select `a0`.`id` from `author` as `a0` where `a0`.`id` in (2)
--> Result:
Book: { id: 1, title: 'a book', author: { id: 2 } }
The only way I found is to add the specific 'authorID' field too to the Book entity:
#Property()
public authorID!: number;
But, I'd like to avoid introducing these foreign key columns, it would be better to handle through the already existing and used 'author' relation (only by the 'id' property).
Does any solution exists where I could retrieve a relation's ID without generating a 2nd SELECT statement (for the relation), and even avoid introducing the foreign key (next to the already existing relation property)? Would be great to receive through the relation without any extra sql statement.
Thanks in advance.

It is correct behaviour you see the second query, that is how population works, and the fact that you want just a single property from the entity does not change anything, you still populate the relation, and each relation will use its own query to load it. You can use LoadStrategy.JOINED if you want to use a single query. But that would still do a join for that relation, which is not needed for your use case.
Given you only want the FK to be present, you dont need to care about the target entity at all. This should do the trick too:
em.getRepository(Book).findOne(1, {
fields: ['id', 'title', 'author'],
populate: [],
});
This way you say you want those 3 properties to be part of what's selected from the Book entity. You already have the author property, which represents the FK. You will end up with what you want once you serialize such entity. During runtime, you will see entity reference there - an entity with just the PK. It is represented as Ref<Author> when you console.log such entity.
Note that you need that populate: [] there, as otherwise it would be inferred from your fields which contains author property, and that would trigger the full load of it.

Related

Add empty value to AssociationField in EasyAdminBundle

I want my AssociationField to display a blank value by defaut, in ordre to force the users to select an item.
As I understand, AssociationField is based on EntityType FormType.
My Problem is, if the field of my entity is required, the create form renders the AssociationField without empty value, and thus selects the first value.
I haven't seen any option to add a placeholder to a select for required properties.
Here are some samples:
My Doctrine entity :
//Player.php
#[ORM\ManyToOne(inversedBy: 'players')]
#[ORM\JoinColumn(name:'team_id', referencedColumnName: 'id', nullable: false)]
private Team $team;
... and my CrudController
//PlayerCrudController.php
public function configureFields(string $pageName): iterable
{
return [
//...
AssociationField::new('team'),
//...
]
}
And the create form displays a select with already a value selected.
I've looked into the docs but couldn't find how to achieve this.

Non-mapped form, EntityType and data attribute

I use an EntityType to create a form, but not mapped on an entity. The form is long and the user re-use the same options many times, then when he valid the form, if it's valid, I store the $form->getData() in session.
When I generate the form I inject the $data. It works well for all options, except the EntityType, I don't understand why...
In the $data, I've an ArrayCollection with the objects selected in the EntityType, but the form doesn't select it. I've used mapped = false, because if I remove it I've an error:
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
Someone I've an idea how to do ?
Settings mapped = false shouldn't be the solution in this case, because you need to write the stored data into this field, right? so mapped = false avoid it (see more about mapped option here)
The problem here is that the EntityType need to get the id value from each item and it requires that these items are managed actually by EntityManager:
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
An entity is in MANAGED state when its persistence is managed by an EntityManager. In other words, an entity is managed if its fetched from the database or registered as new through EntityManager#persist.
In you case, these entities comes from session, so you have two option:
Re-query the stored entities from database:
if (isset($data['foo']) && $data['foo'] instanceof Collection) {
$data['foo'] = $this->getDoctrine()->getRepository(Foo::class)->findBy([
'id' => $data['foo']->toArray(),
]);
}
Or, set a custom choice_value option to avoid the default one:
$form->add('foo', EntityType::class, [
'class' => Foo::class,
'choice_value' => 'id', // <--- default IdReader::getIdValue()
]);

cakephp 3 deeper associations

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])

Call setter method with variable name

What I'm asking here is something weird. I'm using Symfony2 with Doctrine2. To bypass a issue between FOSRestBundle and JMSBundle (who don't like composite primary key) I need to know if it's possible - and how - to call a setter from the name of the field.
Here an example: imagine my Product entity have a primary key composed by 3 fields.
The trick is to send an array this kind of JSON into my API REST:
{
"id": {
"field1": "xxx",
"field2": "...",
"field3": "..."
},
// other fields
}
Then I use json_decode() to extract the ID field. I create a table of $key => $value with the 3 primary keys. And there is my problem, with this table I want, in a foreach loop, to do something like $myEntity->set$KEY($VALUE).
I'm doing this because I want to reuse this code with all my entities.
I know that this is something really weird, to sum up with only the name of the object field/property I want to call is appropriate setter().
Thanks ;-)
PS: sorry if my English isn't perfect, this isn't my birth langage.
You can use dynamic method name in PHP :
$myEntity->{'set'.$KEY}($VALUE);
or more readable version :
$method = 'set'.$KEY;
$myEntity->$method($VALUE);
Don't forget to check if method exists anyway :
$method = 'set'.$KEY;
if (!method_exists($myEntity, $method))
throw new \Exception('Something bad');
$myEntity->$method($VALUE);

Add a child to a #OneToMany bidirectionnal with FOSRestBundle

I'm building a REST API with FOSRestBundle on my Symfony2 application.
What I want to do is to allow my POST and PUT actions, let say on a Product entity who got a #OneToMany relationship with another entity called Risk, to add/delete the children I past in JSON.
Let me give to you an example of JSON:
{
"cproduct": "ASSOC000000000999",
"risks": [
{
//first risk fields
},
{
//second risk fields
}
]
}
This is a simple JSON (the real one got more fields but these aren't needed here).
So here I've my Product ID (ASSOC000000000999) and I want to update this product by adding to him 2 new risks.
I know that normally I would have to create a Risk with the product ID separately, but for the needs of my application, I need to make only one request to the database. I want my users to be able to create a product, then add one or more risks and only then persist it into the database.
In a second time I would like them to be able to delete a child (risk) if he doesn't appear in the JSON sent with PUT action.
Here an example, let say that product “ASSOC000000000999” got a risk “RISK1”.
If I send this JSON:
{
"cproduct": "ASSOC000000000999",
"risks": [
{
“id”: “RISK2”,
//other fields
},
{ “id”: “RISK3”,
//other fields
}
]
}
On persist I want RISK1 to be deleted.
How can I do that? I found nothing on the web about that, please help me. :-)
PS: Sorry for my English, this isn't my birth langage.
EDIT:
I target what my problem really is.
When I send that JSON file with HTTP PUT verb:
{
"cfam": "AUTE",
"lpronom": "My Contract",
"riss": [{
"cris": "AS",
"lris": "Organization Law of 1901",
"lrisfic": "RCAD_FICHERCA9"
}]
}
Doctrine does a SELECT on RIS (my Risk table is called RIS, so the collection is $riss in my PROduct entity) where CRIS = "AS", and that's my problem. Here I want doctrine to create a RIS if the composite PK {cpro, nprover, cris} doesn't exist, and an update if it exist.
How can I do that?
(Without using Symfony form if possible).
Here my API call :
http://localhost/web/web/app_dev.php/fos/api/pros/ASSOC000000000009_1
My putProAction():
public function putProAction($id, Request $request)
{
$detachedEntity = $this->reqDeserialize($request, 'Namespace\Bundle\ProductBundle\Entity\Pro');
// Here I need to explode my serialized PUT parameter ID
list($cpro, $nprover) = explode('_', $id);
$detachedEntity->setCPRO($cpro);
$detachedEntity->setNPROVER($nprover);
$em = $this->getDoctrine()->getManager();
// I use merge to attache the entity to perform the persist
$entity = $em->merge($detachedEntity);
$em->persist($entity);
$em->flush();
return $entity;
}
You should use Symfony's Form Collections.
The example listed on that page is very similar to your requirements.

Resources