phpro / zf-doctrine-hydration-module Goto Github PK
View Code? Open in Web Editor NEWConfigurable Doctrine hydrators for ZF2
Configurable Doctrine hydrators for ZF2
When hydrating referenced documents the current logic assumes that an identifier will be passed and that a document with that identifier already exists. This may not be the case, however, and in the event that no identifier is passed it should be possible to instantiate a new document in lieu of an existing one.
A hydrator should be able to hydrate any document passed. The entity_class
setting implies having to specify a different hydrator for every entity in the system that one may wish to hydrate, which is impractical and cumbersome to implement for large systems.
Each of the Hydrator strategies are configured in the service manager as invokable services.
However within the module, the Hydrator strategy objects are instantiated from the class directly rather than via the service manager. For example in the EmbeddedReferenceField class:
$strategy = new EmbeddedField($this->getObjectManager());
$strategy->setClassMetadata($this->getClassMetadata());
$strategy->setCollectionName($this->getCollectionName());
$strategy->setObject($value);
It prevents being able to override the strategy via the service manager.
I know it is possible to configure custom strategies per field for an entity, however sometimes it may be desirable to set specific logic for all EmbeddedFields for example and therefore an option to set a custom EmbeddedFieldsStrategy without having to configure each specific embed field in every entity.
I'm, working with three entities: Albums, Artists and Tracks. They have a many-to-many relationship with doctrine. And I'm trying to update the Artists and Tracks list for a particular venue.
The way I'm trying to do so is by using PATCH.
//Adding Artists and Tracks to a specific Album entity
PATCH /Album/:album_id
{
"artist": [
{"id":123},
{"id":456},
{"id":789}
],
"tracks": [
{"id":111},
{"id":222},
{"id":333}
]
}
//Removing a particular Artist and two tracks from this Album.
PATCH /Album/:album_id?collection_action=remove
{
"artist": [
{"id":123}
]
"tracks": [
{"id":111},
{"id":222}
]
}
//Removing a particular Artist and adding one track back to this Album.
PATCH /Album/:album_id?collection_action=remove&collection_remove=artist
{
"artist": [
{"id":123}
]
"tracks": [
{"id":222}
]
}
//Removing all artists and tracks from the Album.
//PATCH /Album/:album_id?collection_action=remove&collection_remove=artists,tracks
PATCH /Album/:album_id?collection_action=remove
{
"artist": [],
"tracks: []
}
A detailed example of what I'm proposing could be found zfcampus/zf-apigility-doctrine#215
Problem is, where in the modules could I specify this multiple strategies and make them dependent on the query being sent?
Hi veewee, sorry to bother again.
I cannot get my head around the hydration of 'embedded docs' working with 'zf-apigility-doctrine'.
I have have a MappedSuperclass holding an EmbedDocument as per below code:
/**
@odm\MappedSuperclass
@odm\InheritanceType("COLLECTION_PER_CLASS")
*/
abstract class Item {
/** @odm\Id */
protected $id;
/** @odm\Field(type="string") */
protected $brand;
/** @odm\Field(type="string") */
protected $proddesc;
/** @odm\Field(type="int") */
protected $quantity;
/** @odm\Field(type="int") */
protected $warranty;
/** @odm\EmbedOne(targetDocument="PhysicalAttributes", strategy="set") */
protected $physicalAttributes;
... getters and setters
}
The embedded doc is defined like this:
/**
@odm\EmbeddedDocument
*/
class PhysicalAttributes {
/** @odm\Field(type="float") */
private $width;
/** @odm\Field(type="float") */
private $depth;
/** @odm\Field(type="float") */
private $height;
/** @odm\Field(type="float") */
private $weight;
... getters and setters
}
The concrete child is a very simple doc with 2 params as follow:
/**
@odm\Document(collection="singlesku")
*/
class SingleSku extends Item {
/** @odm\Field(type="boolean") */
protected $isUnique;
/** @odm\Field(type="string") */
protected $googleGtin;
...getters and setters
}
From POSTMAN I send the following POST data to create a 'singlesku' document:
{
"brand": "The Brand",
"proddesc": "Just a description!!!",
"quantity": 4,
"warranty": 5,
"isUnique": true,
"googleGtin": "GTin from Google",
"physicalAttributes": {
"width": 12.5,
"depth": 13.52,
"height": 28.654,
"weight": 543.97
}
}
The 'singlesku' document is successfully created in Mongo yet with no embedded 'PhysicalAttributes' data.
The JSON returned by Apigility is the following:
{
_isUnique: true
*googleGtin: "GTin from Google"
*id: "53c7887f05e17e65840041a8"
*brand: "The Brand"
*proddesc: "Just a description!!!"
*quantity: 4
*warranty: 5
-_physicalAttributes: {
width: 12.5
depth: 13.52
height: 28.654
weight: 543.97
}
-_links: {
-self: {
href: "http://localhost:8888/api/singlesku/53c7887f05e17e65840041a8"
}
}
}
Any help appreciated, as usual.
I have encountered a bug when an object is extracted that has at least one embed field that is either null or does not exist in the database (mongo).
The problem arises because the extract() method within the EmbeddedField Strategy class accepts a value of mixed type, however the Doctrine Hydrator extract() method that is subsequently called requires an object. Unfortunately the type is never tested and ends up in a get_class function call, as the value is NULL when not set in the DB, the get_class function call returns the name of the current class and exceptions are eventually thrown much further down the line.
EmbeddedField :
/**
* @param mixed $value
*
* @return mixed
*/
public function extract($value)
{
$hydrator = $this->getDoctrineHydrator();
return $hydrator->extract($value);
}
Note that the param annotation is mixed however the DoctrineHydrator class requires an object to its extract() method :
/**
* Extract values from an object
*
* @param object $object
* @return array
*/
public function extract($object)
{
$this->prepare($object);
if ($this->byValue) {
return $this->extractByValue($object);
}
return $this->extractByReference($object);
}
I have added a test case below, where the embed one is never set. This test fails because of an exception due to the non object value.
I think the extract in the embed field strategy should either throw an exception or handle non objects by returning the same given value.
I'm using this module as a dependancy of ZF Apigility Doctrine module. I have configured a naming strategy service and filters as per instructions however my custom service is never invoked.
This is quite tricky to debug as I'm not certain whether the issue is within this module or ZF Apigility Doctrine or indeed elsewhere. However the features are part of this module so I though I would report it here to see if anyone has any feedback to help with my debugging.
What I have found so far is that my initial DoctrineObject is instantiated and the various filters and strategies attached, this object is of type Phpro\DoctrineHydrationModule\Hydrator\ODM\MongoDB\DoctrineObject and appears to be correctly configured with my custom filters etc.
However when the actual extraction takes place the object upon which the extract method is called is of type : DoctrineModule\Stdlib\Hydrator\DoctrineObject
A completely different object (different type and hash id ) which does not hold any reference to my custom filters/namingStrategies.
Therefore my extracted data is neither filters, nor the naming strategy processed.
I am continuing to work on this, but if you have any pointers that would be most grateful.
Thanks for this great module! Per hydrator strategies can be defined.
As feature request, it would be awesome if default strategies could also be defined that apply to all hydrators.
I use Apigility with Doctrine that makes use of this module. On all fields that are called "created" I want my UtcDateTimeStrategy to be set. So on every hydrator that apigility creates per rest service. I could add this manually in the configuration for every hydrator, but it is likely someone in the development team will forget one in the future.
This module uses old doctrine/doctrine-module": "^1.2" ...
Im using latest DoctrineORM an Doctrine modules..
doctrine/doctrine-module": "^1.2" versus doctrine/doctrine-module[2.1.4]
Unit tests fail because BaseTest.php is no longer in the Doctrine Mongo ODM library.
PSR-4 should be implemented on the source.
All tests should be re-namespaced to PhproTest\DoctrineHydrationModule.... so they are not in the same namespace as the library code.
The DoctrineHydratorFactory has a method called getObjectManagerType that throws an exception if the objectManager provided is not an instance of DocumentManager or EntityManager. If I have my own object manager that implements EntityManagerInterface this exception is hit.
I think this method should be updated to check against the interface
, so that users may implement their own entity managers if desired
Regardless of the by_value
setting, AbstractMongoStrategy::hydrateCollection
always attaches AllowRemoveByValue
strategy. When by_value
is false it should attach AllowRemoveByReference
instead.
Hydrator strategies are currently fetched only from service manager:
The factory should try to instantiate a class (if it exists) after failing to fetch it from the service manager.
Currently you have to add the strategies to "invokables" of the service manager.
any ideas why?
As Zend Framework is migrated now to Laminas and we have also released DoctrineModule 3.0 with Laminas support we need migrate also this library.
The current documentation has an example but it is unclear what the keys in the example configuration mean or how they should be used. Currently only the use_generated_hydrator
configuration key has any documentation. All other keys should be documented, too.
Perhaps a requirement for php 5.4 could be added to composer.json.
(Since short array notation is used at least once, here).
Possibly a bug, need help.
It's as if all strategies get added by reference, as such, setting the property name on the strategy, sets it for all where this same instance has been added. They all have the same object hash.
Considering the following strategy configuration for a Resource:
'doctrine-hydrator' => [
'Company\\V1\\Rest\\Company\\CompanyHydrator' => [
'entity_class' => \Namespace\Path\To\Company\Entity\Company::class,
'object_manager' => 'doctrine.entitymanager.orm_default',
'by_value' => true,
'strategies' => [
'users' => \ZF\Doctrine\Hydrator\Strategy\CollectionLink::class,
'countries' => \Application\Strategy\CollectionUniDirectionalToManyStrategy::class,
'currencies' => \Application\Strategy\CollectionUniDirectionalToManyStrategy::class,
],
],
],
To have your information complete, this is the entire custom strategy:
class CollectionUniDirectionalToManyStrategy extends AllowRemoveByValue
{
public function extract($value)
{
return new Collection($value ?: []);
}
}
So, when stepping through into the strategy during hydration for Countries, I expect to see that the collectionName
of $this
(instance of CollectionUniDirectionalToManyStrategy
), is set to countries
, however, it is set to currencies
. See the debug image below for reference.
This is due to how these are set in the DoctrineHydratorFactory
, here.
$strategy = $container->get($strategyKey);
Following that into the Zend ServiceManager
we find this (here):
// We start by checking if we have cached the requested service (this
// is the fastest method).
if (isset($this->services[$requestedName])) {
return $this->services[$requestedName];
}
This causes that the same instance is always returned. So when later the collection name is applied to 1 of the these "set" instances, it's applied to all of them. Creating the situation in the screenshot.
The bug fix:
Clone the result from the Zend ServiceManager
to prevent using cached instances.
$strategy = clone $container->get($strategyKey);
zf-apigility-doctrine, along with the rest of zfcampus, aims for php 5.3 compatibility. I'd like to edit this down but wanted to get a go-ahead from you @veewee first. Are you OK with moving this back to a 5.3 min?
This issue is just meant to brainstorm about the possibility of providing an easy mechanism to filter fields once #8 is merged.
I was thinking we could provide an abstract factory that would simplify the process of filtering fields for entities and making that easy to version as well (based on API version). The idea is as follows:
// module configuration
'doctrine-hydrator' => [
'Api\\V1\\Rest\\User\\UserHydrator' => [
// .... other config
'filters' => [
'user-filter' => [
'filter' => 'Api\\V1\\Rest\\User\\UserFilter'
],
],
],
],
The key Api\V1\Rest\User\UserFilter
can obviously have a real service behind it. But by default it would hit a new ExtractFilterFactory
abstract factory that will attempt to build any class that follows the regular expression: /^BEF.*?Filter$/
(we could change this). E.g. it would build a class named Api\V1\Rest\User\UserFilter
.
To tie things up, we would provide a BaseExtractFilter
class off which any filters can inherit. The class would implement the filter()
method and automatically exclude any fields defined in a protected property of the instanced object.
At the end of the day, from a user's point of view, it would be possible to filter extraction output by simply following the config snippet above and defining the following class:
<?php
namespace Api\V1\Rest\User;
use Phpro\DoctrineHydrationModule\Utils\BaseExtractFilter;
class UserFilter extends BaseExtractFilter {
protected $extractWhitelist = array(
'id', 'firstname', 'lastname' // all other fields will not be extracted
);
}
If this is interesting then I have almost everything ready for a PR, just need to incorporate it into your namespaces etc (its currently implemented at the project level).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.