PHP Dependency injection and Inheritance - php

Hi Quick question about dependency injection
I'm using symfony 3 and am coming to terms with DI
Say I have a class
use Doctrine\ORM\EntityManagerInterface;
class CommonRepository
{
/**
* #var EntityManagerInterface
*/
protected $em;
/**
* DoctrineUserRepository constructor.
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getEntityManager()
{
return $this->em;
}
}
Now a new class called UserRepository and I inject the above class, does that mean I can access the injected items injected items (clearly he was dreaming at the end of inception)?
class UserRepository
{
/**
* #var CommonDoctrineRepository
*/
private $commonRepository;
/**
* #var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* DoctrineUserRepository constructor.
* #param CommonRepository $CommonRepository
*/
public function __construct(CommonRepository $CommonRepository)
{
$this->commonRepository = $commonRepository;
$this->em = $this->commonRepository->getEntityManager();
}
public function find($id)
{
//does not seem to work
//return $em->find($id);
//nor does
//return $this->em->find($id);
}
}
even if I extend the class and then try to construct parent no joy, clearly I can just inject the Doctrine manager
into the UserRepository, I was just trying to get some understanding around DI and inheritance
class UserRepository extends CommonRepository
{
/**
* #var CommonDoctrineRepository
*/
private $commonRepository;
/**
* #var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
public function __construct(CommonDoctrineRepository $commonRepository, $em)
{
parent::__construct($em);
$this->commonRepository = $commonRepository;
}
}
for the symfony component I have defined services like
app.repository.common_repository:
class: AppBundle\Repository\Doctrine\CommonRepository
arguments:
- "#doctrine.orm.entity_manager"
app.repository.user_repository:
class: AppBundle\Repository\Doctrine\UserRepository
arguments:
- "#app.repository.common_repository"

Dependency Injection is just the process of passing objects into the constructor (or setter methods). A Dependency Injection Container (or Service Container) is nothing more but a helper to inject the correct instances of objects into the constructor.
With that said, it's clear that Dependency Injection doesn't influence PHP in any way (btw, nothing in Symfony does, it's all just plain PHP stuff).
So inheritance works just as it works with some plain PHP objects (which is a strange comparisation, as they already are plain PHP objects).
This means that if CommonRepository#getEntityManager() is public and the CommonRepository is instantiated correctly, this method should return the entity manager that was passed to its constructor.
The same applies to UserRepository: If you save the passed CommonRepository#getEntityManager() instance in the $em property, all methods can access the entity manager using this $em property. This means doing $this->em->find(...) should perfectly work ($em->find(...) shouldn't, as there is no $em variable).
tl;dr: The code you showed in your question (except from the weird extence example) works perfectly.

Related

TYPO3 v9.5.11 Extbase: Inject ServiceObject generated by a ContainerClass into Repository

I am trying to inject an service object into my Repository. I have created different Service Classes under the directory Classes/Services. There is also one class that I created called ContainerService, which creates and instantiate one ServiceObject for each Service Class.
ContainerService Class:
namespace VendorName\MyExt\Service;
use VendorName\MyExt\Service\RestClientService;
class ContainerService {
private $restClient;
private $otherService;
/**
* #return RestClientService
*/
public function getRestClient() {
$objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
if ($this->restClient === null) {
$this->restClient = $objectManager->get(RestClientService::class);
}
return $this->restClient;
}
...
As I said, I create my ServiceObjects in the ContainerService Class.
Now I want to inject the ContainerService into my Repository and use it.
MyRepository Class:
namespace VendorName\MyExt\Domain\Repository;
use VendorName\MyExt\Service\ContainerService;
class MyRepository extends Repository
{
/**
* #var ContainerService
*/
public $containerService;
/**
* inject the ContainerService
*
* #param ContainerService $containerService
* #return void
*/
public function injectContainerService(ContainerService $containerService) {
$this->containerService = $containerService;
}
// Use Objects from The ContainerService
public function findAddress($addressId) {
$url = 'Person/getAddressbyId/'
$someData = $this->containerService->getRestClient()->sendRequest($url)
return $someData;
}
In MyController I recieve the $someData from my findAddress function and do some work with it.
But when I call my Page, I get following ErrorMessage:
(1/2) #1278450972 TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
Class ContainerService does not exist. Reflection failed.
Already tried to reload all Caches and dumping the Autoload didn't help either.
Didn't install TYPO3 with composer.
I appreciate any advice or help! Thanks!
Actually found the Issue.
In MyRepository Class there was a Problem with the Annotations and the TypeHint:
namespace VendorName\MyExt\Domain\Repository;
use VendorName\MyExt\Service\ContainerService;
class MyRepository extends Repository
{
/**
*** #var \VendorName\MyExt\Service\ContainerService**
*/
public $containerService;
/**
* inject the ContainerService
*
* #param \VendorName\MyExt\Service\ContainerService $containerService
* #return void
*/
public function injectContainerService(\VendorName\MyExt\Service\ContainerService $containerService) {
$this->containerService = $containerService;
}
// Use Objects from The ContainerService
public function findAddress($addressId) {
$url = 'Person/getAddressbyId/'
$someData = $this->containerService->getRestClient()->sendRequest($url)
return $someData;
}
Now it works.

Laravel 5.5 dependency injection with constructor methods

I have created a service in app/services/KDataService.php that looks like this:
class KDataService
{
/** #var string */
private $license;
/** #var string */
private $owner;
/** #var string */
private $accessToken;
public function __construct($owner, $license)
{
$this->owner = $owner;
$this->license = $license;
...
}
...
}
In one of my controller I try to inject this service with the dependency injection pattern but I get the following error:
Unresolvable dependency resolving [Parameter #0 [ $owner ]] in class App\Services\KDataService
My controller:
use App\Services\KDataService;
class DamagePointController extends Controller
{
/** #var KDataService $kDataService */
private $kDataService;
/**
* Instantiate a new controller instance.
*
* #param KDataService $kDataService
*/
public function __construct(KDataService $kDataService)
{
$this->kDataService = $kDataService;
}
...
}
Anyone knows how I can pass my $owner and $license?
The problem is that your service has arguments but you don't specify them. There are several ways to do this.
Using service provider:
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class kDataServiceServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind(KDataService::class, function ($app) {
return new KDataService(getOwner(), getLicense());
});
}
}
bind could be change to other methods. See Service Container docs.
Using app to make instanse:
/* Controller __construct */
$this->kDataService = \App::make(KDataService::class, [getOwner(), getLicense()]);
Simply create class instance
/* Controller __construct */
$this->kDataService = new KDataService(getOwner(), getLicense());
Note: getOwner and getLicense change to your logic. Usually you can retrieve it within controller or from $app.
Generally what you need to resolve the issue is to read about service container and service providers in docs.

Custom functions in Doctrine2 auto generated classes

Is there a way to extend classes auto-generated from database by Doctrine2 ?
Example: I have this User class generated by Doctrine.
<?php
namespace Entities;
/**
* User
*/
class User
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
private $lastName;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
*
* #return User
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set lastName
*
* #param string $lastName
*
* #return User
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* #return string
*/
public function getLastName()
{
return $this->lastName;
}
I would like to add this function :
public function getFullName()
{
return $this->getFirstName().' '.$this->getLastname();
}
Is there a cleaner way than adding it directly into this class?
I tried to create another class (Test) in libraries and extends it, then add it in autoload (which is working), but i get an error when I try to save object :
class Test extends Entities\User {
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
Message: No mapping file found named 'Test.dcm.yml' for class 'Test'.
I'm using Doctrine2 in CodeIgniter3.
Thanks.
As explained in the Doctrine 2 FAQ:
The EntityGenerator is not a full fledged code-generator that solves all tasks. [...] The EntityGenerator is supposed to kick-start you, but not towards 100%.
In plain English this means you ask Doctrine to generate the Entity files only once. After that, you are on your own and do whatever changes you like (or it needs) to them.
Because an Entity is not just a container for some properties but it's where the entire action happens, this is how the flow should happen, Doctrine cannot write more code for you.
The only way to add functionality to the stub Entities generated by Doctrine is to complete the generated classes by writing the code that implements the functionality of each Entity according to its role in your Domain Model.
Regarding the other issue, on the Test class, the error message is self-explanatory: any class passed to the EntityManager for handling needs to be mapped.
Take a look at the help page about Inheritance Mapping. You can either map class User as a Mapped Superclass (it acts like a template for the derived classes and its instances are not persisted in the database) or you can use Single Table Inheritance to store the instances of all classes derived from User in a single table (useful when they have the same properties but different behaviour).
Or, in case you created class Test just because you were afraid to modify the code generated by Doctrine, put the behaviour you need in class User and drop class Test.
Seems you are having trouble while accessing the user entity class. You mentioned that test is a library class. Why not try to access the User entity class from a controller. If can do this then may be something is wrong with the configuration of test file. Besides, you need to map you doctrine entity class properly. You can have a look here to learn about doctrine mapping using yml: http://doctrine-orm.readthedocs.org/en/latest/reference/yaml-mapping.html
you can do this:
<?php
namespace Entities;
/**
* User
*/
class User extends Test
{
//... and extends Test
}
or
<?php
namespace Entities;
/**
* User
*/
class User
{
//...
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
view more
Symfony 2 - Extending generated Entity class
http://www.theodo.fr/blog/2013/11/dynamic-mapping-in-doctrine-and-symfony-how-to-extend-entities/
http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html
Annotation allows you to specify repository class to add more methods to entity class.
/**
* #ORM\Entity(repositoryClass="App\Entity\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
// calling repository method
$entityManager->getRepository('User')->getFullName();
Here's a link [http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html]
7.8.8. Custom Repositories

What is the best practice to keep my Model separate from Entities in Doctrine2?

OK, so now as I eventually decided to use Doctrine2 ORM in my new Zend2 project I have a question about the best design practices to maintain the Model and Entity classes separate. I'm not asking what's the difference between Entity and Model, I understand that. I also have my Service layer connected with Repository classes working just fine. What I'm asking, is a situation when I have a Model class with some business logic, let's say Document class, and then DocumentEntity which represents it's persistence, and just want to keep them separate, so for example, here is my Entity:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="DocumentRepo")
* #ORM\Table(name="document")
*/
class DocumentEntity {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
* #ORM\Column(type="guid")
*/
protected $guid;
/**
* #ORM\Column(length=64)
*/
protected $name;
// more stuff below...
}
Now, there's my Model class with a lot of business logic that I want to keep separate:
class Document implements SomeImportantInterface, AnotherImportantInterface {
public function doSomeImportantStuff() {
}
public function doEvenMoreImportantStuff() {
}
}
And finally, the Service class:
class DocumentService {
const DOCUMENT_ENTITY = 'DocumentEntity';
/**
* #var EntityManager
*/
protected $em;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager) {
$this->em = $entityManager;
}
public function getDocument($guid) {
$documentEntity = $this->em->getRepository(self::DOCUMENT_ENTITY)->findByGuid($guid);
return; //what? I want here Document to be returned, not the DocumentEntity..
}
public function createDocument() {
return new Document();
}
public function saveDocument(Document $document) {
// Document -> DocumentEntity
// $documentEntity = ...?
//$this->em->persist($entityDocument);
//$this->em->flush();
}
}
So as you can see, the plan is to have a Document objects that the application only cares about (accessible via Service), not the DocumentEntities. Two possible approaches that came to my mind:
keep the DocumentEntity as a property inside the Document
make Document to extend DocumentEntity
Or, maybe I'm just missing something in here, and just taking the wrong end of the stick? Looking forward to hear your opinions!
Your entity should extend the model class. Your application needs to do all it's model interaction via a repository interface.
DomainModel
DocumentDomainModel
DocumentDomainModelInterface
DocumentDomainModelRepositoryInterface (get/create/save)
DoctrineEntity
DocumentDoctrineEntity extends DocumentDomainModel
DocumentDoctrineRepository implements DocumentDomainModelRepositoryInterface
This also gives you the flexibility to implement other repositories such as in memory one for testing.

Doctrine doesn't load associations from session correctly

I'm having this behavior with Doctrine 2.1 where I'm looking for a nice 'workaround'. The problem is as follows:
I have a user Entity:
/**
* #Entity(repositoryClass="Application\Entity\Repository\UserRepository")
* #HasLifecycleCallbacks
*/
class User extends AbstractEntity
{
/**
*
* #var integer
*
* #Column(type="integer",nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
*
* #var \DateTime
* #Column(type="datetime",nullable=false)
*/
protected $insertDate;
/**
*
* #var string
* #Column(type="string", nullable=false)
*/
protected $username;
/**
*
* #ManyToOne(targetEntity="UserGroup", cascade={"merge"})
*/
protected $userGroup;
}
And a usergroup entity:
/**
* #Entity
*/
class UserGroup extends AbstractEntity
{
/**
*
* #var integer
*
* #Column(type="integer",nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
*
* #var string
* #Column(type="string",nullable=false)
*/
protected $name;
}
If I instantiate a user object (doing this with Zend_Auth) and Zend_Auth puts it automatically the session.
The problem is however, that is I pull it back from the session at a next page then the data in the user class is perfectly loaded but not in the userGroup association. If I add cascade={"merge"} into the annotation in the user object the userGroup object IS loaded but the data is empty. If you dump something like:
$user->userGroup->name
You will get NULL back. The problem is no data of the usergroup entity is accesed before the user object is saved in the session so a empty initialized object will be returned. If I do something like:
echo $user->userGroup->name;
Before I store the user object in the session all data of the assocication userGroup is succesfully saved and won't return NULL on the next page if I try to access the $user->userGroup->name variable.
Is there a simple way to fix this? Can I manually load the userGroup object/association with a lifecycle callback #onLoad in the user class maybe? Any suggestions?
Your problem is a combination of what mjh_ca answered and a problem with your AbstractEntity implementation.
Since you show that you access entity fields in this fashion:
$user->userGroup->name;
I assume your AbstractEntity base class is using __get() and __set() magic methods instead of proper getters and setters:
function getUserGroup()
{
return $this->userGroup;
}
function setUserGroup(UserGroup $userGroup)
{
$this->userGroup = $userGroup;
}
You are essentially breaking lazy loading:
"... whenever you access a public property of a proxy object that hasn’t been initialized yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load."
Source: Doctrine Best Practices: Don't Use Public Properties on Entities
You should instead be accessing fields this way:
$user->getUserGroup()->getName();
The second part of your problem is exactly as mjh_ca wrote - Zend_Auth detaches your entity from the entity manager when it serializes it for storage in the session. Setting cascade={"merge"} on your association will not work because it is the actual entity that is detached. You have to merge the deserialized User entity into the entity manager.
$detachedIdentity = Zend_Auth::getInstance()->getIdentity();
$identity = $em->merge($detachedIdentity);
The question, is how to do this cleanly. You could look into implementing a __wakeup() magic method for your User entity, but that is also against doctrine best practices...
Source: Implementing Wakeup or Clone
Since we are talking about Zend_Auth, you could extend Zend_Auth and override the getIdentity() function so that it is entity aware.
use Doctrine\ORM\EntityManager,
Doctrine\ORM\UnitOfWork;
class My_Auth extends \Zend_Auth
{
protected $_entityManager;
/**
* override otherwise self::$_instance
* will still create an instance of Zend_Auth
*/
public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public function getEntityManager()
{
return $this->_entityManager;
}
public function setEntityManager(EntityManager $entityManager)
{
$this->_entityManager = $entityManager;
}
public function getIdentity()
{
$storage = $this->getStorage();
if ($storage->isEmpty()) {
return null;
}
$identity = $storage->read();
$em = $this->getEntityManager();
if(UnitOfWork::STATE_DETACHED === $em->getUnitOfWork()->getEntityState($identity))
{
$identity = $em->merge($identity);
}
return $identity;
}
}
And than add an _init function to your Bootstrap:
public function _initAuth()
{
$this->bootstrap('doctrine');
$em = $this->getResource('doctrine')->getEntityManager();
$auth = My_Auth::getInstance();
$auth->setEntityManager($em);
}
At this point calling $user->getUserGroup()->getName(); should work as intended.
When you store the entity to a session (via Zend_Auth or otherwise), the object is serialized and no longer maintained by Doctrine when subsequently retrieved and unserialized. Try merging the entity back into the EntityManager. See http://www.doctrine-project.org/docs/orm/2.1/en/reference/working-with-objects.html

Categories