@@ -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
@@ -314,6 +317,9 @@ see `Enable other Features`_.
314317
315318 var_dump($person->getWouter()); // array(...)
316319
320+ You can override the called setter method using metadata (i.e. annotations or
321+ configuration files). see `Custom method calls and virtual properties in a class `_
322+
317323Writing to Array Properties
318324~~~~~~~~~~~~~~~~~~~~~~~~~~~
319325
@@ -418,8 +424,225 @@ You can also mix objects and arrays::
418424 var_dump('Hello '.$accessor->getValue($person, 'children[0].firstName')); // 'Wouter'
419425 // equal to $person->getChildren()[0]->firstName
420426
427+ Custom method calls and virtual properties in a class
428+ -----------------------------------------------------
429+
430+ Sometimes you may not want the component to guess which method has to be called
431+ when reading or writing properties. This is specially interesting when property
432+ names are not in English or its singularization is not properly detected.
433+
434+ For those cases you can add metadata to the class being accessed so that the
435+ component will use a particular method as a getter, setter or even adder and
436+ remover (for collections).
437+
438+ Another interesting use of custom methods is declaring virtual properties
439+ which are not stored directly in the object.
440+
441+ There are three supported ways to state this metadata supported out-of-the-box by
442+ the component: using annotations, using YAML configuration files or using XML
443+ configuration files.
444+
445+ .. caution ::
446+
447+ When using as a standalone component the metadata feature is disabled by
448+ default. You can enable it by calling
449+ :method: `PropertyAccessorBuilder::setMetadataFactory
450+ <Symfony\\ Component\\ PropertyAccess\\ PropertyAccessorBuilder::setMetadataFactory> `
451+ see `Enable other Features `_.
452+
453+ There are four method calls that can be overriden: `getter `, `setter `, `adder ` and
454+ `remover `.
455+
456+ When using annotations you can precede a property with `@Property ` to state which
457+ method should be called when a get, set, add or remove operation is needed on the
458+ property.
459+
460+ .. configuration-block ::
461+
462+ .. code-block :: php
463+
464+ // ...
465+ use Symfony\Component\PropertyAccess\Annotation\Property;
466+
467+ class Person
468+ {
469+ /**
470+ * @Property(getter="getFullName", setter="setFullName")
471+ */
472+ private $name;
473+
474+ /**
475+ * @Property(adder="addNewChild", remover="discardChild")
476+ */
477+ private $children;
478+
479+ public function getFullName()
480+ {
481+ return $this->name;
482+ }
483+
484+ public function setFullName($fullName)
485+ {
486+ $this->name = $fullName;
487+ }
488+ }
489+
490+ .. code-block :: yaml
491+
492+ Person :
493+ name :
494+ getter : getFullName
495+ setter : setFullName
496+ children :
497+ adder : addNewChild
498+ remover : discardChild
499+
500+ .. code-block :: xml
501+
502+ <?xml version =" 1.0" ?>
503+
504+ <property-access xmlns =" http://symfony.com/schema/dic/property-access-mapping"
505+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
506+ xsi : schemaLocation =" http://symfony.com/schema/dic/property-access-mapping http://symfony.com/schema/dic/property-access-mapping/property-access-mapping-1.0.xsd" >
507+
508+ <class name =" Person" >
509+ <property name =" name" getter =" getFullName" setter =" setFullName" />
510+ <property name =" children" adder =" addNewChild" remover =" discardChild" />
511+ </class >
512+
513+ </property-access >
514+
515+ Then, using the overriden methods is automatic:
516+
517+ .. code-block :: php
518+
519+ $person = new Person();
520+
521+ $accessor->setValue($person, 'name', 'John Doe');
522+ // will call setFullName
523+
524+ var_dump('Hello '.$accesor->getValue($person, 'name'));
525+ // will return 'Hello John Doe'
526+
527+ You can also associate a particular method with an operation on a property
528+ using the `@Getter `, `@Setter `, `@Adder ` and `@Remover ` annotations. All of them
529+ take only one parameter: `property `.
530+
531+ This allows creating virtual properties that are not directly stored in the
532+ object::
533+
534+ .. configuration-block ::
535+
536+ .. code-block :: php
537+
538+ // ...
539+ use Symfony\Component\PropertyAccess\Annotation\Getter;
540+ use Symfony\Component\PropertyAccess\Annotation\Setter;
541+
542+ class Invoice
543+ {
544+ private $quantity;
545+
546+ private $pricePerUnit;
547+
548+ // Notice that there is no real "total" property
549+
550+ /**
551+ * @Getter(property="total")
552+ */
553+ public function getTotal()
554+ {
555+ return $this->quantity * $this->pricePerUnit;
556+ }
557+
558+ /**
559+ * @Setter(property="total")
560+ *
561+ * @param mixed $total
562+ */
563+ public function setTotal($total)
564+ {
565+ $this->quantity = $total / $this->pricePerUnit;
566+ }
567+ }
568+
569+ .. code-block :: yaml
570+
571+ Invoice :
572+ total :
573+ getter : getTotal
574+ setter : setTotal
575+
576+ .. code-block :: xml
577+
578+ <?xml version =" 1.0" ?>
579+
580+ <property-access xmlns =" http://symfony.com/schema/dic/property-access-mapping"
581+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
582+ xsi : schemaLocation =" http://symfony.com/schema/dic/property-access-mapping http://symfony.com/schema/dic/property-access-mapping/property-access-mapping-1.0.xsd" >
583+
584+ <class name =" Invoice" >
585+ <property name =" total" getter =" getTotal" setter =" setTotal" />
586+ </class >
587+
588+ </property-access >
589+
590+ .. code-block :: php
591+
592+ $invoice = new Invoice();
593+
594+ $accessor->setValue($invoice, 'quantity', 20);
595+ $accessor->setValue($invoice, 'pricePerUnit', 10);
596+ var_dump('Total: '.$accesor->getValue($invoice, 'total'));
597+ // will return 'Total: 200'
598+
599+ Using property metadata with Symfony
600+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
601+
602+ By default, Symfony will look for property metadata in the following places
603+ inside each bundle path:
604+
605+ - `<Bundle path>/Resources/config/property_accessor.xml `
606+ - `<Bundle path>/Resources/config/property_accessor.yml `
607+ - `<Bundle path>/Resources/config/property_accessor/*.xml `
608+ - `<Bundle path>/Resources/config/property_accessor/*.yml `
609+
610+ If you need getting metadata from annotations you must explicitly enable them:
611+
612+ .. configuration-block ::
613+
614+ .. code-block :: yaml
615+
616+ # app/config/config.yml
617+ framework :
618+ property_access : { enable_annotations: true }
619+
620+ .. code-block :: xml
621+
622+ <!-- app/config/config.xml -->
623+ <?xml version =" 1.0" encoding =" UTF-8" ?>
624+ <container xmlns =" http://symfony.com/schema/dic/services"
625+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
626+ xmlns : framework =" http://symfony.com/schema/dic/symfony"
627+ xsi : schemaLocation =" http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
628+ http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd" >
629+
630+ <framework : config >
631+ <framework : property_access enable-annotations =" true" />
632+ </framework : config >
633+ </container >
634+
635+ .. code-block :: php
636+
637+ // app/config/config.php
638+ $container->loadFromExtension('framework', array(
639+ 'property_access' => array(
640+ 'enable_annotations' => true,
641+ ),
642+ ));
643+
421644 Enable other Features
422- ~~~~~~~~~~~~~~~~~~~~~
645+ ---------------------
423646
424647The :class: `Symfony\\ Component\\ PropertyAccess\\ PropertyAccessor ` can be
425648configured to enable extra features. To do that you could use the
@@ -450,6 +673,56 @@ Or you can pass parameters directly to the constructor (not the recommended way)
450673 // ...
451674 $accessor = new PropertyAccessor(true); // this enables handling of magic __call
452675
676+ If you need to enable metadata processing (see
677+ `Custom method calls and virtual properties in a class `_) you must instantiate
678+ a :class: `Symfony\\ Componente\\ PropertyAcces\\ Mapping\\ Factory\\ MetadataFactoryInterface `
679+ and use the method `setMetadataFactory ` on the
680+ :class: `Symfony\\ Component\\ PropertyAccess\\ PropertyAccessorBuilder `. Bundled with
681+ the component you can find
682+ a `MetadataFactory ` class that supports different kind of loaders (annotations,
683+ YAML and YML files) called :class: `
684+ Symfony\\ Componente\\ PropertyAcces\\ Mapping\\ Factory\\ LazyLoadingMetadataFactory `.
685+
686+ Its constructor needs a :class: `
687+ Symfony\\ Component\\ PropertyAccess\\ Mapping\\ Loader\\ LoaderInterface ` which specifies
688+ the source of the metadata information. You can also use a PSR6 compliant cache
689+ as the second parameter passing a :class: `Psr\\ Cache\\ CacheItemPoolInterface `
690+ reference.
691+
692+ .. code-block :: php
693+
694+ use Doctrine\Common\Annotations\AnnotationReader;
695+ use Symfony\Component\PropertyAccess\Mapping\Factory\LazyLoadingMetadataFactory;
696+ use Symfony\Component\PropertyAccess\Mapping\Loader\AnnotationLoader;
697+ use Symfony\Component\PropertyAccess\Mapping\Loader\LoaderChain;
698+ use Symfony\Component\PropertyAccess\Mapping\Loader\XMLFileLoader;
699+ use Symfony\Component\PropertyAccess\Mapping\Loader\YamlFileLoader;
700+
701+ // ...
702+
703+ $accessorBuilder = PropertyAccess::createPropertyAccessorBuilder();
704+
705+ // Create annotation loader using Doctrine annotation reader
706+ $loader = new AnnotationLoader(new AnnotationReader());
707+
708+ // or read metadata from a XML file
709+ $loader = new XmlFileLoader('metadata.xml');
710+
711+ // or read metadata from a YAML file
712+ $loader = new YamlFileLoader('metadata.yml');
713+
714+ // or combine several loaders in one
715+ $loader = new LoaderChain(
716+ new AnnotationLoader(new AnnotationReader()),
717+ new XmlFileLoader('metadata.xml'),
718+ new YamlFileLoader('metadata.yml'),
719+ new YamlFileLoader('metadata2.yml')
720+ );
721+
722+ // Enable metadata loading
723+ $metadataFactory = new LazyLoadingMetadataFactory($loader);
724+
725+ $accessorBuilder->setMetadataFactory($metadataFactory);
453726
454727 .. _Packagist : https://packagist.org/packages/symfony/property-access
455728.. _The Inflector component : https://github.com/symfony/inflector
0 commit comments