Extend function with Doctrine ORM Annotation - zend-framework

I've got the following situation: a (Doctrine Entity) ContentCategory that is extending the DataObject class. The DataObject class has the following function, onPrePersist:
/**
* #ORM\HasLifecycleCallbacks
*/
class DataObject implements InputFilterAwareInterface
{
...
/** #ORM\PrePersist */
public function onPrePersist()
{
//using Doctrine DateTime here
$this->creation_date = new \DateTime('now');
}
The ContentCategory class needs this function aswell. When I put this function in the ContentCategory class it works just fine. Is there a way whereby, the ContentCategory class can use the same function, onPrePersist() without defining it in the class itsself?
* #ORM\HasLifecycleCallbacks()
*/
class ContentCategory extends DataObject implements InputFilterAwareInterface
{
...
}
The reason to give objects the onPrePersist function, is to set a DateTime when this object is created or any other object / entity that is extending the DataObject class.
--< Edited >--
I've currently added a construct method to the ContentCategory like this:
public function __construct() {
parent::onPrePersist();
}
In this way Doctrine executes the function onPersist when a new Entity is created. The other case is when an enttiy is being updated, with Doctrine. I'll like to set a Modified_date. In that case there will be a function like this, in the DataObject class.
/**
* #ORM\HasLifecycleCallbacks
*/
class DataObject implements InputFilterAwareInterface
{
...
/**
* #ORM\PreUpdate
*/
public function onUpdate()
{
$this->last_modified_date = new \DateTime('now');
}
The Doctrine ORM Annotation (PreUpdate) that have been added, will make sure that the function (above) will be excuted on an update statement for an object. The problem is, how to call those functions in an object which extends the DataObject

/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks
*/
class TestimonialSuperclass
{
/**
* #ORM\PreFlush
*/
public function onPreFlush ()
{
echo 123;
}
}
/**
* #ORM\Entity
* #ORM\Table(name="testimonials")
* #ORM\HasLifecycleCallbacks
*/
class Testimonial extends TestimonialSuperclass
{
...
}

Related

Zend Framework 2 call TableGateway in Service

I'm new to ZF2. After few days of trying to figure out how all this stuff should work I was unable to figure out how should I call TableGateway Model from Service.
So I have Controller:
class SubscriberController extends AbstractActionController
{
/**
* #var \Subscriber\Service\SubscriberServiceInterface
*/
private $subscriberService;
/**
* #param $subscriberService
*/
public function __construct(SubscriberServiceInterface $subscriberService)
{
$this->subscriberService = $subscriberService;
}
Factroy for this Controller:
class SubscriberControllerFactory implements FactoryInterface
{
/**
* Returns ArchiveController instance.
*
* #param ServiceLocatorInterface $serviceLocator
* #return SubscriberController
* #override
**/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$sm = $serviceLocator->getServiceLocator();
return new SubscriberController(
$sm->get('Subscriber\Service\SubscriberServiceInterface')
);
}
Some SubscriberTable:
class SubscriberTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
And Service in which I want to get SubscriberTable instance and make some logic. But I can't figure out how should I call this instance in SubscriberService and set the DbAdapter for SubscriberTable
First implement servicelocator interface and define get and set locator functions to your service like this.
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Yourservice implements ServiceLocatorAwareInterface{
function test(){
$this->getSubscriberTable->fetchAll(); // call to subscriber table functions
}
/**
* #table gateway Call
**/
public function getSubscriberTable()
{
if (!$this->SubscriberTable) {
$sm = $this->getServiceLocator();
$this->SubscriberTable = $sm->get('Application\Model\SubscriberTable');
}
return $this->SubscriberTable;
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
}
Hope it will help you.

How to singularize route path part using FOSRestBundle

I have this controller (a snippet here)
/**
* Class UserController
* #package Belka\AuthBundle\Controller
*/
class UserController extends FOSRestController implements ClassResourceInterface
{
/**
* #View()
*
* #Route(requirements={"user"="\w+"})
* #ParamConverter("user", converter="fos_rest.request_body")
*/
public function postGlobaltokenAction(User $user)
{
...
}
that automatically generates the route:
post_user_globaltoken POST ANY ANY /api/users/{user}/globaltokens.{_format}
which is OK, except for the fact I would like "globaltoken" singularized. Is that possible? I cannot find any annotation to tweak this. Should I hardcode the route in my route.yml?
I've found two ways:
Using a personal Inflector
as Symfony's documentation suggests, you can register a personal Inflector which returns "globaltoken" always as singular, whereas all the other resources will be pluralized:
use Doctrine\Common\Util\Inflector;
use FOS\RestBundle\Util\Inflector\DoctrineInflector;
use FOS\RestBundle\Util\Inflector\InflectorInterface;
/**
* Inflector class
*
*/
class NoopInflector extends DoctrineInflector implements InflectorInterface
{
public function pluralize($word)
{
if($word == "globaltoken")
return $word;
return parent::pluralize($word);
}
}
services.yml:
services:
belka.auth_bundle.util.inflector:
class: Belka\AuthBundle\Util\NoopInflector
but I found this way a bit dirty, as I could need the plural form in the future.
Overriding the FOSRestBundle auto-generated routes
It's that simple! Just add the #Route annotation on the right methos and you're done!
/**
* #View()
*
* #Route("/users/{user}/globaltoken", defaults={"_format" = "json"}, requirements={"user"="\w+"})
*
* #ParamConverter("user", converter="fos_rest.request_body")
*/
public function postAction(User $user)
{
}
Now if I call php app/console debug:route I get what I want:
post_user_globaltoken POST ANY ANY /api/users/{user}/globaltoken.{_format}

Symfony 3.0.4 Circular reference detected during serialization with FOSRestBundle

I'm using FOSRestBundle in a Symfony project. When it I try to handle a view, it fails during the serialization of my data with the Symfony serializer as well as with the JMSSerializer.
This is the method rendering the response:
DefaultController.php
$em = $this->getDoctrine()->getManager('magellan');
$qb = $em->createQueryBuilder();
$query = $qb->select('h')
->from('DataBundle:Holding', 'h')
->where($qb->expr()->eq('h.id', ':holding_id'))
->setParameter('holding_id', $holding_id)
->getQuery();
$results = $query->getResult();
$view = $this->view($results, 200);
// Everything's ok up to this point
return $this->handleview($view);
And these are my entities:
Holding.php
class Holding
{
...
/**
* #ORM\OneToMany(targetEntity="Subsidiary", mappedBy="holding")
*/
private $subsidiaries;
}
Subsidiary.php
class Subsidiary
{
...
/**
* #ORM\ManyToOne(targetEntity="Holding", inversedBy="subsidiaries")
* #ORM\JoinColumn(name="id_holding", referencedColumnName="id_holding")
*/
private $holding;
/**
* #ORM\OneToMany(targetEntity="Brand", mappedBy="subsidiary")
*/
private $brands;
}
Brand.php
class Brand
{
...
/**
* #ORM\ManyToOne(targetEntity="Subsidiary", inversedBy="brands")
* #ORM\JoinColumn(name="id_subsidiary", referencedColumnName="id_subsidiary")
*/
private $subsidiary;
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="brand")
*/
private $products;
}
Product.php
class Product
{
...
/**
* #ORM\ManyToOne(targetEntity="Brand", inversedBy="products")
* #ORM\JoinColumn(name="id_brand", referencedColumnName="id_brand")
*/
private $brand;
/**
* #ORM\ManyToOne(targetEntity="Sector", inversedBy="products")
* #ORM\JoinColumn(name="id_sector", referencedColumnName="id_sector")
*/
private $sector;
/**
* #ORM\OneToMany(targetEntity="Commercial", mappedBy="product")
*/
private $commercials;
}
Commercial.php
class Commercial
{
...
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="commercials")
* #ORM\JoinColumn(name="id_product", referencedColumnName="id_product")
*/
private $product;
/**
* #ORM\OneToMany(targetEntity="CommercialReport", mappedBy="commercial")
*/
private $reports;
CommercialReport.php
class CommercialReport
{
...
/**
* #ORM\ManyToOne(targetEntity="Commercial", inversedBy="reports")
* #ORM\JoinColumn(name="id_commercial", referencedColumnName="id_commercial")
*/
private $commercial;
}
Sector.php
class Sector
{
...
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="sector")
*/
private $products;
}
When using the default symfony serializer, I get the following error:
"message":"A circular reference has been detected (configured limit:
1).","class":"Symfony\Component\Serializer\Exception\CircularReferenceException"
And when using the JMSSerializer, when I go to the corresponding page of the controller, the page just never finishes loading. At the same time in the dev.log file new Doctrine.debug entries with requests to my DB are added every second.
$normalizers->setCircularReferenceHandler(function ($object) {
return $object->getId();
});
Just add it after you make the instance if your objectNormalizer()
it worl perfectly for me
If you use FosRestBundle, you can use the GROUPS for the serializer. There is an annotation given by FosRestBundle : #FOS\RestBundle\Controller\Annotations\View(serializerGroups={"user"})
Your group can exclude the circular property.
Another idea you can do this. In your app/config/services.yml
circular_reference_handler:
public: false
class: callback
factory: [AppBundle\Serializer\CircularHandlerFactory, getId]
serializer.normalizer.object:
class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
arguments: ["#serializer.mapping.class_metadata_factory", null, "#serializer.property_accessor"]
public: false
tags: [serializer.normalizer]
calls:
- method: setCircularReferenceLimit
arguments: [1]
- method: setCircularReferenceHandler
arguments: ["#circular_reference_handler"]
The factory can be like this:
namespace AppBundle\Serializer;
class CircularHandlerFactory
{
/**
* #return \Closure
*/
public static function getId()
{
return function ($object) {
return $object->getId();
};
}
}

TYPO3 extbase: how to use ObjectStorage?

I'm trying to use a m:n relation, the same way as FrontEndUser is related to FrontEndUserGroup, e.g. without intermediate mm table. In my controller, I build my object, then I call $barRepository->update($barObject); to update the values of my object. However, it fails on the update function with the error:
Fatal error: Call to undefined method Cbrunet\Up\Domain\Model\Foo::getPosition() in /home/cbrunet/websites/typo3_src-6.1.1/typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php on line 486
where Foo is the type of the object contained in the ObjectStorage of Bar. My understanding is that getPosition should be called on the ObjectStorage, not on the object contained into that ObjectStorage. However, I cannot figure out why this is not working in my case.
This is in TYPO3 6.1.5. Any hint would be appreciated.
The model of Bar which has a m:n relation to Foo looks like:
namespace Cbrunet\Up\Domain\Model;
class Bar extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Cbrunet\Up\Domain\Model\Foo>
*/
protected $myprop;
public function __construct() {
$this->myprop = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $myprop
* #return void
*/
public function setMyprop(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $myprop) {
$this->myprop = $myprop;
}
/**
* #param \Cbrunet\Up\Domain\Model\Foo $myprop
* #return void
*/
public function addMyprop(\Cbrunet\Up\Domain\Model\Foo $myprop) {
$this->myprop->attach($myprop);
}
/**
* #param \Cbrunet\Up\Domain\Model\Foo $myprop
* #return void
*/
public function removeMyprop(\Cbrunet\Up\Domain\Model\Foo $myprop) {
$this->myprop->detach($myprop);
}
/**
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage
*/
public function getMyprop() {
return $this->myprop;
}
}
The relevant code in my controller looks like:
/**
* action update
*
* #return void
*/
public function updateAction() {
$args = $this->request->getArgument('myargs');
foreach ($args as $k=>$val) {
$pp = $this->barRepository->findOneByAprop($k); // another prop of Bar, not illustrated in the code above.
$listepour = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
foreach ($val as $p) {
$ap = $this->fooRepository->findOneByUid(intval($p));
$listepour->attach($ap);
}
$pp->setMyprop($listepour);
$this->barRepository->update($pp); // error occurs here
}
$this->redirect('list');
}
Do you also have configured your TCA?
do you have an initStorageObjects-function in your domain model?
Also you can try to build these case with the extension-manager and compare the code.

Doctrine 2 MongoDB ODM i18n

I'm playing with Doctrine ODM, trying to make some i18n-able field in my Mongo document. This is what I want to achieve in Mongo:
{
"title": {
"en": "Car",
"eu": "Autoa"
}
}
And the PHP API I want for the Document would be something like this:
$doc->getTitle()->setDefaultLocale('en');
$doc->getTitle(); // "Car"
$doc->getTitle()->get('eu'); // "Autoa"
$doc->getTitle()->set('es', 'Coche');
$doc->getTitle()->setTranslations([
'fr' => 'Voiture',
'eu' => 'Kotxea',
]);
$doc->getTitle()->getTranslations(); // ["en" => "Car", ...]
I have tried two aproaches, both of them with it's own pitfalls. I don't like none of them.
Custom Annotation
I've created a class which will be the middleman between the document and mongo. This class will be placed in the field, in this case in $title.
class Translation
{
protected $default;
protected $translations;
public function __construct(array $translations = array()) { /* ... */ }
public function get($locale) { /* ... */ }
public function getTranslations() { /* ... */ }
public function set($locale, $value) { /* ... */ }
public function setDefaultLocale($default) { /* ... */ }
public function setTranslations(array $translations = array()) { /* ... */ }
}
Then, I've created a custom FieldType, which converts the Mongo array to the Translation middleman object and viceversa (convertTo* methods seem to be ignored by Doctrine and are equal to the closureTo* methods, so I'll omit them):
class TranslationType extends \Doctrine\ODM\MongoDB\Types\Type
{
public function convertToDatabaseValue($value) { /* ... */ }
public function convertToPHPValue($value) { /* ... */ }
public function closureToMongo()
{
return '$return = $value->getTranslations();';
}
public function closureToPHP()
{
return '$return = new \App\TransBundle\MongoDB\Translation($value);';
}
}
Then, I have my annotation:
/** #Annotation */
class Translation extends \Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractField
{
public $type = 'translation';
}
And the document:
use App\TransBundle\MongoDB\Annotations\Translation;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** #MongoDB\Document */
class Translated
{
/** #MongoDB\Id */
protected $id;
/** #Translation */
protected $title;
public function getId() { /* ... */ }
public function getTitle() { /* ... */ }
}
The GOOD parts:
Easy usage: one use, property declaration, annotation and the getter.
Reads OK from Mongo => Doctrine.
Meets API requirements.
The BAD parts:
Doesn't save to DB, I suppose that it's because the Translation object doesn't dirty the title property on the parent object Translated.
$title doesn't get initialized to the middleman Translation object on object creation.
This would be fixed by initializing the object in the constructor, but if possible I'd like to try to avoid this to keep the usage as lean as possible. I'll have to find a workaround.
EmbedOne
The second approach consists of using an embedded document, this works perfectly, but has it's own small issues. :-)
First, my base Translation class for the embedded document, this class will work directly on the class properties instead of an array property:
class BaseTranslation
{
public function __construct(array $translations = array()) { /* ... */ }
public function get($locale) { /* ... */ }
public function getTranslations() { /* ... */ }
public function set($locale, $value) { /* ... */ }
public function setDefaultLocale($default) { /* ... */ }
public function setTranslations(array $translations = array()) { /* ... */ }
}
Then, the Translation class to be used in my projects, this will be the actual embbeded document:
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** #MongoDB\EmbeddedDocument */
class Translation extends BaseTranslation
{
/** #MongoDB\String */
protected $en;
/** #MongoDB\String */
protected $es;
/** #MongoDB\String */
protected $fr;
}
Finally the Document
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** #MongoDB\Document */
class Translated
{
/** #MongoDB\Id */
protected $id;
/** #MongoDB\EmbedOne(targetDocument="Translation") */
protected $title;
public function getId() { /* ... */ }
public function getTitle() { /* ... */ }
}
The GOOD parts:
It just works, reads and writes nicely.
Easy setup.
Can be used with any data type, so it would be easy to add an isTranslated i18n boolean field, just add a new TranslationBoolean class.
The BAD parts:
Not a big problem but the locales are hardcoded in the Translation class, it would be nice to be able to work directly on an array but this would add another level in the schema and type coercion might be lost.
Like in the other approach, the property doesn't get initialized, but it's easy as initializing it in the constructor (like any One2Many relation).
Conclusions
I'm liking more the second approach, which works how it's now. Do you have any idea how to overcome the BAD parts on both approaches?
Thanks!
Regarding the type methods:
closureToMongo() is actually not used.
closureToPHP() is used by the HydratorFactory class.
convertToPHPValue() is used by ClassMetadataInfo, but only for identifiers.
convertToDatabaseValue() is used in a few places, such as UnitOfWork and PersistenceBuilder.
The missing convertToDatabaseValue() implementation could be related to why you're seeing TranslationType fail to persist to the database.
The issue of "hardcoded locales in the Translation class" could be alleviated by storing an object (Hash annotation) within this field, although that would add another layer to the model. An EmbedMany array of objects containing a field each for the language code and value would consume even more space, although the model could make it easier to devise a form to edit the data.
Have you looked into the DoctrineExtensions library? It contains a component for having translatable documents/entities (supports both ODM and ORM). The author has slowed down his own development of it, but there are a healthy amount of pull requests being merged on the project and it is widely used.