ZFCUser Identity within another entity - doctrine-orm

Is there a way where you can get the ZfcUser Identity or user_id in another entity?
I've been watching the following link, dealing with the servicemanager.
https://github.com/ZF-Commons/ZfcUser/wiki/How-to-check-if-the-user-is-logged-in#service-manager
When using the service manager, I get the following:
Call to a member function get() on a non-object
at the following line:
$auth = $sm->get('zfcuser_auth_service');
Using ServiceManager
public class CustomEntity implements ServiceManagerAwareInterface
{
protected $sm;
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $creator_id;
/**
*
* #ORM\Column(type="datetime", nullable=false)
*/
protected $creation_date;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $last_modifier_id;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $last_modified_date;
public function setServiceManager(ServiceManager $serviceManager)
{
$this->sm = $serviceManager;
return $this->sm;
}
public function getLoggedUserId()
{
$auth = $this->getServiceManager()->get('zfcuser_auth_service');
return $auth->getIdenty()->getId();
}
/**
* #ORM\PrePersist
*/
public function onPrePersist()
{
//$this->creator_id = $this->getLoggedUserId();
$this->creation_date = new \DateTime('now');
}
/**
* #ORM\PreUpdate
*/
public function onUpdate()
{
//$this->last_modifier_id = $this->getLoggedUserId();
$this->last_modified_date = new \DateTime('now');
}
In the above code I've been trying to use the ServiceManager but without any luck. As I said, its going wrong at this point:
Call to a member function get() on a non-object
at the following line:
$auth = $this->getServiceManager()->get('zfcuser_auth_service');
Aswell as the ServiceManager Interfaces don't have a Get Method, to retreive a ServiceManager. So in that case I also tried to use the ServiceLocator instead of the Manager, but also without any luck.
Old version
I've been able to set the User_id with the controller by creating a set method within the entity.
Controller:
public function addAction()
{
...
$customEntity->setCreator($this->zfcUserAuthentication()->getIdentity()->getId());
$this->getEntityManager()->persist($customEntity);
$this->getEntityManager()->flush();
...
}
Entity:
public function setCreator($user_id){
$this->creator_id = $user_id;
$this->creation_date = new \DateTime;
}
public function setModifier($user_id){
$this->last_modifier_id = $user_id;
$this->last_modified_date = new \DateTime;
}
New version
Instead of setting setCreator or setModifier everytime you are executing an action, it would have been lovely to get the User Identity at the Doctrine Persist or Update.
I would like to have the zfcUser_id inside my Entity. Some code example to show you what I ment:
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class CustomEntity
{
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $creator_id;
/**
*
* #ORM\Column(type="datetime", nullable=false)
*/
protected $creation_date;
...
/**
* #ORM\PrePersist
*/
public function onPrePersist()
{
$this->creator_id = ZfcUser_id
$this->creation_date = new \DateTime('now');
}
/**
* #ORM\PreUpdate
*/
public function onUpdate()
{
$this->last_modifier_id = ZfcUser_id
$this->last_modified_date = new \DateTime('now');
}
As you can see, I would like to have the ZfcUser_id at the following functions: onPrePersist and onUpdate to save the creator or updater of this entity/object. And for this version I was looking for a way to get the ZfcUser Identity, and found this link:
https://github.com/ZF-Commons/ZfcUser/wiki/How-to-check-if-the-user-is-logged-in#service-manager
Result based on replies
Create your Entity, create some Set Methods for your fields/attributes. Let your Controller handle this part. For the date(Time) as an default value is being set, you can use onPrePresist, onUpdate or you should check out other Doctrine Block Annotations.

Related

Filtering a Collection using Criteria targeting an Embeddable object

Let's say you have a class Title, and the title is translated in multiple languages using TitleTranslation classes.
To indicate which language the title is translated in, each translation has a Locale value object.
For readability, I am attempting to provide the Title class with a getTitle(Locale $locale) method, returning the correct translation.
The easy way to do this would be to loop over all translations, and check each translation's locale.
However, I would like to accomplish this using Criteria, so only a single TitleTranslation will be fetched and hydrated.
To illustrate the case, a simplified version of the classes I'm working with:
Title:
/** #ORM\Entity #ORM\Table */
class Title
{
/**
* #ORM\OneToMany(targetEntity="TitleTranslation", mappedBy="element")
*/
private $translations;
}
TitleTranslation:
/** #ORM\Entity #ORM\Table */
class TitleTranslation
{
/**
* #ORM\ManyToOne(targetEntity="Title", inversedBy="translations")
*/
private $title;
/**
* #ORM\Column(type="string")
*/
private $translation;
/**
* #ORM\Embedded(class="Locale")
*/
private $locale;
public function getTranslation() : string
{
return $this->translation;
}
}
Locale:
/** #Embeddable */
class Locale
{
/** #ORM\Column(type="string")
private $locale;
public function __toString()
{
return $this->locale;
}
}
I have made the following attempts, all of which are unsuccessful:
public function getTitle(Locale $locale)
{
$localeCriteria = Criteria::create()->where(Criteria::expr()->eq('locale', $locale));
/** #var TitleTranslation | bool $translation */
$translation = $this->translations->matching($translationCriteria)->first();
return $translation ? $translation->getTranslation() : null;
}
This approach fails with an ORMException "Unrecognized field: locale", which seems normal, as Embeddables should be queried as "locale.locale" (field in containing class.field in VO).
However, using this notation:
$localeCriteria = Criteria::create()->where(Criteria::expr()->eq('locale.locale', $locale));
Fails with Undefined property: TitleTranslation::$locale.locale
Am I missing something, or is this approach simply not possible?

ZF2 Doctrine Entity findAll

Being able to use Doctrine speeds up a lot of things however it feels somewhat clunky to me having to set / use the entity manager in all of my controllers. I would prefer to have all of the database logic in 1 specific module. Perhaps I'm just thinking about this the wrong way, and someone can point me in the right direction.
Currently I have my Entity which functions just fine and I can do insertions into the database fine with the following
namespace Manage\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class ViewController extends AbstractActionController {
public function somethingAction(){
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$user = new \Manage\Entity\User();
$user->setname('foo');
$user->settitle('bar');
$objectManager->persist($user);
$objectManager->flush();
}
}
However whenever I want to select something from the database I have to make sure to add
use Doctrine\ORM\EntityManager;
And then the following list of Controller functions...
/**
* #var EntityManager
*/
protected $entityManager;
/**
* Sets the EntityManager
*
* #param EntityManager $em
* #access protected
* #return PostController
*/
protected function setEntityManager(EntityManager $em) {
$this->entityManager = $em;
return $this;
}
/**
* Returns the EntityManager
*
* Fetches the EntityManager from ServiceLocator if it has not been initiated
* and then returns it
*
* #access protected
* #return EntityManager
*/
protected function getEntityManager() {
if (null === $this->entityManager) {
$this->setEntityManager($this->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
}
return $this->entityManager;
}
Once I have added all of that I can now do a query in my getsomethingAction like so...
public function getsomethingAction() {
$repository = $this->getEntityManager()->getRepository('Manage\Entity\User');
$list = $repository->findAll();
var_dump($list);
return new ViewModel();
}
To me that feels very clunky... I can do an insert without needing all the extra functions but I cannot do a select? Is it possible to extend the Entity class in order to get the find / findAll etc functions that is provided by calling $repository = $this->getEntityManager()->getRepository('Manage\Entity\User'); directly inside the entity?
By that I mean I would prefer to be able to run the find directly on the entity as I would when I set the data... like below:
public function getsomethingAction(){
$list = new \Manage\Entity\User();
$l = $list->findAll();
var_dump($l);
return new ViewModel();
}
Ok so my main objective so far has been to move the complex logic out of the controllers into a re-usable model. So with this example answer I'm creating an interface where the complex logic would live however it also allows me to still use the model in a controller to get data from the database... here is the Model...
namespace Manage\Model;
use Doctrine\ORM\EntityManager;
class ApiInterface {
/**
* #var EntityManager
*/
protected $entityManager;
protected $sl;
/**
* Sets the EntityManager
*
* #param EntityManager $em
* #access protected
* #return PostController
*/
protected function setEntityManager(EntityManager $em) {
$this->entityManager = $em;
return $this;
}
/**
* Returns the EntityManager
*
* Fetches the EntityManager from ServiceLocator if it has not been initiated
* and then returns it
*
* #access protected
* #return EntityManager
*/
protected function getEntityManager() {
if (null === $this->entityManager) {
$this->setEntityManager($this->sl->get('Doctrine\ORM\EntityManager'));
}
return $this->entityManager;
}
public function __construct($ServiceLocator) {
$this->sl = $ServiceLocator;
}
public function get() {
$repository = $this->getEntityManager()->getRepository('Manage\Entity\ApiList');
return $repository;
}
public function set() {
return new \Manage\Entity\ApiList();
}
public function save($data) {
$objectManager = $this->sl->get('Doctrine\ORM\EntityManager');
$objectManager->persist($data);
$objectManager->flush();
}
public function doComplexLogic($foo,$bar){
// Can now use both set() and get() to inspect/modify/add data
}
}
So now inside my controller I can do something that gets some basic data from the table like:
public function getapiAction() {
$api = new \Manage\Model\ApiInterface($this->getServiceLocator());
var_dump($api->get()->findAll());
return new ViewModel();
}
And to quickly set data from a controller I can do:
public function setapiAction() {
$apiInterface = new \Manage\Model\ApiInterface($this->getServiceLocator());
$api= $apiInterface->set();
$user->setfoo('blah');
$user->setbar('moo');
$apiInterface->save($api);
return new ViewModel();
}
And it also allows me to run complex logic from the controller by taking the complexity out of the controller like so...
public function complexAction(){
$foo = $this->params()->fromQuery();
$bar = $this->params()->fromPost();
$apiInterface = new \Manage\Model\ApiInterface($this->getServiceLocator());
$apiInterface->doComplexLogic($foo, $bar);
}
Please let me know in comments if this answer would be the proper way to do things, I realize it's very simple and generic but I wanted to keep it that way so others can understand what / why and if this is a good approach / not etc.

Doctrine "reverse" orphan removal

I have two entities (simplified):
class EncryptedMasterKey {
/**
* #ORM\ManyToOne(targetEntity="ExchangeFile", inversedBy="encryptedMasterKeys")
* #ORM\JoinColumn(name="exchange_file_id", referencedColumnName="id")
*
* #var ExchangeFile
*/
protected $exchangeFile;
}
and
class ExchangeFile {
/**
* #ORM\OneToMany(targetEntity="EncryptedMasterKey", mappedBy="exchangeFile", orphanRemoval=true, cascade={"persist", "remove"})
*/
protected $encryptedMasterKeys;
}
There can be many EncryptedMasterKeys for one ExchangeFile in the database. If the ExchangeFile is deleted, all related encrypted MasterKeys are deleted (orphanRemoval=true and cascade={"persist", "remove"} make sure this is the case). So far, so good.
Now as the actual file lies encrypted on the hard disk, there must be at least one EncryptedMasterKey so that the file can be decrypted. So when a EncryptedMasterKey is deleted and I discover that it is the last one for it's ExchangeFile, I also have to delete the ExchangeFile because it cannot be decrypted any more. An ExchangeFile cannot live without at least one EncryptedMasterKey.
How do I achieve this? #ORM\PreRemove in the EncryptedMasterKey class does't really help me because I don't have access to the Entity Manager:
class EncryptedMasterKey {
//...
/** #ORM\PreRemove */
public function removeOrphanExchangeFile()
{
if ($this->exchangeFile->isTheOnlyMasterKey($this))
// I don't have access to the Entity Manager,
// so how do I delete the ExchangeFile?
}
}
Is there any elegant solution to this?
Thanks for your time.
You can use an event subscriber and create a class like following:
class MyEncryptedMasterSubscriber implements \Doctrine\Common\EventSubscriber
{
public function getSubscribedEvents()
{
return array(\Doctrine\ORM\Events::onFlush);
}
public function onFlush(\Doctrine\ORM\Events\OnFlushEventArgs $eventArgs)
{
$uow = $eventArgs->getEntityManager()->getUnitOfWork();
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
if (
$entity instanceof EncryptedMasterKey
&& $entity->getExchangeFile()->isTheOnlyMasterKey($entity)
) {
$uow->scheduleForDelete($entity->getExchangeFile());
}
}
}
}
You can read more about how to register subscribers in the particular case of Symfony 2 on the documentation for it.

Doctrine 2 generates empty id on flush

First question on stackoverflow, little bit nervous.
So, here's the situation :
I'm using Doctrine 2 for a website. I installed it properly without pear, using the good libs and good paths in my application. I created my DB before starting to code, so I created mapping (xml & yml, just in case) with reverse-engineering method (doctrine exporter). Generated mappings are fine, then I wrote the entities files myself (the console didn't worked).
/** #Entity */
class Member {
/** #Id #Column(type="string", nullable=false) */
private $login;
/** #Column(type="string", nullable=false) */
private $password;
/** #Column(type="string") */
private $mail;
/** */
private $dateregister;
/** */
private $datelastconnexion;
/** #Column(type="string") */
private $description;
/** */
private $birthday;
/** #Column(type="string") */
private $website;
/** #Column(type="boolean") */
private $activated;
/** #Column(type="integer") */
private $idstatus;
/** #Column(type="integer") */
private $idcountry;
/** #Column(type="integer") */
private $idlang;
/** #Column(type="integer") */
private $idarticle;
/** #Column(type="integer") */
private $idfilters;
public function __construct($login) {
$this->login = $login;
$this->activated = false;
}
public function getLogin() {return $this->login;}
public function getPassword() {return $this->password;}
public function getMail() {return $this->mail;}
public function getDateregister() {return $this->dateregister->format('Y/m/d');}
public function getDatelastconnexion() {if ($this->datelastconnexion == null) return 'Never logged';
else return $this->datelastconnexion->format('Y/m/d H:i');}
public function getDescription() {return $this->description;}
public function getBirthday() {return $this->birthday->format('Y/m/d');}
public function getWebsite() {return $this->website;}
public function getActivated() {return $this->activated;}
public function getStatus() {return $this->idstatus;}
public function getCountry() {return $this->idcountry;}
public function getLang() {return $this->idlang;}
public function getArticles() {return $this->idarticle->toArray();}
public function getFilters() {return $this->idfilters->toArray();}
public function setLogin($newLogin) {$this->login = $newLogin;}
public function setPassword($newPassword) {$this->password = sha1($newPassword);}
public function setMail($newMail) {$this->mail = $newMail;}
public function setDateregister($newDate) {$this->dateregister = $newDate;}
public function setDatelastconnexion($newDate) {$this->datelastconnexion = $newDate;}
public function setDescription($newDescription) {$this->description = $newDescription;}
public function setBirthday($newDate) {$this->birthday = $newDate;}
public function setWebsite($newWebsite) {$this->website = $newWebsite;}
public function setActivated($activate) {$this->activated = $activate;}
public function setStatus($newStatus) {$this->idstatus = $newStatus;}
public function setCountry($newCountry) {$this->idcountry = $newCountry;}
public function setLang($newLang) {$this->idlang = $newLang;}
public function setArticles($newArticles) {$this->idarticle = $newArticles;}
public function setFilters($newFilters) {$this->idfilters = $newFilters;}
}
Now I was trying to insert something into the database, but I'm blocked with a small problem :
When a member tries to register, he fills some informations (logins, password, etc). I make every tests to see if informations are corrects and if they are, I create a new Object Member, which I fill with every given information and automatically generated one, then I call persist() on the object, then flush().
$newMember = new Member($login);
$newMember -> setPassword($passwd);
$newMember -> setMail($mail);
$newMember -> setDateregister($currentDate);
$newMember -> setDescription($description);
$newMember -> setBirthday($newBirthday);
$newMember -> setStatus($status[0]); // existing object
$newMember -> setCountry($country[0]); // existing object
$newMember -> setLang($lang[0]); // existing object
$doctrineManager->persist($newMember);
$doctrineManager->flush($newMember);
I got some surprise looking into the database, seeing the filled ID is equal to '' (empty string), and not $login.
So... that's my question : how is that possible ? Is it because I didn't fill every property of Member before flushing ? or didn't give information on relations between entities (one-to-many, many-to-many, many-to-one) ? or anything else ? I'm on this problem for one week now, I couldn't figure it out.
For information :
$status, $lang, $country are foreign keys.
$article and $filters are many-to-many relations.
Thanks for reading, and (I hope) thanks for explaining me my mistakes.
You must use flush like this:
$doctrineManager->flush();
after that you can access id with:
$newmember->getId();
The fact is $idarticles and $idfilters were ManyToMany relations, which I didn't specified. On construction of the object, it could be possible Doctrine 2 was not able to construct the object according to the mapping.
So the construct function looks like this :
public function __construct()
{
$this->idfilters = new \Doctrine\Common\Collections\ArrayCollection();
$this->idarticle = new \Doctrine\Common\Collections\ArrayCollection();
$this->activated = false;
}
And each ManyToMany have to get their ArrayCollections in order to work.

Doctrine 2 Can't Seem to Remove Many to Many Relationships

I have the following setup "Many Users can have Many Projects (Collaborators)"
/**
* #Entity #HasLifeCycleCallbacks
* #Table(name="projects")
*/
class Project implements \Zend_Acl_Resource_Interface {
/**
* #ManyToMany(targetEntity="User", mappedBy="projects")
* #OrderBy({"displayName" = "ASC", "username" = "ASC"})
*/
protected $collaborators;
..
}
/**
* #Entity
* #Table(name="users")
*/
class User implements \Zend_Acl_Role_Interface {
/**
* #ManyToMany(targetEntity="Project", inversedBy="collaborators")
*/
protected $projects;
...
}
I tried to remove a collaborator using the following
$user = Application_DAO_User::findById($this->_getParam('userid'));
$proj = Application_DAO_Project::getProjectById($this->_getParam('id'));
Application_DAO_Project::removeCollaborator($proj, $user); // <---
// Application_DAO_User
public static function findById($id) {
return self::getStaticEm()->find('Application\Models\User', $id);
}
// Application_DAO_Project
public static function getProjectById($id) {
return self::getStaticEm()->find('Application\Models\Project', $id);
}
public static function removeCollaborator(Project $proj, User $collaborator) { // <---
$proj->getCollaborators()->remove($collaborator);
$collaborator->getProjects()->remove($proj);
self::getStaticEm()->flush();
}
And there isn't any errors but the database stays the same ...
This may be well over due but was just experiencing the same problem myself... According to the doctrine 2 documents, the function ArrayCollection->remove($i) is for removing by array index.
What you are after is:
getCollaborators()->removeElement($collaborator);
I went round in circles trying to figure this out until I realised that for this to work:
getCollaborators()->removeElement($collaborator);
$collaborator would have to be the actual object from the collaborators ArrayCollection. That is, if you pass in a new Collaborator object with the same parameters it won't remove it. That's because ArrayCollection uses array_search to look for the object you want to remove.
Hope that saves someone else a few hours...