Dependency injection and Model entities - the right way ? - php

My symfony2 application has the following structure:
There is a service data_provider, which gets data from various external sources and represents it as entity objects.
Some objects has relations. Currently I am loading relations in controller or helper-services if needed.
It is not very convenient, sometimes I want to get relations from my entity ojbect. To do this I need access to data_provider service.
I want to implement something like doctrine lazy-loading, what is the right way of doing this ?
Some obvious solutions - to inject data_provider in every entity instacne, or to some static property, or to make some static methods in service, or to use evenet dispatcher, but I don't think it is the right way

Made some research of ObjectManagerInterface as Cerad suggested, and found this peace of code: https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Persistence/PersistentObject.php
PersistentObject implements ObjectManagerAware interface, it has private static property where ObjectManager is stored.
So I ended with this:
class DataProvider
{
public function __construct()
{
...
AbstractEntity::setDataProvider($this);
}
}
abstract class AbstractEntity
{
private static $dataProvider;
public static function setDataProvider() {...};
protected static function getDataProvider() {...};
}

The main purpose of services in Symfony (and not only) is exactly this one - to deliver distinguished functionalitys globally over your project.
In this regard, a single service, in your case - dataProvider, should always deliver a single entity. If you have to deal with multiple entities returned from one data source, wrap the data source deliverer into a service itself, and then define one service per each entity with the deliverer injected into it.
Then you can inject the respective entity services into your controllers.

Related

Why I can't access to the paginator DTO in EasyAdmin?

