Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c943984

Browse filesBrowse files
committed
[PropertyAccess] Custom methods on property accesses
1 parent 10ea5e9 commit c943984
Copy full SHA for c943984

File tree

1 file changed

+273
-1
lines changed
Filter options

1 file changed

+273
-1
lines changed

‎components/property_access/introduction.rst

Copy file name to clipboardExpand all lines: components/property_access/introduction.rst
+273-1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.