@@ -116,6 +116,9 @@ property name (``first_name`` becomes ``FirstName``) and prefixes it with
116116
117117 var_dump($accessor->getValue($person, 'first_name')); // 'Wouter'
118118
119+ You can override the called getter method using metadata (i.e. annotations or
120+ configuration files). see `Custom method calls and virtual properties in a class `_
121+
119122Using Hassers/Issers
120123~~~~~~~~~~~~~~~~~~~~
121124
@@ -306,6 +309,9 @@ see `Enable other Features`_.
306309
307310 var_dump($person->getWouter()); // array(...)
308311
312+ You can override the called setter method using metadata (i.e. annotations or
313+ configuration files). see `Custom method calls and virtual properties in a class `_
314+
309315Checking Property Paths
310316-----------------------
311317
@@ -365,8 +371,224 @@ You can also mix objects and arrays::
365371 var_dump('Hello '.$accessor->getValue($person, 'children[0].firstName')); // 'Wouter'
366372 // equal to $person->getChildren()[0]->firstName
367373
374+ Custom method calls and virtual properties in a class
375+ -----------------------------------------------------
376+
377+ Sometimes you may not want the component to guess which method has to be called
378+ when reading or writing properties. This is specially interesting when property
379+ names are not in English or its singularization is not properly detected.
380+
381+ For those cases you can add metadata to the class being accessed so that the
382+ component will use a particular method as a getter, setter or even adder and remover (for collections).
383+
384+ Another interesting use of custom methods is declaring virtual properties
385+ which are not stored directly in the object.
386+
387+ There are three supported ways to state this metadata supported out-of-the-box by
388+ the component: using annotations, using YAML configuration files or using XML
389+ configuration files.
390+
391+ .. caution ::
392+
393+ When using the component as standalone the metadata feature is disabled by
394+ default. You can enable it by calling
395+ :method: `PropertyAccessorBuilder::setMetadataFactory
396+ <Symfony\\ Component\\ PropertyAccess\\ PropertyAccessorBuilder::setMetadataFactory> `
397+ see `Enable other Features `_.
398+
399+ There are four method calls that can be overriden: `getter `, `setter `, `adder ` and
400+ `remover `.
401+
402+ When using annotations you can precede a property with `@Property ` to state which
403+ method should be called when a get, set, add or remove operation is needed on the
404+ property.
405+
406+ .. configuration-block ::
407+
408+ .. code-block :: php
409+
410+ // ...
411+ use Symfony\Component\PropertyAccess\Annotation\Property;
412+
413+ class Person
414+ {
415+ /**
416+ * @Property(getter="getFullName", setter="setFullName")
417+ */
418+ private $name;
419+
420+ /**
421+ * @Property(adder="addNewChild", remover="discardChild")
422+ */
423+ private $children;
424+
425+ public function getFullName()
426+ {
427+ return $this->name;
428+ }
429+
430+ public function setFullName($fullName)
431+ {
432+ $this->name = $fullName;
433+ }
434+ }
435+
436+ .. code-block :: yaml
437+
438+ Person :
439+ name :
440+ getter : getFullName
441+ setter : setFullName
442+ children :
443+ adder : addNewChild
444+ remover : discardChild
445+
446+ .. code-block :: xml
447+
448+ <?xml version =" 1.0" ?>
449+
450+ <property-access xmlns =" http://symfony.com/schema/dic/property-access-mapping"
451+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
452+ xsi : schemaLocation =" http://symfony.com/schema/dic/property-access-mapping http://symfony.com/schema/dic/property-access-mapping/property-access-mapping-1.0.xsd" >
453+
454+ <class name =" Person" >
455+ <property name =" name" getter =" getFullName" setter =" setFullName" />
456+ <property name =" children" adder =" addNewChild" remover =" discardChild" />
457+ </class >
458+
459+ </property-access >
460+
461+ Then, using the overriden methods is automatic:
462+
463+ .. code-block :: php
464+
465+ $person = new Person();
466+
467+ $accessor->setValue($person, 'name', 'John Doe');
468+ // will call setFullName
469+
470+ var_dump('Hello '.$accesor->getValue($person, 'name'));
471+ // will return 'Hello John Doe'
472+
473+ You can also associate a particular method with an operation on a property
474+ using the `@Getter `, `@Setter `, `@Adder ` and `@Remover ` annotations. All of them
475+ take only one parameter: `property `.
476+
477+ This allows creating virtual properties that are not directly stored in the
478+ object::
479+
480+ .. configuration-block ::
481+
482+ .. code-block :: php
483+
484+ // ...
485+ use Symfony\Component\PropertyAccess\Annotation\Getter;
486+ use Symfony\Component\PropertyAccess\Annotation\Setter;
487+
488+ class Invoice
489+ {
490+ private $quantity;
491+
492+ private $pricePerUnit;
493+
494+ // Notice that there is no real "total" property
495+
496+ /**
497+ * @Getter(property="total")
498+ */
499+ public function getTotal()
500+ {
501+ return $this->quantity * $this->pricePerUnit;
502+ }
503+
504+ /**
505+ * @Setter(property="total")
506+ *
507+ * @param mixed $total
508+ */
509+ public function setTotal($total)
510+ {
511+ $this->quantity = $total / $this->pricePerUnit;
512+ }
513+ }
514+
515+ .. code-block :: yaml
516+
517+ Invoice :
518+ total :
519+ getter : getTotal
520+ setter : setTotal
521+
522+ .. code-block :: xml
523+
524+ <?xml version =" 1.0" ?>
525+
526+ <property-access xmlns =" http://symfony.com/schema/dic/property-access-mapping"
527+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
528+ xsi : schemaLocation =" http://symfony.com/schema/dic/property-access-mapping http://symfony.com/schema/dic/property-access-mapping/property-access-mapping-1.0.xsd" >
529+
530+ <class name =" Invoice" >
531+ <property name =" total" getter =" getTotal" setter =" setTotal" />
532+ </class >
533+
534+ </property-access >
535+
536+ .. code-block :: php
537+
538+ $invoice = new Invoice();
539+
540+ $accessor->setValue($invoice, 'quantity', 20);
541+ $accessor->setValue($invoice, 'pricePerUnit', 10);
542+ var_dump('Total: '.$accesor->getValue($invoice, 'total'));
543+ // will return 'Total: 200'
544+
545+ Using property metadata with Symfony
546+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
547+
548+ By default, Symfony will look for property metadata in the following places
549+ inside each bundle path:
550+
551+ - `<Bundle path>/Resources/config/property_access.xml `
552+ - `<Bundle path>/Resources/config/property_access.yml `
553+ - `<Bundle path>/Resources/config/property_access/*.xml `
554+ - `<Bundle path>/Resources/config/property_access/*.yml `
555+
556+ If you need getting metadata from annotations you must explicitly enable them:
557+
558+ .. configuration-block ::
559+
560+ .. code-block :: yaml
561+
562+ # app/config/config.yml
563+ framework :
564+ property_access : { enable_annotations: true }
565+
566+ .. code-block :: xml
567+
568+ <!-- app/config/config.xml -->
569+ <?xml version =" 1.0" encoding =" UTF-8" ?>
570+ <container xmlns =" http://symfony.com/schema/dic/services"
571+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
572+ xmlns : framework =" http://symfony.com/schema/dic/symfony"
573+ xsi : schemaLocation =" http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
574+ http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd" >
575+
576+ <framework : config >
577+ <framework : property_access enable-annotations =" true" />
578+ </framework : config >
579+ </container >
580+
581+ .. code-block :: php
582+
583+ // app/config/config.php
584+ $container->loadFromExtension('framework', array(
585+ 'property_access' => array(
586+ 'enable_annotations' => true,
587+ ),
588+ ));
589+
368590 Enable other Features
369- ~~~~~~~~~~~~~~~~~~~~~
591+ ---------------------
370592
371593The :class: `Symfony\\ Component\\ PropertyAccess\\ PropertyAccessor ` can be
372594configured to enable extra features. To do that you could use the
@@ -397,5 +619,55 @@ Or you can pass parameters directly to the constructor (not the recommended way)
397619 // ...
398620 $accessor = new PropertyAccessor(true); // this enables handling of magic __call
399621
622+ If you need to enable metadata processing (see
623+ `Custom method calls and virtual properties in a class `_) you must instantiate
624+ a :class: `Symfony\\ Componente\\ PropertyAcces\\ Mapping\\ Factory\\ MetadataFactoryInterface `
625+ and use the method `setMetadataFactory ` on the
626+ :class: `Symfony\\ Component\\ PropertyAccess\\ PropertyAccessorBuilder `. Bundled with
627+ the component you can find
628+ a `MetadataFactory ` class that supports different kind of loaders (annotations,
629+ YAML and YML files) called :class: `
630+ Symfony\\ Componente\\ PropertyAcces\\ Mapping\\ Factory\\ LazyLoadingMetadataFactory `.
631+
632+ Its constructor needs a :class: `
633+ Symfony\\ Component\\ PropertyAccess\\ Mapping\\ Loader\\ LoaderInterface ` which specifies
634+ the source of the metadata information. You can also use a PSR6 compliant cache
635+ as the second parameter passing a :class: `Psr\\ Cache\\ CacheItemPoolInterface `
636+ reference.
637+
638+ .. code-block :: php
639+
640+ use Doctrine\Common\Annotations\AnnotationReader;
641+ use Symfony\Component\PropertyAccess\Mapping\Factory\LazyLoadingMetadataFactory;
642+ use Symfony\Component\PropertyAccess\Mapping\Loader\AnnotationLoader;
643+ use Symfony\Component\PropertyAccess\Mapping\Loader\LoaderChain;
644+ use Symfony\Component\PropertyAccess\Mapping\Loader\XMLFileLoader;
645+ use Symfony\Component\PropertyAccess\Mapping\Loader\YamlFileLoader;
646+
647+ // ...
648+
649+ $accessorBuilder = PropertyAccess::createPropertyAccessorBuilder();
650+
651+ // Create annotation loader using Doctrine annotation reader
652+ $loader = new AnnotationLoader(new AnnotationReader());
653+
654+ // or read metadata from a XML file
655+ $loader = new XmlFileLoader('metadata.xml');
656+
657+ // or read metadata from a YAML file
658+ $loader = new YamlFileLoader('metadata.yml');
659+
660+ // or combine several loaders in one
661+ $loader = new LoaderChain(
662+ new AnnotationLoader(new AnnotationReader()),
663+ new XmlFileLoader('metadata.xml'),
664+ new YamlFileLoader('metadata.yml'),
665+ new YamlFileLoader('metadata2.yml')
666+ );
667+
668+ // Enable metadata loading
669+ $metadataFactory = new LazyLoadingMetadataFactory($loader);
670+
671+ $accessorBuilder->setMetadataFactory($metadataFactory);
400672
401673 .. _Packagist : https://packagist.org/packages/symfony/property-access
0 commit comments