Skip to content

Commit c943984

Browse files
committed
[PropertyAccess] Custom methods on property accesses
1 parent 10ea5e9 commit c943984

File tree

1 file changed

+273
-1
lines changed

1 file changed

+273
-1
lines changed

components/property_access/introduction.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

@@ -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+
309315
Checking 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

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

Comments
 (0)