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