Skip to content

Commit 5f4e4b6

Browse files
committed
[PropertyAccess] Custom methods on property accesses
1 parent 7bb20ba commit 5f4e4b6

File tree

1 file changed

+273
-1
lines changed

1 file changed

+273
-1
lines changed

components/property_access.rst

Lines changed: 273 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
119122
Using 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+
317323
Writing 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

424653
The :class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` can be
425654
configured 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

Comments
 (0)