1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Serializer\Tests\Normalizer; 13 14use Doctrine\Common\Annotations\AnnotationReader; 15use PHPUnit\Framework\TestCase; 16use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; 17use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; 18use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; 19use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; 20use Symfony\Component\Serializer\Normalizer\NormalizerInterface; 21use Symfony\Component\Serializer\Serializer; 22use Symfony\Component\Serializer\SerializerInterface; 23use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; 24use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy; 25use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy; 26use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; 27 28class GetSetMethodNormalizerTest extends TestCase 29{ 30 /** 31 * @var GetSetMethodNormalizer 32 */ 33 private $normalizer; 34 /** 35 * @var SerializerInterface 36 */ 37 private $serializer; 38 39 protected function setUp() 40 { 41 $this->serializer = $this->getMockBuilder(SerializerNormalizer::class)->getMock(); 42 $this->normalizer = new GetSetMethodNormalizer(); 43 $this->normalizer->setSerializer($this->serializer); 44 } 45 46 public function testInterface() 47 { 48 $this->assertInstanceOf('Symfony\Component\Serializer\Normalizer\NormalizerInterface', $this->normalizer); 49 $this->assertInstanceOf('Symfony\Component\Serializer\Normalizer\DenormalizerInterface', $this->normalizer); 50 } 51 52 public function testNormalize() 53 { 54 $obj = new GetSetDummy(); 55 $object = new \stdClass(); 56 $obj->setFoo('foo'); 57 $obj->setBar('bar'); 58 $obj->setBaz(true); 59 $obj->setCamelCase('camelcase'); 60 $obj->setObject($object); 61 62 $this->serializer 63 ->expects($this->once()) 64 ->method('normalize') 65 ->with($object, 'any') 66 ->willReturn('string_object') 67 ; 68 69 $this->assertEquals( 70 [ 71 'foo' => 'foo', 72 'bar' => 'bar', 73 'baz' => true, 74 'fooBar' => 'foobar', 75 'camelCase' => 'camelcase', 76 'object' => 'string_object', 77 ], 78 $this->normalizer->normalize($obj, 'any') 79 ); 80 } 81 82 public function testDenormalize() 83 { 84 $obj = $this->normalizer->denormalize( 85 ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], 86 GetSetDummy::class, 87 'any' 88 ); 89 $this->assertEquals('foo', $obj->getFoo()); 90 $this->assertEquals('bar', $obj->getBar()); 91 $this->assertTrue($obj->isBaz()); 92 } 93 94 public function testIgnoredAttributesInContext() 95 { 96 $ignoredAttributes = ['foo', 'bar', 'baz', 'object']; 97 $this->normalizer->setIgnoredAttributes($ignoredAttributes); 98 $obj = new GetSetDummy(); 99 $obj->setFoo('foo'); 100 $obj->setBar('bar'); 101 $obj->setCamelCase(true); 102 $this->assertEquals( 103 [ 104 'fooBar' => 'foobar', 105 'camelCase' => true, 106 ], 107 $this->normalizer->normalize($obj, 'any') 108 ); 109 } 110 111 public function testDenormalizeWithObject() 112 { 113 $data = new \stdClass(); 114 $data->foo = 'foo'; 115 $data->bar = 'bar'; 116 $data->fooBar = 'foobar'; 117 $obj = $this->normalizer->denormalize($data, GetSetDummy::class, 'any'); 118 $this->assertEquals('foo', $obj->getFoo()); 119 $this->assertEquals('bar', $obj->getBar()); 120 } 121 122 public function testDenormalizeNull() 123 { 124 $this->assertEquals(new GetSetDummy(), $this->normalizer->denormalize(null, GetSetDummy::class)); 125 } 126 127 public function testConstructorDenormalize() 128 { 129 $obj = $this->normalizer->denormalize( 130 ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], 131 GetConstructorDummy::class, 'any'); 132 $this->assertEquals('foo', $obj->getFoo()); 133 $this->assertEquals('bar', $obj->getBar()); 134 $this->assertTrue($obj->isBaz()); 135 } 136 137 public function testConstructorDenormalizeWithNullArgument() 138 { 139 $obj = $this->normalizer->denormalize( 140 ['foo' => 'foo', 'bar' => null, 'baz' => true], 141 GetConstructorDummy::class, 'any'); 142 $this->assertEquals('foo', $obj->getFoo()); 143 $this->assertNull($obj->getBar()); 144 $this->assertTrue($obj->isBaz()); 145 } 146 147 public function testConstructorDenormalizeWithMissingOptionalArgument() 148 { 149 $obj = $this->normalizer->denormalize( 150 ['foo' => 'test', 'baz' => [1, 2, 3]], 151 GetConstructorOptionalArgsDummy::class, 'any'); 152 $this->assertEquals('test', $obj->getFoo()); 153 $this->assertEquals([], $obj->getBar()); 154 $this->assertEquals([1, 2, 3], $obj->getBaz()); 155 } 156 157 public function testConstructorDenormalizeWithOptionalDefaultArgument() 158 { 159 $obj = $this->normalizer->denormalize( 160 ['bar' => 'test'], 161 GetConstructorArgsWithDefaultValueDummy::class, 'any'); 162 $this->assertEquals([], $obj->getFoo()); 163 $this->assertEquals('test', $obj->getBar()); 164 } 165 166 /** 167 * @requires PHP 5.6 168 */ 169 public function testConstructorDenormalizeWithVariadicArgument() 170 { 171 $obj = $this->normalizer->denormalize( 172 ['foo' => [1, 2, 3]], 173 'Symfony\Component\Serializer\Tests\Fixtures\VariadicConstructorArgsDummy', 'any'); 174 $this->assertEquals([1, 2, 3], $obj->getFoo()); 175 } 176 177 /** 178 * @requires PHP 5.6 179 */ 180 public function testConstructorDenormalizeWithMissingVariadicArgument() 181 { 182 $obj = $this->normalizer->denormalize( 183 [], 184 'Symfony\Component\Serializer\Tests\Fixtures\VariadicConstructorArgsDummy', 'any'); 185 $this->assertEquals([], $obj->getFoo()); 186 } 187 188 public function testConstructorWithObjectDenormalize() 189 { 190 $data = new \stdClass(); 191 $data->foo = 'foo'; 192 $data->bar = 'bar'; 193 $data->baz = true; 194 $data->fooBar = 'foobar'; 195 $obj = $this->normalizer->denormalize($data, GetConstructorDummy::class, 'any'); 196 $this->assertEquals('foo', $obj->getFoo()); 197 $this->assertEquals('bar', $obj->getBar()); 198 } 199 200 public function testConstructorWArgWithPrivateMutator() 201 { 202 $obj = $this->normalizer->denormalize(['foo' => 'bar'], ObjectConstructorArgsWithPrivateMutatorDummy::class, 'any'); 203 $this->assertEquals('bar', $obj->getFoo()); 204 } 205 206 public function testGroupsNormalize() 207 { 208 $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); 209 $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory); 210 $this->normalizer->setSerializer($this->serializer); 211 212 $obj = new GroupDummy(); 213 $obj->setFoo('foo'); 214 $obj->setBar('bar'); 215 $obj->setFooBar('fooBar'); 216 $obj->setSymfony('symfony'); 217 $obj->setKevin('kevin'); 218 $obj->setCoopTilleuls('coopTilleuls'); 219 220 $this->assertEquals([ 221 'bar' => 'bar', 222 ], $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['c']])); 223 224 $this->assertEquals([ 225 'symfony' => 'symfony', 226 'foo' => 'foo', 227 'fooBar' => 'fooBar', 228 'bar' => 'bar', 229 'kevin' => 'kevin', 230 'coopTilleuls' => 'coopTilleuls', 231 ], $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['a', 'c']])); 232 } 233 234 public function testGroupsDenormalize() 235 { 236 $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); 237 $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory); 238 $this->normalizer->setSerializer($this->serializer); 239 240 $obj = new GroupDummy(); 241 $obj->setFoo('foo'); 242 243 $toNormalize = ['foo' => 'foo', 'bar' => 'bar']; 244 245 $normalized = $this->normalizer->denormalize( 246 $toNormalize, 247 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', 248 null, 249 [GetSetMethodNormalizer::GROUPS => ['a']] 250 ); 251 $this->assertEquals($obj, $normalized); 252 253 $obj->setBar('bar'); 254 255 $normalized = $this->normalizer->denormalize( 256 $toNormalize, 257 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', 258 null, 259 [GetSetMethodNormalizer::GROUPS => ['a', 'b']] 260 ); 261 $this->assertEquals($obj, $normalized); 262 } 263 264 public function testGroupsNormalizeWithNameConverter() 265 { 266 $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); 267 $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); 268 $this->normalizer->setSerializer($this->serializer); 269 270 $obj = new GroupDummy(); 271 $obj->setFooBar('@dunglas'); 272 $obj->setSymfony('@coopTilleuls'); 273 $obj->setCoopTilleuls('les-tilleuls.coop'); 274 275 $this->assertEquals( 276 [ 277 'bar' => null, 278 'foo_bar' => '@dunglas', 279 'symfony' => '@coopTilleuls', 280 ], 281 $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) 282 ); 283 } 284 285 public function testGroupsDenormalizeWithNameConverter() 286 { 287 $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); 288 $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); 289 $this->normalizer->setSerializer($this->serializer); 290 291 $obj = new GroupDummy(); 292 $obj->setFooBar('@dunglas'); 293 $obj->setSymfony('@coopTilleuls'); 294 295 $this->assertEquals( 296 $obj, 297 $this->normalizer->denormalize([ 298 'bar' => null, 299 'foo_bar' => '@dunglas', 300 'symfony' => '@coopTilleuls', 301 'coop_tilleuls' => 'les-tilleuls.coop', 302 ], 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) 303 ); 304 } 305 306 /** 307 * @dataProvider provideCallbacks 308 */ 309 public function testCallbacks($callbacks, $value, $result, $message) 310 { 311 $this->normalizer->setCallbacks($callbacks); 312 313 $obj = new GetConstructorDummy('', $value, true); 314 315 $this->assertEquals( 316 $result, 317 $this->normalizer->normalize($obj, 'any'), 318 $message 319 ); 320 } 321 322 public function testUncallableCallbacks() 323 { 324 $this->expectException('InvalidArgumentException'); 325 $this->normalizer->setCallbacks(['bar' => null]); 326 327 $obj = new GetConstructorDummy('baz', 'quux', true); 328 329 $this->normalizer->normalize($obj, 'any'); 330 } 331 332 public function testIgnoredAttributes() 333 { 334 $this->normalizer->setIgnoredAttributes(['foo', 'bar', 'baz', 'camelCase', 'object']); 335 336 $obj = new GetSetDummy(); 337 $obj->setFoo('foo'); 338 $obj->setBar('bar'); 339 $obj->setBaz(true); 340 341 $this->assertEquals( 342 ['fooBar' => 'foobar'], 343 $this->normalizer->normalize($obj, 'any') 344 ); 345 } 346 347 public function provideCallbacks() 348 { 349 return [ 350 [ 351 [ 352 'bar' => function ($bar) { 353 return 'baz'; 354 }, 355 ], 356 'baz', 357 ['foo' => '', 'bar' => 'baz', 'baz' => true], 358 'Change a string', 359 ], 360 [ 361 [ 362 'bar' => function ($bar) { 363 }, 364 ], 365 'baz', 366 ['foo' => '', 'bar' => null, 'baz' => true], 367 'Null an item', 368 ], 369 [ 370 [ 371 'bar' => function ($bar) { 372 return $bar->format('d-m-Y H:i:s'); 373 }, 374 ], 375 new \DateTime('2011-09-10 06:30:00'), 376 ['foo' => '', 'bar' => '10-09-2011 06:30:00', 'baz' => true], 377 'Format a date', 378 ], 379 [ 380 [ 381 'bar' => function ($bars) { 382 $foos = ''; 383 foreach ($bars as $bar) { 384 $foos .= $bar->getFoo(); 385 } 386 387 return $foos; 388 }, 389 ], 390 [new GetConstructorDummy('baz', '', false), new GetConstructorDummy('quux', '', false)], 391 ['foo' => '', 'bar' => 'bazquux', 'baz' => true], 392 'Collect a property', 393 ], 394 [ 395 [ 396 'bar' => function ($bars) { 397 return \count($bars); 398 }, 399 ], 400 [new GetConstructorDummy('baz', '', false), new GetConstructorDummy('quux', '', false)], 401 ['foo' => '', 'bar' => 2, 'baz' => true], 402 'Count a property', 403 ], 404 ]; 405 } 406 407 public function testUnableToNormalizeObjectAttribute() 408 { 409 $this->expectException('Symfony\Component\Serializer\Exception\LogicException'); 410 $this->expectExceptionMessage('Cannot normalize attribute "object" because the injected serializer is not a normalizer'); 411 $serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock(); 412 $this->normalizer->setSerializer($serializer); 413 414 $obj = new GetSetDummy(); 415 $object = new \stdClass(); 416 $obj->setObject($object); 417 418 $this->normalizer->normalize($obj, 'any'); 419 } 420 421 public function testUnableToNormalizeCircularReference() 422 { 423 $this->expectException('Symfony\Component\Serializer\Exception\CircularReferenceException'); 424 $serializer = new Serializer([$this->normalizer]); 425 $this->normalizer->setSerializer($serializer); 426 $this->normalizer->setCircularReferenceLimit(2); 427 428 $obj = new CircularReferenceDummy(); 429 430 $this->normalizer->normalize($obj); 431 } 432 433 public function testSiblingReference() 434 { 435 $serializer = new Serializer([$this->normalizer]); 436 $this->normalizer->setSerializer($serializer); 437 438 $siblingHolder = new SiblingHolder(); 439 440 $expected = [ 441 'sibling0' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 442 'sibling1' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 443 'sibling2' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 444 ]; 445 $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder)); 446 } 447 448 public function testCircularReferenceHandler() 449 { 450 $serializer = new Serializer([$this->normalizer]); 451 $this->normalizer->setSerializer($serializer); 452 $this->normalizer->setCircularReferenceHandler(function ($obj) { 453 return \get_class($obj); 454 }); 455 456 $obj = new CircularReferenceDummy(); 457 458 $expected = ['me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy']; 459 $this->assertEquals($expected, $this->normalizer->normalize($obj)); 460 } 461 462 public function testObjectToPopulate() 463 { 464 $dummy = new GetSetDummy(); 465 $dummy->setFoo('foo'); 466 467 $obj = $this->normalizer->denormalize( 468 ['bar' => 'bar'], 469 GetSetDummy::class, 470 null, 471 [GetSetMethodNormalizer::OBJECT_TO_POPULATE => $dummy] 472 ); 473 474 $this->assertEquals($dummy, $obj); 475 $this->assertEquals('foo', $obj->getFoo()); 476 $this->assertEquals('bar', $obj->getBar()); 477 } 478 479 public function testDenormalizeNonExistingAttribute() 480 { 481 $this->assertEquals( 482 new GetSetDummy(), 483 $this->normalizer->denormalize(['non_existing' => true], GetSetDummy::class) 484 ); 485 } 486 487 public function testDenormalizeShouldNotSetStaticAttribute() 488 { 489 $obj = $this->normalizer->denormalize(['staticObject' => true], GetSetDummy::class); 490 491 $this->assertEquals(new GetSetDummy(), $obj); 492 $this->assertNull(GetSetDummy::getStaticObject()); 493 } 494 495 public function testNoTraversableSupport() 496 { 497 $this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject())); 498 } 499 500 public function testNoStaticGetSetSupport() 501 { 502 $this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy())); 503 } 504 505 public function testPrivateSetter() 506 { 507 $obj = $this->normalizer->denormalize(['foo' => 'foobar'], ObjectWithPrivateSetterDummy::class); 508 $this->assertEquals('bar', $obj->getFoo()); 509 } 510 511 public function testHasGetterDenormalize() 512 { 513 $obj = $this->normalizer->denormalize(['foo' => true], ObjectWithHasGetterDummy::class); 514 $this->assertTrue($obj->hasFoo()); 515 } 516 517 public function testHasGetterNormalize() 518 { 519 $obj = new ObjectWithHasGetterDummy(); 520 $obj->setFoo(true); 521 522 $this->assertEquals( 523 ['foo' => true], 524 $this->normalizer->normalize($obj, 'any') 525 ); 526 } 527 528 public function testMaxDepth() 529 { 530 $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); 531 $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory); 532 $serializer = new Serializer([$this->normalizer]); 533 $this->normalizer->setSerializer($serializer); 534 535 $level1 = new MaxDepthDummy(); 536 $level1->bar = 'level1'; 537 538 $level2 = new MaxDepthDummy(); 539 $level2->bar = 'level2'; 540 $level1->child = $level2; 541 542 $level3 = new MaxDepthDummy(); 543 $level3->bar = 'level3'; 544 $level2->child = $level3; 545 546 $level4 = new MaxDepthDummy(); 547 $level4->bar = 'level4'; 548 $level3->child = $level4; 549 550 $result = $serializer->normalize($level1, null, [GetSetMethodNormalizer::ENABLE_MAX_DEPTH => true]); 551 552 $expected = [ 553 'bar' => 'level1', 554 'child' => [ 555 'bar' => 'level2', 556 'child' => [ 557 'bar' => 'level3', 558 'child' => [ 559 'child' => null, 560 ], 561 ], 562 ], 563 ]; 564 565 $this->assertEquals($expected, $result); 566 } 567} 568 569class GetSetDummy 570{ 571 protected $foo; 572 private $bar; 573 private $baz; 574 protected $camelCase; 575 protected $object; 576 private static $staticObject; 577 578 public function getFoo() 579 { 580 return $this->foo; 581 } 582 583 public function setFoo($foo) 584 { 585 $this->foo = $foo; 586 } 587 588 public function getBar() 589 { 590 return $this->bar; 591 } 592 593 public function setBar($bar) 594 { 595 $this->bar = $bar; 596 } 597 598 public function isBaz() 599 { 600 return $this->baz; 601 } 602 603 public function setBaz($baz) 604 { 605 $this->baz = $baz; 606 } 607 608 public function getFooBar() 609 { 610 return $this->foo.$this->bar; 611 } 612 613 public function getCamelCase() 614 { 615 return $this->camelCase; 616 } 617 618 public function setCamelCase($camelCase) 619 { 620 $this->camelCase = $camelCase; 621 } 622 623 public function otherMethod() 624 { 625 throw new \RuntimeException('Dummy::otherMethod() should not be called'); 626 } 627 628 public function setObject($object) 629 { 630 $this->object = $object; 631 } 632 633 public function getObject() 634 { 635 return $this->object; 636 } 637 638 public static function getStaticObject() 639 { 640 return self::$staticObject; 641 } 642 643 public static function setStaticObject($object) 644 { 645 self::$staticObject = $object; 646 } 647 648 protected function getPrivate() 649 { 650 throw new \RuntimeException('Dummy::getPrivate() should not be called'); 651 } 652} 653 654class GetConstructorDummy 655{ 656 protected $foo; 657 private $bar; 658 private $baz; 659 660 public function __construct($foo, $bar, $baz) 661 { 662 $this->foo = $foo; 663 $this->bar = $bar; 664 $this->baz = $baz; 665 } 666 667 public function getFoo() 668 { 669 return $this->foo; 670 } 671 672 public function getBar() 673 { 674 return $this->bar; 675 } 676 677 public function isBaz() 678 { 679 return $this->baz; 680 } 681 682 public function otherMethod() 683 { 684 throw new \RuntimeException('Dummy::otherMethod() should not be called'); 685 } 686} 687 688abstract class SerializerNormalizer implements SerializerInterface, NormalizerInterface 689{ 690} 691 692class GetConstructorOptionalArgsDummy 693{ 694 protected $foo; 695 private $bar; 696 private $baz; 697 698 public function __construct($foo, $bar = [], $baz = []) 699 { 700 $this->foo = $foo; 701 $this->bar = $bar; 702 $this->baz = $baz; 703 } 704 705 public function getFoo() 706 { 707 return $this->foo; 708 } 709 710 public function getBar() 711 { 712 return $this->bar; 713 } 714 715 public function getBaz() 716 { 717 return $this->baz; 718 } 719 720 public function otherMethod() 721 { 722 throw new \RuntimeException('Dummy::otherMethod() should not be called'); 723 } 724} 725 726class GetConstructorArgsWithDefaultValueDummy 727{ 728 protected $foo; 729 protected $bar; 730 731 public function __construct($foo = [], $bar = null) 732 { 733 $this->foo = $foo; 734 $this->bar = $bar; 735 } 736 737 public function getFoo() 738 { 739 return $this->foo; 740 } 741 742 public function getBar() 743 { 744 return $this->bar; 745 } 746 747 public function otherMethod() 748 { 749 throw new \RuntimeException('Dummy::otherMethod() should not be called'); 750 } 751} 752 753class ObjectConstructorArgsWithPrivateMutatorDummy 754{ 755 private $foo; 756 757 public function __construct($foo) 758 { 759 $this->setFoo($foo); 760 } 761 762 public function getFoo() 763 { 764 return $this->foo; 765 } 766 767 private function setFoo($foo) 768 { 769 $this->foo = $foo; 770 } 771} 772 773class ObjectWithPrivateSetterDummy 774{ 775 private $foo = 'bar'; 776 777 public function getFoo() 778 { 779 return $this->foo; 780 } 781 782 private function setFoo($foo) 783 { 784 } 785} 786 787class ObjectWithJustStaticSetterDummy 788{ 789 private static $foo = 'bar'; 790 791 public static function getFoo() 792 { 793 return self::$foo; 794 } 795 796 public static function setFoo($foo) 797 { 798 self::$foo = $foo; 799 } 800} 801 802class ObjectWithHasGetterDummy 803{ 804 private $foo; 805 806 public function setFoo($foo) 807 { 808 $this->foo = $foo; 809 } 810 811 public function hasFoo() 812 { 813 return $this->foo; 814 } 815} 816