partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit 95e518c03cfe58f3996b672b13a20bed00a5cceb
parent 7ea6026b9750726a32b727352b41bf8baaa6ce58
Author: Felicia Hummel <felicitus@felicitus.org>
Date:   Wed, 17 Aug 2016 00:10:51 +0200

Merge pull request #715 from partkeepr/PartKeepr-685

Added an option to let associations be passed by reference and not by…
Diffstat:
Asrc/PartKeepr/DoctrineReflectionBundle/Annotation/ByReference.php | 15+++++++++++++++
Msrc/PartKeepr/DoctrineReflectionBundle/Resources/views/model.js.twig | 1+
Msrc/PartKeepr/DoctrineReflectionBundle/Services/ReflectionService.php | 44++++++++++++++++++++++++++++++++++++++------
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraField.js | 20++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js | 129++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Util/JsonWithAssociationsWriter.js | 31++++++++++++++-----------------
Msrc/PartKeepr/ProjectBundle/Entity/ProjectPart.php | 2++
7 files changed, 215 insertions(+), 27 deletions(-)

diff --git a/src/PartKeepr/DoctrineReflectionBundle/Annotation/ByReference.php b/src/PartKeepr/DoctrineReflectionBundle/Annotation/ByReference.php @@ -0,0 +1,15 @@ +<?php + +namespace PartKeepr\DoctrineReflectionBundle\Annotation; + +use Doctrine\ORM\Mapping\Annotation; + +/** + * Tells the system to pass the association by reference and not by value + * + * @Annotation + * @Target("PROPERTY") + */ +final class ByReference implements Annotation +{ +} diff --git a/src/PartKeepr/DoctrineReflectionBundle/Resources/views/model.js.twig b/src/PartKeepr/DoctrineReflectionBundle/Resources/views/model.js.twig @@ -13,6 +13,7 @@ Ext.define('{{ className }}', { {% for association in associations.MANY_TO_ONE %} { name: '{{ association.name }}', reference: '{{ association.target }}' + {% if association.byReference %},byReference: true{% endif %} }{% if not loop.last %},{% endif %} {% endfor %} diff --git a/src/PartKeepr/DoctrineReflectionBundle/Services/ReflectionService.php b/src/PartKeepr/DoctrineReflectionBundle/Services/ReflectionService.php @@ -74,10 +74,10 @@ class ReflectionService $associationMappings = $this->getDatabaseAssociationMappings($cm, $bTree); $renderParams = [ - 'fields' => $fieldMappings, + 'fields' => $fieldMappings, 'associations' => $associationMappings, - 'className' => $this->convertPHPToExtJSClassName($entity), - 'parentClass' => $parentClass, + 'className' => $this->convertPHPToExtJSClassName($entity), + 'parentClass' => $parentClass, ]; $targetService = $this->reader->getClassAnnotation( @@ -114,6 +114,7 @@ class ReflectionService protected function getDatabaseAssociationMappings(ClassMetadata $cm, $bTree = false) { $associations = $cm->getAssociationMappings(); + $byReferenceMappings = $this->getByReferenceMappings($cm); $associationMappings = []; @@ -154,10 +155,16 @@ class ReflectionService // The self-referencing association may not be written for trees, because ExtJS can't load all nodes // in one go. if (!($bTree && $association['targetEntity'] == $cm->getName())) { + $byReference = false; + + if (in_array($association['fieldName'], $byReferenceMappings)) { + $byReference = true; + } $associationMappings[$associationType][] = [ - 'name' => $association['fieldName'], - 'target' => $this->convertPHPToExtJSClassName($association['targetEntity']), - 'getter' => $getter, + 'name' => $association['fieldName'], + 'target' => $this->convertPHPToExtJSClassName($association['targetEntity']), + 'byReference' => $byReference, + 'getter' => $getter, 'getterField' => $getterField, ]; } @@ -195,6 +202,31 @@ class ReflectionService } /** + * Returns all by-reference associations. + * + * @param ClassMetadata $cm + * + * @return array + */ + protected function getByReferenceMappings(ClassMetadata $cm) + { + $byReferenceMappings = []; + + foreach ($cm->getReflectionClass()->getProperties() as $property) { + $byReferenceAnnotation = $this->reader->getPropertyAnnotation( + $property, + 'PartKeepr\DoctrineReflectionBundle\Annotation\ByReference' + ); + + if ($byReferenceAnnotation !== null) { + $byReferenceMappings[] = $property->getName(); + } + } + + return $byReferenceMappings; + } + + /** * Returns database field mappings. * * @param ClassMetadata $cm diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraField.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraField.js @@ -0,0 +1,20 @@ +/** + * Adds the config field "byReference" to a field. + * + * byReference tells the system not to serialize the whole item but only its reference. + */ +Ext.define('PartKeepr.data.HydraField', { + override: "Ext.data.field.Field", + + byReference: false, + + constructor: function (config) + { + if (config.byReference) { + this.byReference = config.byReference; + } else { + this.byReference = false; + } + this.callParent(arguments); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js @@ -3,7 +3,7 @@ Ext.define("PartKeepr.data.HydraModel", { mixins: ['PartKeepr.data.CallActions'], - getData: function (options) + getData: function () { var data = this.callParent(arguments); @@ -13,7 +13,8 @@ Ext.define("PartKeepr.data.HydraModel", { return data; }, - get: function (fieldName) { + get: function (fieldName) + { var ret, role, item; ret = this.callParent(arguments); @@ -22,7 +23,7 @@ Ext.define("PartKeepr.data.HydraModel", { // The field is undefined, attempt to retrieve data via associations var parts = fieldName.split("."); - if (parts.length < 2) { + if (parts.length < 2) { return ret; } @@ -39,6 +40,126 @@ Ext.define("PartKeepr.data.HydraModel", { return ret; }, /** + * Gets all of the data from this Models *loaded* associations. It does this + * recursively. For example if we have a User which hasMany Orders, and each Order + * hasMany OrderItems, it will return an object like this: + * + * { + * orders: [ + * { + * id: 123, + * status: 'shipped', + * orderItems: [ + * ... + * ] + * } + * ] + * } + * + * @param {Object} [result] The object on to which the associations will be added. If + * no object is passed one is created. This object is then returned. + * @param {Boolean/Object} [options] An object containing options describing the data + * desired. + * @param {Boolean} [options.associated=true] Pass `true` to include associated data from + * other associated records. + * @param {Boolean} [options.changes=false] Pass `true` to only include fields that + * have been modified. Note that field modifications are only tracked for fields that + * are not declared with `persist` set to `false`. In other words, only persistent + * fields have changes tracked so passing `true` for this means `options.persist` is + * redundant. + * @param {Boolean} [options.critical] Pass `true` to include fields set as `critical`. + * This is only meaningful when `options.changes` is `true` since critical fields may + * not have been modified. + * @param {Boolean} [options.persist] Pass `true` to only return persistent fields. + * This is implied when `options.changes` is set to `true`. + * @param {Boolean} [options.serialize=false] Pass `true` to invoke the `serialize` + * method on the returned fields. + * @return {Object} The nested data set for the Model's loaded associations. + */ + getAssociatedData: function (result, options) + { + var me = this, + associations = me.associations, + deep, i, item, items, itemData, length, + record, role, roleName, opts, clear, associated; + + result = result || {}; + + me.$gathering = 1; + + if (options) { + options = Ext.Object.chain(options); + } + + for (roleName in associations) { + role = associations[roleName]; + + item = role.getAssociatedItem(me); + if (!item || item.$gathering) { + continue; + } + + if (item.isStore) { + item.$gathering = 1; + + items = item.getData().items; // get the records for the store + length = items.length; + itemData = []; + + for (i = 0; i < length; ++i) { + // NOTE - we don't check whether the record is gathering here because + // we cannot remove it from the store (it would invalidate the index + // values and misrepresent the content). Instead we tell getData to + // only get the fields vs descend further. + record = items[i]; + deep = !record.$gathering; + record.$gathering = 1; + if (options) { + associated = options.associated; + if (associated === undefined) { + options.associated = deep; + clear = true; + } else { + if (!deep) { + options.associated = false; + clear = true; + } + } + opts = options; + } else { + opts = deep ? me._getAssociatedOptions : me._getNotAssociatedOptions; + } + itemData.push(record.getData(opts)); + if (clear) { + options.associated = associated; + clear = false; + } + delete record.$gathering; + } + + delete item.$gathering; + } else { + opts = options || me._getAssociatedOptions; + if (options && options.associated === undefined) { + opts.associated = true; + } + + if (this.getField(roleName) !== null && this.getField(roleName).byReference) { + itemData = item.getId(); + } else { + itemData = item.getData(opts); + } + } + + result[roleName] = itemData; + } + + delete me.$gathering; + + return result; + }, + + /** * Returns data from all associations * * @return {Object} An object containing the associations as properties @@ -82,7 +203,7 @@ Ext.define("PartKeepr.data.HydraModel", { getterName = this.associations[roleName].getterName; store = this[getterName](); - for (var i=0;i<data[roleName].length;i++) { + for (var i = 0; i < data[roleName].length; i++) { // Delete existing IDs for duplicated data if (data[roleName][i].isEntity) { idProperty = data[roleName][i].idProperty; diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Util/JsonWithAssociationsWriter.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Util/JsonWithAssociationsWriter.js @@ -1,21 +1,19 @@ Ext.define("PartKeepr.JsonWithAssociations", { - extend: 'Ext.data.writer.Json', - alias: 'writer.jsonwithassociations', + extend: 'Ext.data.writer.Json', + alias: 'writer.jsonwithassociations', - /** - * @cfg {Array} associations Which associations to include. - */ - associations: [], - writeRecordId: false, + /** + * @cfg {Array} associations Which associations to include. + */ + associations: [], + writeRecordId: false, - getRecordData: function(record) { - var me = this, i, key, subStore, - data = me.callParent(arguments); + getRecordData: function (record) + { + var data = this.callParent(arguments); - var storeName; - - Ext.apply(data, record.getAssociatedData()); + Ext.apply(data, record.getAssociatedData()); - return data; - } -});- \ No newline at end of file + return data; + } +}); diff --git a/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php b/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php @@ -4,6 +4,7 @@ namespace PartKeepr\ProjectBundle\Entity; use Doctrine\ORM\Mapping as ORM; use PartKeepr\CoreBundle\Entity\BaseEntity; +use PartKeepr\DoctrineReflectionBundle\Annotation\ByReference; use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; use PartKeepr\PartBundle\Entity\Part; use Symfony\Component\Serializer\Annotation\Groups; @@ -21,6 +22,7 @@ class ProjectPart extends BaseEntity * * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part", inversedBy="projectParts") * @Groups({"default"}) + * @ByReference * * @var Part */