I am trying to implement doctrine2 embeddable feature for value objects.
This is simple snippet, slightly modified from doctrine official documentation:
/** #Entity */
class User
{
/** #Embedded(class = "Address") */
protected $address;
/** #Column(type = "string") */
protected $street;
}
/** #Embeddable */
class Address
{
/** #Column(type = "string") */
protected $street;
/** #Column(type = "string") */
protected $postalCode;
/** #Column(type = "string") */
protected $city;
/** #Column(type = "string") */
protected $country;
}
When I try to access property from embedded object, I am getting "cannot access protected property" error - which is expected.
Embeddable objects should be read only by their base entity class, and not modified since they are immutable (following domain driven design guidelines).
What I don't understand is how can I read their properties since they are private/protected ? Is there any PHP magic involved here ? I was trying to use different getters like:
getStreet(Address $address){
$this->street = $address->street;
}
but always get error, before calling persist and flush methods on entity manager instance.
Thanks.
Use getters on the embedded class.
class Address
{
...
public function getStreet(){
return $this->street;
}
...
}
Then, your method changes
getStreet(Address $address){
$this->street = $address->getStreet();
}
If you want to retrieve the street value starting from a user, you should create a getter method in the User class that internally has access to the address and does:
class User
{
public getAddressStreet() {
$this->address->getStreet();
}
}
or something similar.
Related
In our application, we use repositories for models that are fetched from the database. So, we have an abstract repository that knows about the database, has a loadById method to load a database record and an abstract getEntity method that creates an object for that specific repository. Example code:
abstract class EntityRepository {
/**
* #param int $id
* #return AbstractEntity
*/
public function loadById($id) {
$record = $this->db->loadById($id);
$entity = $this->getEntity();
return $this->inflate($record, $entity);
}
/**
* #return AbstractEntity
*/
protected abstract function getEntity();
}
class PeopleRepository extends EntityRepository {
protected function getEntity() {
return new PeopleEntity();
}
}
abstract class AbstractEntity {
private $id;
/**
* #return int
*/
public function getId() {
return $this->id;
}
/**
* #param int $id;
*/
public function setId($id) {
$this->id = $id;
}
}
class PeopleEntity extends AbstractEntity {
private $name;
/**
* #return string
*/
public function getName() {
return $this->name;
}
/**
* #param string $name;
*/
public function setName($name) {
$this->name= $name;
}
}
When using an instance of PeopleRepository and fetching a model through loadById, PhpStorm is not able to resolve the returned model to a concrete type, but provides only code completion for the functions of AbstractEntity. Is there any simple way to make it work?
In https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata, I've only found ways to make it work for concrete classes and their functions. So, enumerating all repository classes and all their ways of creating an entity might work. But I'd love to see an abstract way of defining like "All instances of EntityRepository will return an entity of that type defined in getEntity() when loading an entity"
I doubt there's a blanket way of doing this. Even using PHPStorm meta you have to be explicit for each case. Perhaps the way of doing this is by doing something like adding a repository facade e.g.
class RepositoryFacade {
public static function __callStatic($method, $args) {
if ($args[0] == People::class) {
array_shift($args);
return new PeopleRepository()->{$method}(...$args);
}
}
}
Then you might be able to typehint this using:
override(RepositoryFacade::loadById(0), type(0));
Of course the facade is not really the best pattern to be using in general so I can see how this might not be ideal.
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
I'm looking for a way to extend my Symfony2 (i currently use 2.3) Entity class with a method to effectively filter its relations on demand. So, imaging i have such 2 classes with OneToMany relation:
/**
* ME\MyBundle\Entity\Kindergarten
*/
class Kindergarten
{
/**
* #var integer $id
*/
private $id;
/**
* #var ME\MyBundle\Entity\Kinder
*/
private $kinders;
public function __construct()
{
$this->kinders = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get kinders
*
* #return Doctrine\Common\Collections\Collection
*/
public function getKinders()
{
return $this->kinders;
}
}
/**
* ME\MyBundle\Entity\Kinder
*/
class Kinder
{
/**
* #var integer $id
*/
private $id;
/**
* #var string $name
*/
private $name;
/**
* #var integer $age
*/
private $age;
}
My goal is to have a method on Kindergarten class to get on demand all kinders with age, for instance, between 10 and 12:
$myKindergarten->getKindersByAgeInInterval(10,12);
Of course, i can do something like:
class Kindergarten
{
...
public function getKindersByAgeInInterval($start, $end)
{
return $this->getKinders()->filter(
function($kinder) use ($start, $end)
{
$kinderAge = $kinder->getAge();
if($kinderAge < $start || $kinderAge > $end)
{
return false;
}
return true;
}
);
}
...
}
The solution above will work, but it's very inefficient, since I need to iterate across ALL kinders which can be a really big list and have no way to cache such filters. I have in mind usage of Criteria class or some proxy patterns, but not sure about a way to do it nice in Symfony2 especially since they probably will need access to EntityManager.
Any ideas?
I would suggest extracting this responsibility into an EntityRepository:
<?php
class KinderRepository extends \Doctrine\ORM\EntityRepository
{
public function findByKindergartenAndAge(Kindergarten $entity, $minAge = 10, $maxAge = 20)
{
return $this->createQueryBuilder()
->... // your query logic here
}
}
All the lookups should really happen in classes where you have access to the entity manager.
This is actually the way suggested by the Doctrine architecture. You can never have access to any services from your entities, and if you ever think you need it, well, then something is wrong with your architecture.
Of course, it may occur to you that the repository method could become pretty ugly if you later decide on adding more criteria (imagine you'll be searching by kindergarten, age, weight and height too, see http://www.whitewashing.de/2013/03/04/doctrine_repositories.html). Then you should consider implementing more logic, but again, that should not be that necessary.
I have a hook that gets successfully called
class tx_srfeuserregister_MyHooksHandler {
public function registrationProcess_afterSaveCreate ($recordArray, &$invokingObj) {
var_dump($recordArray); //i get here
}
}
thanks to being registered in the sr_feuser_register/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['sr_feuser_register']['tx_srfeuserregister_pi1']['registrationProcess'][] = 'EXT:sr_feuser_register/hooks/class.tx_srfeuserregister_MyHooksHandler.php:&tx_srfeuserregister_MyHooksHandler';
To do useful things here, and without copy/pasting code, I'd like to call a service method from an extbase service that is located in another extension
typo3conf/ext/my_extension/Classes/Domain/Service/Tx_MyExtension_Domain_Service_EntityFactory.php
How do I inject that into my hook, or get it through the object factory? I've tried a couple of things and googled a lot, but could not figure it out.
I suggest you get an instance of the extbase object manager, get your service and call your method. Something like this:
/**
* #var Tx_Extbase_Object_ObjectManager
*/
protected $objectManager;
/**
* #var Tx_MyExt_Service_MyService
*/
protected $myService;
public function registrationProcess_afterSaveCreate ($recordArray, &$invokingObj) {
$this->initializeObjects();
// Now you can use your Service.
$this->myService->myMethod($recordArray);
}
/**
* #return void
*/
public function initializeObjects() {
if (empty($this->objectManager)) {
$this->objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_ObjectManager');
}
if (empty($this->myService)) {
$this->myService = $this->objectManager->get('Tx_MyExt_Service_MyService');
}
}
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