Skip to content

Commit 94ca7d5

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

File tree

1 file changed

+274
-1
lines changed

1 file changed

+274
-1
lines changed

components/property_access.rst

Lines changed: 274 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,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

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

Comments
 (0)