I'm trying to access the EasyCorp\Bundle\EasyAdminBundle\Dto\PaginatorDtoin my Crud Controller :
public function __construct(
private EntityManagerInterface $manager,
private EntityRepository $entityRepository,
private PaginatorDto $paginatorDto,
) {
}
But I've got this error => Cannot autowire service "App\Controller\Activity\ActivityCrudController": argument "$paginatorDto" of method "__construct()" references class "EasyCorp\Bundle\EasyAdminBundle\Dto\PaginatorDto" but no such service exists. and I don't understand why and How to fix it :(
Any idea ?
I'm not an expert of that bundle so take my answer with a pinch of salt but looking at bundle's code I've noticed PaginatorDto not to be a service (as the name suggests).
As that DTO is not a service (and it's ok it is not), you can't autowire it nor make it a service "locally" (eg.: in your application).
So, in order to retrieve the DTO object, inject AdminContextProvider (that is a service as you can notice here) instead and use it to get the DTO
$adminContext->getCrud()->getPaginator();
Your crud controller should extend AbstractCrudController which give you access to the current admin context.
So if you want to use it in one of your crud controller method you should be able to access the paginator with:
$paginator = $this->getContext()->getCrud()->getPaginator();
If you want to do the same outside your crud controller, let's say in another service. You need to inject the AdminContextProvider to first get the AdminContext and do it the same way.
private ?AdminContext $siteRepository;
public function __construct(AdminContextProvider $adminContextProvider)
{
$this->adminContext = $adminContextProvider->getContext();
}

Doctrine relationship with non-entity model

I'm trying to create an application where I have two related models: "User" and "Debtor".
User is a doctrine entity that is persisted in my database.
Debtor is a model that is stored on an external system and accessed through an API. I have already created a repository for accessing Debtors with find(), findAll() and findByUserId().
If "Debtor" was a Doctrine Entity, i would have a #OneToMany relationship on the User model in order to access debtors. Something like this:
/**
* #Entity
*/
class User {
/**
* #OneToMany
*/
private $debtors;
public function getDebtors() {
return $this->debtors;
}
public function setDebtors($debtors) {
$this->debtors = $debtors;
return $this;
}
}
One solution would be to call DebtorRepository::findByUserId($this->id) in the getDebtors() method, but I don't think that is the right way to go.
I also thought about have a #PostLoad method that loads the users debtors into the $debtors property.
I'm not much for having that much logic inside my models, but i don't know what other choices I have?
Furthermore, I also need to be able to access various other models through the Debtor model and so i have the issue again.
If resource for your API, I think you should have service which will take care of loading that (as probably you need to check for errors/authentication/...). So I would load it on demand from service.
But if you really want to have it accessible in the entity, then you can use Entity Listener, hook to the postLoad method and inject it there. In this case your entity will still stay thin, because this heavy logic will be in external service.

Is it considered a bad practice to add fields to Symfony entity in controller?

Is it considered a bad practice to add fields to Symfony entity in controller? For example lets say that I have a simple entity:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And then in UserController.php I want to do the following:
foreach($users as $user){
$user->postsCount = someMethodThatWillCountPosts();
}
So later that postsCount can be displayed in Twig. Is it a bad practice?
Edit:
It's important to count posts on side of mysql database, there will be more than 50.000 elements to count for each user.
Edit2:
Please take a note that this questions is not about some particular problem but rather about good and bad practices in object oriented programming in Symfony.
As #Rooneyl explained that if you have relation between user and post then you can get count easily in your controller, refer this for the same. But if you are looking to constructing and using more complex queries from inside a controller. In order to isolate, reuse and test these queries, it's a good practice to create a custom repository class for your entity.Methods containing your query logic can then be stored in this class.
To do this, add the repository class name to your entity's mapping definition:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
Doctrine can generate empty repository classes for all the entities in your application via the same command used earlier to generate the missing getter and setter methods:
$ php bin/console doctrine:generate:entities AppBundle
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
More Deatils
Updated Answer
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: SOURCE
"rather about good and bad practices in object oriented programming"
If that's the case then you really shouldn't have any business logic in controller, you should move this to services.
So if you need to do something with entities before passing them to twig template you might want to do that in specific service or have a custom repository class that does that (maybe using some other service class) before returning the results.
i.e. then your controller's action could look more like that:
public function someAction()
{
//using custom repository
$users = $this->usersRepo->getWithPostCount()
//or using some other service
//$users = $this->usersFormatter->getWithPostCount(x)
return $this->render('SomeBundle:Default:index.html.twig', [
users => $users
]);
}
It's really up to you how you're going to do it, the main point to take here is that best practices rather discourage from having any biz logic in controller. Just imagine you'll need to do the same thing in another controller, or yet some other service. If you don't encapsulate it in it's own service then you'll need to write it every single time.
btw. have a read there:
http://symfony.com/doc/current/best_practices/index.html

Laravel extend Eloquent and 3rd party class

I'm currently working on my first PHP/Laravel 4 project, I'm developing on it a storage class to add Eloquent support to a 3rd party library.
My EloquentStorage class extends the AbstractStorage class from the library, and I make usage of most of AbstractStorage methods. Now that I want to add Eloquent support to my new EloquentStorage class, I faced the fact that PHP does not support multiple inheritance.
Is there a proper way to define an Eloquent model without extending it as:
class MyClass extends Eloquent {}
And, if not, how to deal with such situation when I need to extend 3rd party class and also extend Eloquent? Maybe using Laravel's IoC?
I think your model should extend from Eloquent, and instead be accessed through a repository. Your repository can have a $storage property, and would be responsible for calling the appropriate methods on your AbstractStorage implementation. Below is more pseudo- than actual code, but illustrates where you can plug in your implementation for an update operation.
class MyClass extends Eloquent
{
/* Normal Eloquent model implementation */
}
class MyRepository
{
protected $storage;
protected $myClass;
public function __construct(MyClass $myClass, AbstractStorage $storage)
{
$this->myClass = $myClass;
$this->storage = $storage;
}
public function update($id, $data)
{
// This is just an example operation, basically here's your chance to call
// the 3rd-party implementation. Here is pre-eloquent update, but can be
// after
$this->storage->update($id, $data);
// Use the empty Eloquent class property instance to obtain an instance of
// the requested model
$instance = $this->myClass->find($id);
// set instance properties
$instance->save();
// Example post-eloquent update
$this->storage->update($id, $data);
}
}
class MyStorage extends AbstractStorage { /* Your Storage Implementation */ }
$repo = new MyRepository(new MyClass, new MyStorage);
// Update item id 42's foo property
$repo->update(42, [ 'foo' => 'bar' ]);
A benefit to this approach is that the construction of the repository itself can be offloaded to the IoC through a Service Provider, and be injected inside of a controller / form validator etc, which means the execution will become automatic and hide the underlying complexity of the 3rd party library from the rest of the system (the repository helps keep your 3rd party abstraction from leaking).
Another benefit is that you don't need any special code in your eloquent models having anything to do with your completely unrelated 3rd party code. All of the logic is encapsulated in a single spot, and can even be shared amongst multiple models. Want to change 3rd party providers? Write a new implementation of AbstractStorage, update the service provider and you're done.
One other benefit is improved testability. Instead of statically utilizing an eloquent model directly (a la $user = User::find($id)) you would be manipulating your repository object instead ($user = $this->repo->find($id)). Since your repository can be trivially mocked and itself be tested (without also testing Eloquent or hitting the database), you can write integration tests on all of your controller routes and know the moment that changes to your codebase break your business rules.

Symfony2: Get Service Inside Entity

Is there any way to call service inside entity
I need entity Manager inside entity so I can able to get custom result with repository functions.
I am thinking about inject ContainerInterface inside my entity like this.
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyEntity
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getEntityManager(){
return $this->container->get('doctrine')->getEntityManager();
}
}
But I think this is not right way to do that and it take more code I mean I have to do this for all entity where I need entity Manager
Is there any good solution ?
I don't know if you can but you shouldn't do it anyway. The entities are meant to be really simple...
need entity Manager inside entity so i can able to get custom result with repository functions
What do you want to do exactly, there must be a different solution...
As already mentioned, dependency injection is definitely the wrong way to go.
Use either Custom Entity Repositories (http://symfony.com/doc/2.0/book/doctrine.html#custom-repository-classes) for more complex queries or use a specific service where you can implement your custom result if more complexity is needed (http://symfony.com/doc/2.0/book/service_container.html#referencing-injecting-services)

Categories