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