Doctrine - mapping to a non entity class - php

Is there a possibility to map doctrine's native query to a class that is not annotated as ORM entity?
I have a basic class under App\Model
class BasicModel
{
private int $weight;
private int $points;
}
And my goal is to map the result of a query directly into this non entity class.
$rsm = new ResultSetMapping();
$rsm->addEntityResult(BasicModel::class, 'b');
$rsm->addFieldResult('b', 'points', 'points');
$rsm->addFieldResult('b', 'weight', 'weight');
$query = $this->em->createNativeQuery('select points, weight from some_table', $rsm);
$result = $query->getResult();
Currently, an error occurs
The class 'App\Model\BasicModel' was not found in the chain configured namespaces App\Entity
I don't want to annotate this class as Entity, since I don't need this information to be stored in database.
My current stack:
Symfony 5
MySQL 8
Doctrine 2

Unfortunately that is not possible. Doctrine is build as a Database Abstraction Layer (DBAL) and does not allow for this use case.
I see two paths forward. I know you say you do not want that, but since you have MySQL in your stack, you could opt to add the data anyway to the database and have the entity as a proper entity class. The other option is to not use Doctrine and build a different implementation to list all entities.

Related

Doctrine persist not updating existing records but trying to create a new one

In my application my domain model (aggregate) is different than a database models (entities), and because of that I have a separate aggregate and doctrine entity classes, and a mapper class which is building doctrine entity from aggregate and vise versa before getting/persisting, I'm doing it's directly in repository, like this:
public function persist(Aggregate $aggregate): void
{
$this->entityManager->persist(
$this->mapper->mapToDoctrineEntity($aggregate)
);
}
public function get(AggregateId $id): Aggregate
{
/**
* #var ?DoctrineEntity $entity
*/
$entity = $this->entityManager->find(DoctrineEntity::class, $id->toInt());
if (null === $entity) {
throw new AggregateNotFoundException(sprintf('Aggregate [%d] not found', $id->toInt()));
}
return $this->mapper->mapToAggregate($entity);
}
What mapper doing is simply creating new class of doctrine enitty or aggregate depends on what we need to create and populate with a data from entity/aggregate. All working fine with creating new records, but when I'm trying to update existing, doctrine does not see that entity with provided id already in the database and trying to insert it, which causing a duplication error. Is there a way to notify doctrine somehow that it's existing record????
Btw, before updating records I'm using repo method get to get existing record from database, but probably due to that transformation from doctrine entity to aggregate, doctrine loose tracking of that entity
Custom mapper on top of the doctrine seems not a good idea. Doctrine already is a data mapper and entities is meant to be a domain model.
But if you really need it you must keep entity that you got from repository, and update it on changes.

Utility functions that a Symfony entity should have

I don't know maybe it is me or may be it is symfony that is lame, please why is symfony entity class not having utility functions like these:
//Assume product is an entity class
$product = Product::findById($id);
$productExample = new Product(array("price"=>20, "vendor"=>'friends'));
$products = Product::findByExample($productExample);
$Dql = "SELECT * FROM product p WHERE p.id IN (SELECT si.pid FROM solditem si WHERE si.sold_date BETWEEN 1-1-2017 AND 2-2-2017 ORDER BY si.price)";
$products = Product::findByDql($Dql) or Product::findBySql($sql);
$product = new Product(array('id' => $id)); // I should be able to get the entity with that Id
In other frameworks like Zend (even small and simple ones like cakePHP, codeIgniter) it is very easy to implement functions like these and they will be callable everywhere, you will not have to be running after one entityManagers or entityCEOs.
Is there an easy way to implement way to implement these in symfony, if there is please let me know, if not please give me why.
Thanks
In general I suggest you read a little bit more about the documentations of the frameworks you are comparing.
I don't know maybe it is me or may be it is symfony that is lame, please why is symfony entity class not having utility functions like these
To be honest in this case it is you ;-) ... no offense, but the systems are implementing different storage layers. Other frameworks like CodeIgniter, CakePHP and Laravel are implementing the Active Record Pattern. The latter e.g. by providing the Eloquent ORM
The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table.
Symfony on the other hand is using Doctrine 2 ORM as storage layer:
As the term ORM already hints at, Doctrine 2 aims to simplify the translation between database rows and the PHP object model. The primary use case for Doctrine are therefore applications that utilize the Object-Oriented Programming Paradigm. For applications that do not primarily work with objects Doctrine 2 is not suited very well.
You can make up your own mind which one you favour, but it is not an easy task to swap the storage layer in these frameworks. If you search for it I think you will find some discussions around this topic.
In Symfony Entity Repositories are usually the place where you define your desired functionality:
When you query for a particular type of object, you always use what's
known as its "repository". You can think of a repository as a PHP
class whose only job is to help you fetch entities of a certain class.
Create a custom repository class following the official documentation or take it a step further and set up your own repository class without using Doctrines EntityRepository as described in this highly recommended article.
There is no one preventing you from adding static functions to it, but the recommended way is to make a service out of your repository, e.g.
Repository
// src/AppBundle/Product/DoctrineBasedProductRepository.php
namespace AppBundle\Product;
use Doctrine\ORM\EntityManager;
class DoctrineBasedProductRepository implements ProductRepository
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function all()
{
// Create a basic DQL query to fetch all entities
return $this->entityManager->createQuery('SELECT p FROM '.Product::class.' p')
->getResult();
}
...
}
Service definition
# app/config/services.yml
services:
app.product_repository:
class: AppBundle\Product\DoctrineBasedProductRepository
arguments: ['#doctrine.orm.entity_manager']
Now you can use the repository e.g. in your controller:
public function listAction()
{
$products = $this->get'app.product_repository')->all();
// ... do something, like pass the $products object into a template
}

