@@ -116,6 +116,9 @@ property name (``first_name`` becomes ``FirstName``) and prefixes it with
116
116
117
117
var_dump($accessor->getValue($person, 'first_name')); // 'Wouter'
118
118
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
+
119
122
Using Hassers/Issers
120
123
~~~~~~~~~~~~~~~~~~~~
121
124
@@ -314,6 +317,9 @@ see `Enable other Features`_.
314
317
315
318
var_dump($person->getWouter()); // array(...)
316
319
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
+
317
323
Writing to Array Properties
318
324
~~~~~~~~~~~~~~~~~~~~~~~~~~~
319
325
@@ -418,8 +424,231 @@ You can also mix objects and arrays::
418
424
var_dump('Hello '.$accessor->getValue($person, 'children[0].firstName')); // 'Wouter'
419
425
// equal to $person->getChildren()[0]->firstName
420
426
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
+
421
650
Enable other Features
422
- ~~~~~~~~~~~~~~~~~~~~~
651
+ ---------------------
423
652
424
653
The :class: `Symfony\\ Component\\ PropertyAccess\\ PropertyAccessor ` can be
425
654
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)
450
679
// ...
451
680
$accessor = new PropertyAccessor(true); // this enables handling of magic __call
452
681
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);
453
725
454
726
.. _Packagist : https://packagist.org/packages/symfony/property-access
455
727
.. _The Inflector component : https://github.com/symfony/inflector
0 commit comments