Override Persister:loadAll in EntityRepository

I have the need to display data from a table in a pager and at the same time get a count on records in a child table. There are too many child records to load into memory and count, so I want to modify how the query is built in my EntityRepository.
Ideally I don't want to re-implement the pager functionality, so I am looking to override findBy in my EntityRepository and add the count(), join and group by in my query?
How do I do this best? I'm using Symfony 2.8, Doctrine 2 and PagerFanta
I also found this http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/filters.html but it seems thin on documentation
I hope I can help you!
If I understand correctly, you want to load from the database for each instance of an object the number of child objects in addition to all the fields of this object - as an additional field. Right?
I don't know, how the parent entity and the child entity are named in your model. But I will give a working example for your tasks from my web application. You have only to rename the parent and child entity.
There are affiliate programs, each of which has a finite set of accounts in my application. So, I have a parent entity called 'AffiliateProgram' and I have a child entity called 'AffiliateProgramAccount'.
You should not override the standard method in the repository for class of your entity. You just have to add your method according to your needs.
First thing, create a repository for class of your parent entity (How to Create custom Repository Classes). I do it in the YAML file with a description as follows:
Analytics\TrafficStatisticsBundle\Entity\AffiliateProgram:
type: entity
table: affiliate_program
repositoryClass: Analytics\TrafficStatisticsBundle\Entity\AffiliateProgramRepository
Then you must create a repository class of your parent entity according to the path in the description for Doctrine. I keep the classes of repositories together with the model classes in the 'Entity' directory. Now you can create your custom methods in the created repository according to your individual needs.
I suggest to use Doctrine DBAL to solve your problem (How to use Doctrine DBAL). Here is an example of my query, absolutely identical to yours:
use Doctrine\ORM\EntityRepository;
class AffiliateProgramRepository extends EntityRepository {
/**
* #return array
* #throws \Doctrine\DBAL\DBALException
*/
public function findAllAffiliatePrograms() {
// $stmt = $this->getEntityManager()
// ->getConnection()
// ->prepare('
// SELECT COUNT(apa.id) AS Number_of_accounts, ap.*
// FROM affiliate_program AS ap
// LEFT JOIN affiliate_program_account AS apa ON apa.affiliate_program_id = ap.id
// GROUP BY ap.id');
// $stmt->execute();
// return $stmt->fetchAll();
$stmt = $this->getEntityManager()
->getConnection()
->prepare('
SELECT COUNT(apa.id) AS Number_of_accounts, ap.*
FROM affiliate_program AS ap,
affiliate_program_account AS apa
WHERE apa.affiliate_program_id = ap.id
GROUP BY ap.id');
$stmt->execute();
return $stmt->fetchAll();
}
}
Here notice that I don't use object names ('AnalyticsTrafficStatisticsBundle:AffiliateProgram' in my case) for operators FROM, [LEFT | RIGHT | INNER | OUTER] JOIN as it must be used in DQL, etc. Instead, I use the real table names.
Note: The query without using the JOIN operator executes faster. In my example I showed two ways - using the JOIN operator and the same with the using of WHERE operator. Proof:
and
Now you can get all the objects according to your query in the controller, simply by calling the newly created method:
<?php
namespace Testing\TestBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* TestController
*
* #Route("/test")
*/
class TestController extends Controller {
/**
* #Route("/", name="test_index")
* #Method("GET")
*/
public function indexAction() {
$em = $this->getDoctrine()->getManager();
$affiliatePrograms = $em->getRepository('AnalyticsTrafficStatisticsBundle:AffiliateProgram')->findAllAffiliatePrograms();
return $this->render('TestingTestBundle:Test:test.html.twig', [
'result' => $affiliatePrograms
]);
}
}
And to make sure that everything works, you just write the following snippet in .twig file (for example):
{{ dump(result) }}
Materials also, see here:
How to use Raw SQL Queries in Symfony 2
Raw SQL Queries
Executing SQL directly in Symfony2 Doctrine
I hope that's all you need!

PHP Pattern - how to retrieve collection of objects

Situation:
There is simple User class which is Doctrine entity (I skipped comments to keep the code short):
class User {
protected $iUserId;
protected $sName;
}
Question:
How to retrieve collection of objects of class User from the controller?
Follow up:
Until now, we were creating methods like getUsers() in User class which gets data from DB, creates User class objects and returns it.
Now, I'm wondering if it's not a better solution to create class like UserCollection which would handle data retrieving and creating User objects? Maybe I should make use of \Doctrine\Common\Collections\Collection class somehow?
What I'd like to accomplish is easy way to handles i.e. where clauses. UserCollection class could have QueryBuilder object on which I could operate from controller like this:
$oUserCollection = new UserCollection();
$oUserCollection->setWhere( 'u.iUserId = 1' );
$aUsers = oUserCollection->getUsers();
...
Please share your thoughts on that topic.
Update:
Doctrine provides a concept of repositories of entities which I think maybe the solution I'm looking for.
You have two options:
If your criterias are simple, eg. you just want to filter on a single property, or you don't need filtering at all, you can use the findBy() and findAll() methods, on the EntityRepository:
// find all users
$repository = $em->getRepository('My\\User');
$users = $repository->findAll();
// find users who are marked as active
$users = $repository->findBy(array('active' => true));
// sort users by age
$users = $repository->findBy(array(), array('age' => 'DESC'));
If you have complex(er) requirements, or your finders will be used from multiple places, you will have to create a custom EntityRepository, and group your finder logic there. You have to specify in the mapping that you want to create your own EntityRepository for this entity, the method for this varies depending on what mapping driver you use (annotation, yaml, xml). For example, in yaml you would have to place this line in your mapping:
RepositoryClass: My\Repository\UserRepository
And then create the file:
namespace My\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository {
public function findVIPUsers() {
$query = $this->_em->createQuery("Your query here");
return $query->getResult();
// you can also use $this->findBy(), or $this->findAll() here
}
}
And then in your controller, when you call $em->getRepository('My\User') it will return the custom repository you just created.

Extending Doctrine Entity in order to add business logic

I'm trying to practice a good design and extending Doctrine entity.
My extended class, the model basically, will have extra business logic + access to the entity basic data.
I am using Doctrine 2.2.1 & Zend Framework 1.11.4 & php 5.3.8
When I use DQL, doctrine return successfully the Model entity.
When I use Doctrine native find() function, it returns nothing :(.
HELP...
This is how it rolls:
Bootstrap.php:
$classLoader = new \Doctrine\Common\ClassLoader('Entities', APPLICATION_PATH.'/doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Models', APPLICATION_PATH);
$classLoader->register();
Model in APPLICATION_PATH\models\User.php:
namespace Models;
use Doctrine\ORM\Query;
/**
* Models\User
*
* #Table(name="user")
* #Entity
*/
class User extends \Entities\User {
public function __wakeup() {
$this->tools = new Application_App_Tools();
}
Entity retrieval functions:
DOESN'T WORK:
$userEntity = $registry->entityManager->find('Models\User', $userEntity);
WORKS:
$qry = $qb
->select('u')
->from('Models\User','u');
You shouldn't add the business logic to the entities, you should use models for that instead. One way of doing it would be:
Use models for business logic.
Create custom Doctrine 2 repositories for your all your database queries (DQL or otherwise) [1].
Leave your entities alone.
In practise this means that models are plain PHP classes (or maybe framework extended depending on what your're using) but your models have no relation to your database. Your models do however, instantiate your custom Doctrine 2 repositories. E.g. a UserRepository might contain a method called getUserById. Within your repositories is where your run your actual queries and return entity instances for the models to work with.
[1] http://docs.doctrine-project.org/en/latest/reference/working-with-objects.html#custom-repositories
As I understand Doctrine, entityManager is responsible only for persistent entities, and extending Entities\User entity with Model\User will create another entity (stored in same table as stated in docblock), but not managed by entityManager or in collision with it because you probably didn't mention #InheritanceType("SINGLE_TABLE") in Entities\User docblocks:
Read this docs for more info http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
What I tried to do was a bad practice.
I coupled my DB entity and tools from zend as #Ivan HuĆĄnjak mentioned.
What should be done is de-coupling.
Business logic should be in services\controller and these should address the entity and it's methods.
you can\should add helper functions to the doctrine entity that only relates to the entity properties.
Regarding my main purpose (to have an entity class that the Doctrine CLI can rewrite & update):
doctrine only searches from changes in the native fields\methods, updates them accordingly and discard all other functions (helpers). so there is no problem when letting doctrine update the php entity!
p.s.
move to symfony2.

Categories