[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n\n*.png    binary\n\n.github/ export-ignore\ndoc/ export-ignore\nfixtures/ export-ignore\ntests/ export-ignore\n.gitattributes export-ignore\n.gitignore export-ignore\n.scrutinizer.yml export-ignore\n.travis.yml export-ignore\nphpunit.xml.dist export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: \"packagist/myclabs/deep-copy\"\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: \"Continuous Integration\"\n\non:\n  - pull_request\n  - push\n\nenv:\n  COMPOSER_ROOT_VERSION: 1.99\n\njobs:\n  composer-json-lint:\n    name: \"Lint composer.json\"\n\n    runs-on: \"ubuntu-latest\"\n\n    strategy:\n      matrix:\n        php-version:\n          - \"8.1\"\n\n    steps:\n      - name: \"Checkout repository\"\n        uses: \"actions/checkout@v6\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@v2\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          tools: \"composer-normalize\"\n\n      - name: \"Get composer cache directory\"\n        id: \"composercache\"\n        run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: \"Cache dependencies\"\n        uses: \"actions/cache@v4\"\n        with:\n          path: ${{ steps.composercache.outputs.dir }}\n          key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }}\n          restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-\n\n      - name: \"Install dependencies\"\n        run: \"composer update --no-interaction --no-progress\"\n\n      - name: \"Validate composer.json\"\n        run: \"composer validate --strict\"\n\n      - name: \"Normalize composer.json\"\n        run: \"composer-normalize --dry-run\"\n\n  tests:\n    name: \"Tests\"\n\n    runs-on: \"ubuntu-latest\"\n\n    strategy:\n      matrix:\n        php-version:\n          - \"7.1\"\n          - \"7.2\"\n          - \"7.3\"\n          - \"7.4\"\n          - \"8.0\"\n          - \"8.1\"\n          - \"8.2\"\n          - \"8.3\"\n          - \"8.4\"\n          - \"8.5\"\n        dependencies:\n          - \"lowest\"\n          - \"highest\"\n\n    steps:\n      - name: \"Checkout repository\"\n        uses: \"actions/checkout@v6\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@v2\"\n        with:\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: \"zend.assertions=1\"\n\n      - name: \"Get composer cache directory\"\n        id: \"composercache\"\n        run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: \"Cache dependencies\"\n        uses: \"actions/cache@v4\"\n        with:\n          path: ${{ steps.composercache.outputs.dir }}\n          key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }}\n          restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-\n\n      - name: \"Install lowest dependencies\"\n        if: ${{ matrix.dependencies == 'lowest' }}\n        run: \"composer update --no-interaction --no-progress --prefer-lowest\"\n\n      - name: \"Install highest dependencies\"\n        if: ${{ matrix.dependencies == 'highest' }}\n        run: \"composer update --no-interaction --no-progress\"\n\n      - name: \"Run tests\"\n        timeout-minutes: 3\n        run: \"vendor/bin/phpunit\"\n"
  },
  {
    "path": ".gitignore",
    "content": "/composer.phar\n/composer.lock\n/vendor/*\n.phpunit.result.cache\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "build:\n    environment:\n        variables:\n            COMPOSER_ROOT_VERSION: '1.8.0'\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 My C-Sense\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# DeepCopy\n\nDeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph.\n\n[![Total Downloads](https://poser.pugx.org/myclabs/deep-copy/downloads.svg)](https://packagist.org/packages/myclabs/deep-copy)\n[![Integrate](https://github.com/myclabs/DeepCopy/actions/workflows/ci.yaml/badge.svg?branch=1.x)](https://github.com/myclabs/DeepCopy/actions/workflows/ci.yaml)\n\n## Table of Contents\n\n1. [How](#how)\n1. [Why](#why)\n    1. [Using simply `clone`](#using-simply-clone)\n    1. [Overriding `__clone()`](#overriding-__clone)\n    1. [With `DeepCopy`](#with-deepcopy)\n1. [How it works](#how-it-works)\n1. [Going further](#going-further)\n    1. [Matchers](#matchers)\n        1. [Property name](#property-name)\n        1. [Specific property](#specific-property)\n        1. [Type](#type)\n    1. [Filters](#filters)\n        1. [`SetNullFilter`](#setnullfilter-filter)\n        1. [`KeepFilter`](#keepfilter-filter)\n        1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter-filter)\n        1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter-filter)\n        1. [`DoctrineProxyFilter`](#doctrineproxyfilter-filter)\n        1. [`ReplaceFilter`](#replacefilter-type-filter)\n        1. [`ShallowCopyFilter`](#shallowcopyfilter-type-filter)\n1. [Edge cases](#edge-cases)\n1. [Contributing](#contributing)\n    1. [Tests](#tests)\n\n\n## How?\n\nInstall with Composer:\n\n```\ncomposer require myclabs/deep-copy\n```\n\nUse it:\n\n```php\nuse DeepCopy\\DeepCopy;\n\n$copier = new DeepCopy();\n$myCopy = $copier->copy($myObject);\n```\n\n\n## Why?\n\n- How do you create copies of your objects?\n\n```php\n$myCopy = clone $myObject;\n```\n\n- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)?\n\nYou use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior\nyourself.\n\n- But how do you handle **cycles** in the association graph?\n\nNow you're in for a big mess :(\n\n![association graph](doc/graph.png)\n\n\n### Using simply `clone`\n\n![Using clone](doc/clone.png)\n\n\n### Overriding `__clone()`\n\n![Overriding __clone](doc/deep-clone.png)\n\n\n### With `DeepCopy`\n\n![With DeepCopy](doc/deep-copy.png)\n\n\n## How it works\n\nDeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it\nkeeps a hash map of all instances and thus preserves the object graph.\n\nTo use it:\n\n```php\nuse function DeepCopy\\deep_copy;\n\n$copy = deep_copy($var);\n```\n\nAlternatively, you can create your own `DeepCopy` instance to configure it differently for example:\n\n```php\nuse DeepCopy\\DeepCopy;\n\n$copier = new DeepCopy(true);\n\n$copy = $copier->copy($var);\n```\n\nYou may want to roll your own deep copy function:\n\n```php\nnamespace Acme;\n\nuse DeepCopy\\DeepCopy;\n\nfunction deep_copy($var)\n{\n    static $copier = null;\n    \n    if (null === $copier) {\n        $copier = new DeepCopy(true);\n    }\n    \n    return $copier->copy($var);\n}\n```\n\n\n## Going further\n\nYou can add filters to customize the copy process.\n\nThe method to add a filter is `DeepCopy\\DeepCopy::addFilter($filter, $matcher)`,\nwith `$filter` implementing `DeepCopy\\Filter\\Filter`\nand `$matcher` implementing `DeepCopy\\Matcher\\Matcher`.\n\nWe provide some generic filters and matchers.\n\n\n### Matchers\n\n  - `DeepCopy\\Matcher` applies on a object attribute.\n  - `DeepCopy\\TypeMatcher` applies on any element found in graph, including array elements.\n\n\n#### Property name\n\nThe `PropertyNameMatcher` will match a property by its name:\n\n```php\nuse DeepCopy\\Matcher\\PropertyNameMatcher;\n\n// Will apply a filter to any property of any objects named \"id\"\n$matcher = new PropertyNameMatcher('id');\n```\n\n\n#### Specific property\n\nThe `PropertyMatcher` will match a specific property of a specific class:\n\n```php\nuse DeepCopy\\Matcher\\PropertyMatcher;\n\n// Will apply a filter to the property \"id\" of any objects of the class \"MyClass\"\n$matcher = new PropertyMatcher('MyClass', 'id');\n```\n\n\n#### Type\n\nThe `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of\n[gettype()](http://php.net/manual/en/function.gettype.php) function):\n\n```php\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\n\n// Will apply a filter to any object that is an instance of Doctrine\\Common\\Collections\\Collection\n$matcher = new TypeMatcher('Doctrine\\Common\\Collections\\Collection');\n```\n\n\n### Filters\n\n- `DeepCopy\\Filter` applies a transformation to the object attribute matched by `DeepCopy\\Matcher`\n- `DeepCopy\\TypeFilter` applies a transformation to any element matched by `DeepCopy\\TypeMatcher`\n\nBy design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied).\nUsing the ([`ChainableFilter`](#chainablefilter-filter)) won't stop the chain of filters.\n\n\n#### `SetNullFilter` (filter)\n\nLet's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have\nany ID:\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\SetNullFilter;\nuse DeepCopy\\Matcher\\PropertyNameMatcher;\n\n$object = MyClass::load(123);\necho $object->id; // 123\n\n$copier = new DeepCopy();\n$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));\n\n$copy = $copier->copy($object);\n\necho $copy->id; // null\n```\n\n\n#### `KeepFilter` (filter)\n\nIf you want a property to remain untouched (for example, an association to an object):\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\KeepFilter;\nuse DeepCopy\\Matcher\\PropertyMatcher;\n\n$copier = new DeepCopy();\n$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category'));\n\n$copy = $copier->copy($object);\n// $copy->category has not been touched\n```\n\n\n#### `ChainableFilter` (filter)\n\nIf you use cloning on proxy classes, you might want to apply two filters for:\n1. loading the data\n2. applying a transformation\n\nYou can use the `ChainableFilter` as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e. \nthe next ones may be applied).\n\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\ChainableFilter;\nuse DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter;\nuse DeepCopy\\Filter\\SetNullFilter;\nuse DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher;\nuse DeepCopy\\Matcher\\PropertyNameMatcher;\n\n$copier = new DeepCopy();\n$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());\n$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));\n\n$copy = $copier->copy($object);\n\necho $copy->id; // null\n```\n\n\n#### `DoctrineCollectionFilter` (filter)\n\nIf you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`:\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter;\nuse DeepCopy\\Matcher\\PropertyTypeMatcher;\n\n$copier = new DeepCopy();\n$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\\Common\\Collections\\Collection'));\n\n$copy = $copier->copy($object);\n```\n\n\n#### `DoctrineEmptyCollectionFilter` (filter)\n\nIf you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the\n`DoctrineEmptyCollectionFilter`\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter;\nuse DeepCopy\\Matcher\\PropertyMatcher;\n\n$copier = new DeepCopy();\n$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty'));\n\n$copy = $copier->copy($object);\n\n// $copy->myProperty will return an empty collection\n```\n\n\n#### `DoctrineProxyFilter` (filter)\n\nIf you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a\nDoctrine proxy class (...\\\\\\_\\_CG\\_\\_\\Proxy).\nYou can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class.\n**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded\nbefore other filters are applied!**\nWe recommend to decorate the `DoctrineProxyFilter` with the `ChainableFilter` to allow applying other filters to the\ncloned lazy loaded entities.\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter;\nuse DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher;\n\n$copier = new DeepCopy();\n$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());\n\n$copy = $copier->copy($object);\n\n// $copy should now contain a clone of all entities, including those that were not yet fully loaded.\n```\n\n\n#### `ReplaceFilter` (type filter)\n\n1. If you want to replace the value of a property:\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Filter\\ReplaceFilter;\nuse DeepCopy\\Matcher\\PropertyMatcher;\n\n$copier = new DeepCopy();\n$callback = function ($currentValue) {\n  return $currentValue . ' (copy)'\n};\n$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title'));\n\n$copy = $copier->copy($object);\n\n// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)'\n```\n\n2. If you want to replace whole element:\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\ReplaceFilter;\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\n\n$copier = new DeepCopy();\n$callback = function (MyClass $myClass) {\n  return get_class($myClass);\n};\n$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass'));\n\n$copy = $copier->copy([new MyClass, 'some string', new MyClass]);\n\n// $copy will contain ['MyClass', 'some string', 'MyClass']\n```\n\n\nThe `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable.\n\n\n#### `ShallowCopyFilter` (type filter)\n\nStop *DeepCopy* from recursively copying element, using standard `clone` instead:\n\n```php\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\ShallowCopyFilter;\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\nuse Mockery as m;\n\n$this->deepCopy = new DeepCopy();\n$this->deepCopy->addTypeFilter(\n\tnew ShallowCopyFilter,\n\tnew TypeMatcher(m\\MockInterface::class)\n);\n\n$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class));\n// All mocks will be just cloned, not deep copied\n```\n\n\n## Edge cases\n\nThe following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are\nnot applied. There is two ways for you to handle them:\n\n- Implement your own `__clone()` method\n- Use a filter with a type matcher\n\n\n## Contributing\n\nDeepCopy is distributed under the MIT license.\n\n\n### Tests\n\nRunning the tests is simple:\n\n```php\nvendor/bin/phpunit\n```\n\n### Support\n\nGet professional support via [the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-myclabs-deep-copy?utm_source=packagist-myclabs-deep-copy&utm_medium=referral&utm_campaign=readme).\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"myclabs/deep-copy\",\n    \"description\": \"Create deep copies (clones) of your objects\",\n    \"license\": \"MIT\",\n    \"type\": \"library\",\n    \"keywords\": [\n        \"clone\",\n        \"copy\",\n        \"duplicate\",\n        \"object\",\n        \"object graph\"\n    ],\n    \"require\": {\n        \"php\": \"^7.1 || ^8.0\"\n    },\n    \"require-dev\": {\n        \"doctrine/collections\": \"^1.6.8\",\n        \"doctrine/common\": \"^2.13.3 || ^3.2.2\",\n        \"phpspec/prophecy\": \"^1.10\",\n        \"phpunit/phpunit\": \"^7.5.20 || ^8.5.23 || ^9.5.13\"\n    },\n    \"conflict\": {\n        \"doctrine/collections\": \"<1.6.8\",\n        \"doctrine/common\": \"<2.13.3 || >=3 <3.2.2\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"DeepCopy\\\\\": \"src/DeepCopy/\"\n        },\n        \"files\": [\n            \"src/DeepCopy/deep_copy.php\"\n        ]\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"DeepCopyTest\\\\\": \"tests/DeepCopyTest/\",\n            \"DeepCopy\\\\\": \"fixtures/\"\n        }\n    },\n    \"config\": {\n        \"sort-packages\": true\n    }\n}\n"
  },
  {
    "path": "fixtures/f001/A.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f001;\n\nclass A\n{\n    private $aProp;\n\n    public function getAProp()\n    {\n        return $this->aProp;\n    }\n\n    public function setAProp($prop)\n    {\n        $this->aProp = $prop;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "fixtures/f001/B.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f001;\n\nclass B extends A\n{\n    private $bProp;\n\n    public function getBProp()\n    {\n        return $this->bProp;\n    }\n\n    public function setBProp($prop)\n    {\n        $this->bProp = $prop;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "fixtures/f002/A.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f002;\n\nclass A\n{\n    private $prop1;\n    private $prop2;\n\n    public function getProp1()\n    {\n        return $this->prop1;\n    }\n\n    public function setProp1($prop)\n    {\n        $this->prop1 = $prop;\n\n        return $this;\n    }\n\n    public function getProp2()\n    {\n        return $this->prop2;\n    }\n\n    public function setProp2($prop)\n    {\n        $this->prop2 = $prop;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "fixtures/f003/Foo.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f003;\n\nclass Foo\n{\n    private $name;\n    private $prop;\n\n    public function __construct($name)\n    {\n        $this->name = $name;\n    }\n\n    public function getProp()\n    {\n        return $this->prop;\n    }\n\n    public function setProp($prop)\n    {\n        $this->prop = $prop;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "fixtures/f004/UnclonableItem.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f004;\n\nuse BadMethodCallException;\n\nclass UnclonableItem\n{\n    private function __clone()\n    {\n        throw new BadMethodCallException('Unsupported call.');\n    }\n}\n"
  },
  {
    "path": "fixtures/f005/Foo.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f005;\n\nclass Foo\n{\n    public $cloned = false;\n\n    public function __clone()\n    {\n        $this->cloned = true;\n    }\n}\n"
  },
  {
    "path": "fixtures/f006/A.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f006;\n\nclass A\n{\n    public $cloned = false;\n    private $aProp;\n\n    public function getAProp()\n    {\n        return $this->aProp;\n    }\n\n    public function setAProp($prop)\n    {\n        $this->aProp = $prop;\n\n        return $this;\n    }\n\n    public function __clone()\n    {\n        $this->cloned = true;\n    }\n}\n"
  },
  {
    "path": "fixtures/f006/B.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f006;\n\nclass B\n{\n    public $cloned = false;\n    private $bProp;\n\n    public function getBProp()\n    {\n        return $this->bProp;\n    }\n\n    public function setBProp($prop)\n    {\n        $this->bProp = $prop;\n\n        return $this;\n    }\n\n    public function __clone()\n    {\n        $this->cloned = true;\n    }\n}\n"
  },
  {
    "path": "fixtures/f007/FooDateInterval.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f007;\n\nuse DateInterval;\n\nclass FooDateInterval extends DateInterval\n{\n    public $cloned = false;\n\n    public function __clone()\n    {\n        $this->cloned = true;\n    }\n}\n"
  },
  {
    "path": "fixtures/f007/FooDateTimeZone.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f007;\n\nuse DateTimeZone;\n\nclass FooDateTimeZone extends DateTimeZone\n{\n    public $cloned = false;\n\n    public function __clone()\n    {\n        $this->cloned = true;\n    }\n}\n"
  },
  {
    "path": "fixtures/f008/A.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f008;\n\nclass A\n{\n    private $foo;\n\n    public function __construct($foo)\n    {\n        $this->foo = $foo;\n    }\n\n    public function getFoo()\n    {\n        return $this->foo;\n    }\n}\n"
  },
  {
    "path": "fixtures/f008/B.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f008;\n\nclass B extends A\n{\n}\n"
  },
  {
    "path": "fixtures/f009/TypedObjectProperty.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DeepCopy\\f009;\n\nclass TypedObjectProperty\n{\n    public \\DateTime $date;\n}\n"
  },
  {
    "path": "fixtures/f009/TypedProperty.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f009;\n\nclass TypedProperty\n{\n    public int $foo;\n}\n"
  },
  {
    "path": "fixtures/f011/ArrayObjectExtended.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f011;\n\nuse ArrayObject;\n\nclass ArrayObjectExtended extends ArrayObject\n{\n    public $x;\n\n    public function __construct($x)\n    {\n        parent::__construct();\n        $this->x = $x;\n    }\n}\n"
  },
  {
    "path": "fixtures/f012/Suit.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f012;\n\nenum Suit: string\n{\n    case Hearts = 'Hearts';\n    case Diamonds = 'Diamonds';\n    case Clubs = 'Clubs';\n    case Spades = 'Spades';\n}\n"
  },
  {
    "path": "fixtures/f013/A.php",
    "content": "<?php\n\nnamespace DeepCopy\\f013;\n\nuse BadMethodCallException;\nuse Doctrine\\Persistence\\Proxy;\n\nclass A implements Proxy\n{\n    public $foo = 1;\n\n    /**\n     * @inheritdoc\n     */\n    public function __load(): void\n    {\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function __isInitialized(): bool\n    {\n        throw new BadMethodCallException();\n    }\n}\n"
  },
  {
    "path": "fixtures/f013/B.php",
    "content": "<?php\n\nnamespace DeepCopy\\f013;\n\nuse BadMethodCallException;\nuse Doctrine\\Persistence\\Proxy;\n\nclass B implements Proxy\n{\n    private $foo;\n\n    /**\n     * @inheritdoc\n     */\n    public function __load(): void\n    {\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function __isInitialized(): bool\n    {\n        throw new BadMethodCallException();\n    }\n\n    public function getFoo()\n    {\n        return $this->foo;\n    }\n\n    public function setFoo($foo)\n    {\n        $this->foo = $foo;\n    }\n}\n"
  },
  {
    "path": "fixtures/f013/C.php",
    "content": "<?php\n\nnamespace DeepCopy\\f013;\n\nclass C\n{\n    public $foo = 1;\n\n    public function __clone()\n    {\n        $this->foo = null;\n    }\n}\n"
  },
  {
    "path": "fixtures/f014/ReadonlyObjectProperty.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f014;\n\nclass ReadonlyObjectProperty\n{\n    public readonly ReadonlyScalarProperty $foo;\n\n    public function __construct()\n    {\n        $this->foo = new ReadonlyScalarProperty();\n    }\n}"
  },
  {
    "path": "fixtures/f014/ReadonlyScalarProperty.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f014;\n\nclass ReadonlyScalarProperty\n{\n    public readonly string $foo;\n    public readonly int $bar;\n    public readonly array $baz;\n\n    public function __construct()\n    {\n        $this->foo = 'foo';\n        $this->bar = 1;\n        $this->baz = [];\n    }\n}"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         colors=\"true\">\n\n    <testsuites>\n        <testsuite name=\"Test suite\">\n            <directory>./tests/DeepCopyTest</directory>\n        </testsuite>\n    </testsuites>\n\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">./src/</directory>\n            <exclude>\n                <file>src/DeepCopy/deep_copy.php</file>\n            </exclude>\n        </whitelist>\n    </filter>\n\n</phpunit>\n"
  },
  {
    "path": "src/DeepCopy/DeepCopy.php",
    "content": "<?php\n\nnamespace DeepCopy;\n\nuse ArrayObject;\nuse DateInterval;\nuse DatePeriod;\nuse DateTimeInterface;\nuse DateTimeZone;\nuse DeepCopy\\Exception\\CloneException;\nuse DeepCopy\\Filter\\ChainableFilter;\nuse DeepCopy\\Filter\\Filter;\nuse DeepCopy\\Matcher\\Matcher;\nuse DeepCopy\\Reflection\\ReflectionHelper;\nuse DeepCopy\\TypeFilter\\Date\\DateIntervalFilter;\nuse DeepCopy\\TypeFilter\\Date\\DatePeriodFilter;\nuse DeepCopy\\TypeFilter\\Spl\\ArrayObjectFilter;\nuse DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter;\nuse DeepCopy\\TypeFilter\\TypeFilter;\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\nuse ReflectionObject;\nuse ReflectionProperty;\nuse SplDoublyLinkedList;\n\n/**\n * @final\n */\nclass DeepCopy\n{\n    /**\n     * @var object[] List of objects copied.\n     */\n    private $hashMap = [];\n\n    /**\n     * Filters to apply.\n     *\n     * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.\n     */\n    private $filters = [];\n\n    /**\n     * Type Filters to apply.\n     *\n     * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.\n     */\n    private $typeFilters = [];\n\n    /**\n     * @var bool\n     */\n    private $skipUncloneable = false;\n\n    /**\n     * @var bool\n     */\n    private $useCloneMethod;\n\n    /**\n     * @param bool $useCloneMethod   If set to true, when an object implements the __clone() function, it will be used\n     *                               instead of the regular deep cloning.\n     */\n    public function __construct($useCloneMethod = false)\n    {\n        $this->useCloneMethod = $useCloneMethod;\n\n        $this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class));\n        $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class));\n        $this->addTypeFilter(new DatePeriodFilter(), new TypeMatcher(DatePeriod::class));\n        $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class));\n    }\n\n    /**\n     * If enabled, will not throw an exception when coming across an uncloneable property.\n     *\n     * @param $skipUncloneable\n     *\n     * @return $this\n     */\n    public function skipUncloneable($skipUncloneable = true)\n    {\n        $this->skipUncloneable = $skipUncloneable;\n\n        return $this;\n    }\n\n    /**\n     * Deep copies the given object.\n     *\n     * @template TObject\n     *\n     * @param TObject $object\n     *\n     * @return TObject\n     */\n    public function copy($object)\n    {\n        $this->hashMap = [];\n\n        return $this->recursiveCopy($object);\n    }\n\n    public function addFilter(Filter $filter, Matcher $matcher)\n    {\n        $this->filters[] = [\n            'matcher' => $matcher,\n            'filter'  => $filter,\n        ];\n    }\n\n    public function prependFilter(Filter $filter, Matcher $matcher)\n    {\n        array_unshift($this->filters, [\n            'matcher' => $matcher,\n            'filter'  => $filter,\n        ]);\n    }\n\n    public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)\n    {\n        $this->typeFilters[] = [\n            'matcher' => $matcher,\n            'filter'  => $filter,\n        ];\n    }\n\n    public function prependTypeFilter(TypeFilter $filter, TypeMatcher $matcher)\n    {\n        array_unshift($this->typeFilters, [\n            'matcher' => $matcher,\n            'filter'  => $filter,\n        ]);\n    }\n\n    private function recursiveCopy($var)\n    {\n        // Matches Type Filter\n        if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) {\n            return $filter->apply($var);\n        }\n\n        // Resource\n        if (is_resource($var)) {\n            return $var;\n        }\n\n        // Array\n        if (is_array($var)) {\n            return $this->copyArray($var);\n        }\n\n        // Scalar\n        if (! is_object($var)) {\n            return $var;\n        }\n\n        // Enum\n        if (PHP_VERSION_ID >= 80100 && enum_exists(get_class($var))) {\n            return $var;\n        }\n\n        // Object\n        return $this->copyObject($var);\n    }\n\n    /**\n     * Copy an array\n     * @param array $array\n     * @return array\n     */\n    private function copyArray(array $array)\n    {\n        foreach ($array as $key => $value) {\n            $array[$key] = $this->recursiveCopy($value);\n        }\n\n        return $array;\n    }\n\n    /**\n     * Copies an object.\n     *\n     * @param object $object\n     *\n     * @throws CloneException\n     *\n     * @return object\n     */\n    private function copyObject($object)\n    {\n        $objectHash = spl_object_hash($object);\n\n        if (isset($this->hashMap[$objectHash])) {\n            return $this->hashMap[$objectHash];\n        }\n\n        $reflectedObject = new ReflectionObject($object);\n        $isCloneable = $reflectedObject->isCloneable();\n\n        if (false === $isCloneable) {\n            if ($this->skipUncloneable) {\n                $this->hashMap[$objectHash] = $object;\n\n                return $object;\n            }\n\n            throw new CloneException(\n                sprintf(\n                    'The class \"%s\" is not cloneable.',\n                    $reflectedObject->getName()\n                )\n            );\n        }\n\n        $newObject = clone $object;\n        $this->hashMap[$objectHash] = $newObject;\n\n        if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) {\n            return $newObject;\n        }\n\n        if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) {\n            return $newObject;\n        }\n\n        foreach (ReflectionHelper::getProperties($reflectedObject) as $property) {\n            $this->copyObjectProperty($newObject, $property);\n        }\n\n        return $newObject;\n    }\n\n    private function copyObjectProperty($object, ReflectionProperty $property)\n    {\n        // Ignore static properties\n        if ($property->isStatic()) {\n            return;\n        }\n\n        // Ignore readonly properties\n        if (method_exists($property, 'isReadOnly') && $property->isReadOnly()) {\n            return;\n        }\n\n        // Apply the filters\n        foreach ($this->filters as $item) {\n            /** @var Matcher $matcher */\n            $matcher = $item['matcher'];\n            /** @var Filter $filter */\n            $filter = $item['filter'];\n\n            if ($matcher->matches($object, $property->getName())) {\n                $filter->apply(\n                    $object,\n                    $property->getName(),\n                    function ($object) {\n                        return $this->recursiveCopy($object);\n                    }\n                );\n\n                if ($filter instanceof ChainableFilter) {\n                    continue;\n                }\n\n                // If a filter matches, we stop processing this property\n                return;\n            }\n        }\n\n        if (PHP_VERSION_ID < 80100) {\n            $property->setAccessible(true);\n        }\n\n        // Ignore uninitialized properties (for PHP >7.4)\n        if (method_exists($property, 'isInitialized') && !$property->isInitialized($object)) {\n            return;\n        }\n\n        $propertyValue = $property->getValue($object);\n\n        // Copy the property\n        $property->setValue($object, $this->recursiveCopy($propertyValue));\n    }\n\n    /**\n     * Returns first filter that matches variable, `null` if no such filter found.\n     *\n     * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and\n     *                             'matcher' with value of type {@see TypeMatcher}\n     * @param mixed $var\n     *\n     * @return TypeFilter|null\n     */\n    private function getFirstMatchedTypeFilter(array $filterRecords, $var)\n    {\n        $matched = $this->first(\n            $filterRecords,\n            function (array $record) use ($var) {\n                /* @var TypeMatcher $matcher */\n                $matcher = $record['matcher'];\n\n                return $matcher->matches($var);\n            }\n        );\n\n        return isset($matched) ? $matched['filter'] : null;\n    }\n\n    /**\n     * Returns first element that matches predicate, `null` if no such element found.\n     *\n     * @param array    $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs.\n     * @param callable $predicate Predicate arguments are: element.\n     *\n     * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher'\n     *                    with value of type {@see TypeMatcher} or `null`.\n     */\n    private function first(array $elements, callable $predicate)\n    {\n        foreach ($elements as $element) {\n            if (call_user_func($predicate, $element)) {\n                return $element;\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Exception/CloneException.php",
    "content": "<?php\n\nnamespace DeepCopy\\Exception;\n\nuse UnexpectedValueException;\n\nclass CloneException extends UnexpectedValueException\n{\n} "
  },
  {
    "path": "src/DeepCopy/Exception/PropertyException.php",
    "content": "<?php\n\nnamespace DeepCopy\\Exception;\n\nuse ReflectionException;\n\nclass PropertyException extends ReflectionException\n{\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/ChainableFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter;\n\n/**\n * Defines a decorator filter that will not stop the chain of filters.\n */\nclass ChainableFilter implements Filter\n{\n    /**\n     * @var Filter\n     */\n    protected $filter;\n\n    public function __construct(Filter $filter)\n    {\n        $this->filter = $filter;\n    }\n\n    public function apply($object, $property, $objectCopier)\n    {\n        $this->filter->apply($object, $property, $objectCopier);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Filter;\nuse DeepCopy\\Reflection\\ReflectionHelper;\n\n/**\n * @final\n */\nclass DoctrineCollectionFilter implements Filter\n{\n    /**\n     * Copies the object property doctrine collection.\n     *\n     * {@inheritdoc}\n     */\n    public function apply($object, $property, $objectCopier)\n    {\n        $reflectionProperty = ReflectionHelper::getProperty($object, $property);\n\n        if (PHP_VERSION_ID < 80100) {\n            $reflectionProperty->setAccessible(true);\n        }\n        $oldCollection = $reflectionProperty->getValue($object);\n\n        $newCollection = $oldCollection->map(\n            function ($item) use ($objectCopier) {\n                return $objectCopier($item);\n            }\n        );\n\n        $reflectionProperty->setValue($object, $newCollection);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Filter;\nuse DeepCopy\\Reflection\\ReflectionHelper;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\n\n/**\n * @final\n */\nclass DoctrineEmptyCollectionFilter implements Filter\n{\n    /**\n     * Sets the object property to an empty doctrine collection.\n     *\n     * @param object   $object\n     * @param string   $property\n     * @param callable $objectCopier\n     */\n    public function apply($object, $property, $objectCopier)\n    {\n        $reflectionProperty = ReflectionHelper::getProperty($object, $property);\n        if (PHP_VERSION_ID < 80100) {\n            $reflectionProperty->setAccessible(true);\n        }\n\n        $reflectionProperty->setValue($object, new ArrayCollection());\n    }\n} "
  },
  {
    "path": "src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Filter;\n\n/**\n * @final\n */\nclass DoctrineProxyFilter implements Filter\n{\n    /**\n     * Triggers the magic method __load() on a Doctrine Proxy class to load the\n     * actual entity from the database.\n     *\n     * {@inheritdoc}\n     */\n    public function apply($object, $property, $objectCopier)\n    {\n        $object->__load();\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/Filter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter;\n\n/**\n * Filter to apply to a property while copying an object\n */\ninterface Filter\n{\n    /**\n     * Applies the filter to the object.\n     *\n     * @param object   $object\n     * @param string   $property\n     * @param callable $objectCopier\n     */\n    public function apply($object, $property, $objectCopier);\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/KeepFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter;\n\nclass KeepFilter implements Filter\n{\n    /**\n     * Keeps the value of the object property.\n     *\n     * {@inheritdoc}\n     */\n    public function apply($object, $property, $objectCopier)\n    {\n        // Nothing to do\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/ReplaceFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter;\n\nuse DeepCopy\\Reflection\\ReflectionHelper;\n\n/**\n * @final\n */\nclass ReplaceFilter implements Filter\n{\n    /**\n     * @var callable\n     */\n    protected $callback;\n\n    /**\n     * @param callable $callable Will be called to get the new value for each property to replace\n     */\n    public function __construct(callable $callable)\n    {\n        $this->callback = $callable;\n    }\n\n    /**\n     * Replaces the object property by the result of the callback called with the object property.\n     *\n     * {@inheritdoc}\n     */\n    public function apply($object, $property, $objectCopier)\n    {\n        $reflectionProperty = ReflectionHelper::getProperty($object, $property);\n        if (PHP_VERSION_ID < 80100) {\n            $reflectionProperty->setAccessible(true);\n        }\n\n        $value = call_user_func($this->callback, $reflectionProperty->getValue($object));\n\n        $reflectionProperty->setValue($object, $value);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Filter/SetNullFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\Filter;\n\nuse DeepCopy\\Reflection\\ReflectionHelper;\n\n/**\n * @final\n */\nclass SetNullFilter implements Filter\n{\n    /**\n     * Sets the object property to null.\n     *\n     * {@inheritdoc}\n     */\n    public function apply($object, $property, $objectCopier)\n    {\n        $reflectionProperty = ReflectionHelper::getProperty($object, $property);\n\n        if (PHP_VERSION_ID < 80100) {\n            $reflectionProperty->setAccessible(true);\n        }\n        $reflectionProperty->setValue($object, null);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php",
    "content": "<?php\n\nnamespace DeepCopy\\Matcher\\Doctrine;\n\nuse DeepCopy\\Matcher\\Matcher;\nuse Doctrine\\Persistence\\Proxy;\n\n/**\n * @final\n */\nclass DoctrineProxyMatcher implements Matcher\n{\n    /**\n     * Matches a Doctrine Proxy class.\n     *\n     * {@inheritdoc}\n     */\n    public function matches($object, $property)\n    {\n        return $object instanceof Proxy;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Matcher/Matcher.php",
    "content": "<?php\n\nnamespace DeepCopy\\Matcher;\n\ninterface Matcher\n{\n    /**\n     * @param object $object\n     * @param string $property\n     *\n     * @return boolean\n     */\n    public function matches($object, $property);\n}\n"
  },
  {
    "path": "src/DeepCopy/Matcher/PropertyMatcher.php",
    "content": "<?php\n\nnamespace DeepCopy\\Matcher;\n\n/**\n * @final\n */\nclass PropertyMatcher implements Matcher\n{\n    /**\n     * @var string\n     */\n    private $class;\n\n    /**\n     * @var string\n     */\n    private $property;\n\n    /**\n     * @param string $class    Class name\n     * @param string $property Property name\n     */\n    public function __construct($class, $property)\n    {\n        $this->class = $class;\n        $this->property = $property;\n    }\n\n    /**\n     * Matches a specific property of a specific class.\n     *\n     * {@inheritdoc}\n     */\n    public function matches($object, $property)\n    {\n        return ($object instanceof $this->class) && $property == $this->property;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Matcher/PropertyNameMatcher.php",
    "content": "<?php\n\nnamespace DeepCopy\\Matcher;\n\n/**\n * @final\n */\nclass PropertyNameMatcher implements Matcher\n{\n    /**\n     * @var string\n     */\n    private $property;\n\n    /**\n     * @param string $property Property name\n     */\n    public function __construct($property)\n    {\n        $this->property = $property;\n    }\n\n    /**\n     * Matches a property by its name.\n     *\n     * {@inheritdoc}\n     */\n    public function matches($object, $property)\n    {\n        return $property == $this->property;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Matcher/PropertyTypeMatcher.php",
    "content": "<?php\n\nnamespace DeepCopy\\Matcher;\n\nuse DeepCopy\\Reflection\\ReflectionHelper;\nuse ReflectionException;\n\n/**\n * Matches a property by its type.\n *\n * It is recommended to use {@see DeepCopy\\TypeFilter\\TypeFilter} instead, as it applies on all occurrences\n * of given type in copied context (eg. array elements), not just on object properties.\n *\n * @final\n */\nclass PropertyTypeMatcher implements Matcher\n{\n    /**\n     * @var string\n     */\n    private $propertyType;\n\n    /**\n     * @param string $propertyType Property type\n     */\n    public function __construct($propertyType)\n    {\n        $this->propertyType = $propertyType;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function matches($object, $property)\n    {\n        try {\n            $reflectionProperty = ReflectionHelper::getProperty($object, $property);\n        } catch (ReflectionException $exception) {\n            return false;\n        }\n\n        if (PHP_VERSION_ID < 80100) {\n            $reflectionProperty->setAccessible(true);\n        }\n\n        // Uninitialized properties (for PHP >7.4)\n        if (method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) {\n            // null instanceof $this->propertyType\n            return false;\n        }\n\n        return $reflectionProperty->getValue($object) instanceof $this->propertyType;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/Reflection/ReflectionHelper.php",
    "content": "<?php\n\nnamespace DeepCopy\\Reflection;\n\nuse DeepCopy\\Exception\\PropertyException;\nuse ReflectionClass;\nuse ReflectionException;\nuse ReflectionObject;\nuse ReflectionProperty;\n\nclass ReflectionHelper\n{\n    /**\n     * Retrieves all properties (including private ones), from object and all its ancestors.\n     *\n     * Standard \\ReflectionClass->getProperties() does not return private properties from ancestor classes.\n     *\n     * @author muratyaman@gmail.com\n     * @see http://php.net/manual/en/reflectionclass.getproperties.php\n     *\n     * @param ReflectionClass $ref\n     *\n     * @return ReflectionProperty[]\n     */\n    public static function getProperties(ReflectionClass $ref)\n    {\n        $props = $ref->getProperties();\n        $propsArr = array();\n\n        foreach ($props as $prop) {\n            $propertyName = $prop->getName();\n            $propsArr[$propertyName] = $prop;\n        }\n\n        if ($parentClass = $ref->getParentClass()) {\n            $parentPropsArr = self::getProperties($parentClass);\n            foreach ($propsArr as $key => $property) {\n                $parentPropsArr[$key] = $property;\n            }\n\n            return $parentPropsArr;\n        }\n\n        return $propsArr;\n    }\n\n    /**\n     * Retrieves property by name from object and all its ancestors.\n     *\n     * @param object|string $object\n     * @param string $name\n     *\n     * @throws PropertyException\n     * @throws ReflectionException\n     *\n     * @return ReflectionProperty\n     */\n    public static function getProperty($object, $name)\n    {\n        $reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object);\n\n        if ($reflection->hasProperty($name)) {\n            return $reflection->getProperty($name);\n        }\n\n        if ($parentClass = $reflection->getParentClass()) {\n            return self::getProperty($parentClass->getName(), $name);\n        }\n\n        throw new PropertyException(\n            sprintf(\n                'The class \"%s\" doesn\\'t have a property with the given name: \"%s\".',\n                is_object($object) ? get_class($object) : $object,\n                $name\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Date;\n\nuse DateInterval;\nuse DeepCopy\\TypeFilter\\TypeFilter;\n\n/**\n * @final\n *\n * @deprecated Will be removed in 2.0. This filter will no longer be necessary in PHP 7.1+.\n */\nclass DateIntervalFilter implements TypeFilter\n{\n\n    /**\n     * {@inheritdoc}\n     *\n     * @param DateInterval $element\n     *\n     * @see http://news.php.net/php.bugs/205076\n     */\n    public function apply($element)\n    {\n        $copy = new DateInterval('P0D');\n\n        foreach ($element as $propertyName => $propertyValue) {\n            $copy->{$propertyName} = $propertyValue;\n        }\n\n        return $copy;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/Date/DatePeriodFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Date;\n\nuse DatePeriod;\nuse DeepCopy\\TypeFilter\\TypeFilter;\n\n/**\n * @final\n */\nclass DatePeriodFilter implements TypeFilter\n{\n    /**\n     * {@inheritdoc}\n     *\n     * @param DatePeriod $element\n     *\n     * @see http://news.php.net/php.bugs/205076\n     */\n    public function apply($element)\n    {\n        $options = 0;\n        if (PHP_VERSION_ID >= 80200 && $element->include_end_date) {\n            $options |= DatePeriod::INCLUDE_END_DATE;\n        }\n        if (!$element->include_start_date) {\n            $options |= DatePeriod::EXCLUDE_START_DATE;\n        }\n\n        if ($element->getEndDate()) {\n            return new DatePeriod($element->getStartDate(), $element->getDateInterval(), $element->getEndDate(), $options);\n        }\n\n        if (PHP_VERSION_ID >= 70217) {\n            $recurrences = $element->getRecurrences();\n        } else {\n            $recurrences = $element->recurrences - $element->include_start_date;\n        }\n\n        return new DatePeriod($element->getStartDate(), $element->getDateInterval(), $recurrences, $options);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/ReplaceFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter;\n\n/**\n * @final\n */\nclass ReplaceFilter implements TypeFilter\n{\n    /**\n     * @var callable\n     */\n    protected $callback;\n\n    /**\n     * @param callable $callable Will be called to get the new value for each element to replace\n     */\n    public function __construct(callable $callable)\n    {\n        $this->callback = $callable;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function apply($element)\n    {\n        return call_user_func($this->callback, $element);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/ShallowCopyFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter;\n\n/**\n * @final\n */\nclass ShallowCopyFilter implements TypeFilter\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function apply($element)\n    {\n        return clone $element;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.php",
    "content": "<?php\nnamespace DeepCopy\\TypeFilter\\Spl;\n\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\TypeFilter;\n\n/**\n * In PHP 7.4 the storage of an ArrayObject isn't returned as\n * ReflectionProperty. So we deep copy its array copy.\n */\nfinal class ArrayObjectFilter implements TypeFilter\n{\n    /**\n     * @var DeepCopy\n     */\n    private $copier;\n\n    public function __construct(DeepCopy $copier)\n    {\n        $this->copier = $copier;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function apply($arrayObject)\n    {\n        $clone = clone $arrayObject;\n        foreach ($arrayObject->getArrayCopy() as $k => $v) {\n            $clone->offsetSet($k, $this->copier->copy($v));\n        }\n\n        return $clone;\n    }\n}\n\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Spl;\n\n/**\n * @deprecated Use {@see SplDoublyLinkedListFilter} instead.\n */\nclass SplDoublyLinkedList extends SplDoublyLinkedListFilter\n{\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Spl;\n\nuse Closure;\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\TypeFilter;\nuse SplDoublyLinkedList;\n\n/**\n * @final\n */\nclass SplDoublyLinkedListFilter implements TypeFilter\n{\n    private $copier;\n\n    public function __construct(DeepCopy $copier)\n    {\n        $this->copier = $copier;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function apply($element)\n    {\n        $newElement = clone $element;\n\n        $copy = $this->createCopyClosure();\n\n        return $copy($newElement);\n    }\n\n    private function createCopyClosure()\n    {\n        $copier = $this->copier;\n\n        $copy = function (SplDoublyLinkedList $list) use ($copier) {\n            // Replace each element in the list with a deep copy of itself\n            for ($i = 1; $i <= $list->count(); $i++) {\n                $copy = $copier->recursiveCopy($list->shift());\n\n                $list->push($copy);\n            }\n\n            return $list;\n        };\n\n        return Closure::bind($copy, null, DeepCopy::class);\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeFilter/TypeFilter.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeFilter;\n\ninterface TypeFilter\n{\n    /**\n     * Applies the filter to the object.\n     *\n     * @param mixed $element\n     */\n    public function apply($element);\n}\n"
  },
  {
    "path": "src/DeepCopy/TypeMatcher/TypeMatcher.php",
    "content": "<?php\n\nnamespace DeepCopy\\TypeMatcher;\n\nclass TypeMatcher\n{\n    /**\n     * @var string\n     */\n    private $type;\n\n    /**\n     * @param string $type\n     */\n    public function __construct($type)\n    {\n        $this->type = $type;\n    }\n\n    /**\n     * @param mixed $element\n     *\n     * @return boolean\n     */\n    public function matches($element)\n    {\n        return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type;\n    }\n}\n"
  },
  {
    "path": "src/DeepCopy/deep_copy.php",
    "content": "<?php\n\nnamespace DeepCopy;\n\nuse function function_exists;\n\nif (false === function_exists('DeepCopy\\deep_copy')) {\n    /**\n     * Deep copies the given value.\n     *\n     * @param mixed $value\n     * @param bool  $useCloneMethod\n     *\n     * @return mixed\n     */\n    function deep_copy($value, $useCloneMethod = false)\n    {\n        return (new DeepCopy($useCloneMethod))->copy($value);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/DeepCopyTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest;\n\nuse ArrayObject;\nuse DateInterval;\nuse DatePeriod;\nuse DateTime;\nuse DateTimeImmutable;\nuse DateTimeZone;\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\Exception\\CloneException;\nuse DeepCopy\\f001;\nuse DeepCopy\\f002;\nuse DeepCopy\\f003;\nuse DeepCopy\\f004;\nuse DeepCopy\\f005;\nuse DeepCopy\\f006;\nuse DeepCopy\\f007;\nuse DeepCopy\\f008;\nuse DeepCopy\\f009;\nuse DeepCopy\\f011;\nuse DeepCopy\\f012\\Suit;\nuse DeepCopy\\f013;\nuse DeepCopy\\f014;\nuse DeepCopy\\Filter\\ChainableFilter;\nuse DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter;\nuse DeepCopy\\Filter\\KeepFilter;\nuse DeepCopy\\Filter\\SetNullFilter;\nuse DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher;\nuse DeepCopy\\Matcher\\PropertyNameMatcher;\nuse DeepCopy\\Matcher\\PropertyTypeMatcher;\nuse DeepCopy\\TypeFilter\\ReplaceFilter;\nuse DeepCopy\\TypeFilter\\ShallowCopyFilter;\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\nuse PHPUnit\\Framework\\TestCase;\nuse RecursiveArrayIterator;\nuse SplDoublyLinkedList;\nuse stdClass;\nuse function DeepCopy\\deep_copy;\n\n/**\n * @covers \\DeepCopy\\DeepCopy\n */\nclass DeepCopyTest extends TestCase\n{\n    /**\n     * @dataProvider provideScalarValues\n     */\n    public function test_it_can_copy_scalar_values($value)\n    {\n        $copy = deep_copy($value);\n\n        $this->assertSame($value, $copy);\n    }\n\n    public function provideScalarValues()\n    {\n        return [\n            [true],\n            ['string'],\n            [null],\n            [10],\n            [-1],\n            [.5],\n        ];\n    }\n\n    public function test_it_can_copy_an_array_of_scalar_values()\n    {\n        $copy = deep_copy([10, 20]);\n\n        $this->assertSame([10, 20], $copy);\n    }\n\n    public function test_it_can_copy_an_object()\n    {\n        $object = new stdClass();\n\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object, $copy);\n    }\n\n    public function test_it_can_copy_an_array_of_objects()\n    {\n        $object = [new stdClass()];\n\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object, $copy);\n        $this->assertEqualButNotSame($object[0], $copy[0]);\n    }\n\n    /**\n     * @dataProvider provideObjectWithScalarValues\n     */\n    public function test_it_can_copy_an_object_with_scalar_properties($object, $expectedVal)\n    {\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object, $copy);\n        $this->assertSame($expectedVal, $copy->prop);\n    }\n\n    public function provideObjectWithScalarValues()\n    {\n        $createObject = function ($val) {\n            $object = new stdClass();\n\n            $object->prop = $val;\n\n            return $object;\n        };\n\n        return array_map(\n            function (array $vals) use ($createObject) {\n                return [$createObject($vals[0]), $vals[0]];\n            },\n            $this->provideScalarValues()\n        );\n    }\n\n    public function test_it_can_copy_an_object_with_an_object_property()\n    {\n        $foo = new stdClass();\n        $bar = new stdClass();\n\n        $foo->bar = $bar;\n\n        $copy = deep_copy($foo);\n\n        $this->assertEqualButNotSame($foo, $copy);\n        $this->assertEqualButNotSame($foo->bar, $copy->bar);\n    }\n\n    public function test_it_copies_dynamic_properties()\n    {\n        $foo = new stdClass();\n        $bar = new stdClass();\n\n        $foo->bar = $bar;\n\n        $copy = deep_copy($foo);\n\n        $this->assertEqualButNotSame($foo, $copy);\n        $this->assertEqualButNotSame($foo->bar, $copy->bar);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/issues/38\n     * @ticket https://github.com/myclabs/DeepCopy/pull/70\n     * @ticket https://github.com/myclabs/DeepCopy/pull/76\n     */\n    public function test_it_can_copy_an_object_with_a_date_object_property()\n    {\n        $object = new stdClass();\n\n        $object->d1 = new DateTime();\n        $object->d2 = new DateTimeImmutable();\n        $object->dtz = new DateTimeZone('UTC');\n        $object->di = new DateInterval('P2D');\n        $object->dp = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3);\n\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object->d1, $copy->d1);\n        $this->assertEqualButNotSame($object->d2, $copy->d2);\n        $this->assertEqualButNotSame($object->dtz, $copy->dtz);\n        $this->assertEqualButNotSame($object->di, $copy->di);\n        $this->assertEqualButNotSame($object->dp, $copy->dp);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/pull/70\n     */\n    public function test_it_skips_the_copy_for_userland_datetimezone()\n    {\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(\n            new SetNullFilter(),\n            new PropertyNameMatcher('cloned')\n        );\n\n        $object = new stdClass();\n\n        $object->dtz = new f007\\FooDateTimeZone('UTC');\n\n        $copy = $deepCopy->copy($object);\n\n        $this->assertTrue($copy->dtz->cloned);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/pull/76\n     */\n    public function test_it_skips_the_copy_for_userland_dateinterval()\n    {\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(\n            new SetNullFilter(),\n            new PropertyNameMatcher('cloned')\n        );\n\n        $object = new stdClass();\n\n        $object->di = new f007\\FooDateInterval('P2D');\n\n        $copy = $deepCopy->copy($object);\n\n        $this->assertFalse($copy->di->cloned);\n    }\n\n    public function test_it_copies_the_private_properties_of_the_parent_class()\n    {\n        $object = new f001\\B();\n\n        $object->setAProp($aStdClass = new stdClass());\n        $object->setBProp($bStdClass = new stdClass());\n\n        /** @var f001\\B $copy */\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($aStdClass, $copy->getAProp());\n        $this->assertEqualButNotSame($bStdClass, $copy->getBProp());\n    }\n\n    public function test_it_keeps_reference_of_the_copied_objects_when_copying_the_graph()\n    {\n        $a = new f002\\A();\n\n        $b = new stdClass();\n        $c = new stdClass();\n\n        $a->setProp1($b);\n        $a->setProp2($c);\n\n        $b->c = $c;\n\n        /** @var f002\\A $copy */\n        $copy = deep_copy($a);\n\n        $this->assertEqualButNotSame($a, $copy);\n        $this->assertEqualButNotSame($b, $copy->getProp1());\n        $this->assertEqualButNotSame($c, $copy->getProp2());\n\n        $this->assertSame($copy->getProp1()->c, $copy->getProp2());\n    }\n\n    public function test_it_can_copy_graphs_with_circular_references()\n    {\n        $a = new stdClass();\n        $b = new stdClass();\n\n        $a->prop = $b;\n        $b->prop = $a;\n\n        $copy = deep_copy($a);\n\n        $this->assertEqualButNotSame($a, $copy);\n        $this->assertEqualButNotSame($b, $copy->prop);\n\n        $this->assertSame($copy, $copy->prop->prop);\n    }\n\n    public function test_it_can_copy_graphs_with_circular_references_with_userland_class()\n    {\n        $a = new f003\\Foo('a');\n        $b = new f003\\Foo('b');\n\n        $a->setProp($b);\n        $b->setProp($a);\n\n        /** @var f003\\Foo $copy */\n        $copy = deep_copy($a);\n\n        $this->assertEqualButNotSame($a, $copy);\n        $this->assertEqualButNotSame($b, $copy->getProp());\n\n        $this->assertSame($copy, $copy->getProp()->getProp());\n    }\n\n    public function test_it_cannot_copy_unclonable_items()\n    {\n        $object = new f004\\UnclonableItem();\n\n        try {\n            deep_copy($object);\n\n            $this->fail('Expected exception to be thrown.');\n        } catch (CloneException $exception) {\n            $this->assertSame(\n                sprintf(\n                    'The class \"%s\" is not cloneable.',\n                    f004\\UnclonableItem::class\n                ),\n                $exception->getMessage()\n            );\n            $this->assertSame(0, $exception->getCode());\n            $this->assertNull($exception->getPrevious());\n        }\n    }\n\n    public function test_it_can_skip_uncloneable_objects()\n    {\n        $object = new f004\\UnclonableItem();\n\n        $deepCopy = new DeepCopy();\n        $deepCopy->skipUncloneable(true);\n\n        $copy = $deepCopy->copy($object);\n\n        $this->assertSame($object, $copy);\n    }\n\n    public function test_it_uses_the_userland_defined_cloned_method()\n    {\n        $object = new f005\\Foo();\n\n        $copy = deep_copy($object);\n\n        $this->assertTrue($copy->cloned);\n    }\n\n    public function test_it_only_uses_the_userland_defined_cloned_method_when_configured_to_do_so()\n    {\n        $object = new f005\\Foo();\n        $object->foo = new stdClass();\n\n        $copy = deep_copy($object, true);\n\n        $this->assertTrue($copy->cloned);\n        $this->assertSame($object->foo, $copy->foo);\n    }\n\n    public function test_it_uses_type_filter_to_copy_objects_if_matcher_matches()\n    {\n        $deepCopy = new DeepCopy();\n        $deepCopy->addTypeFilter(\n            new ShallowCopyFilter(),\n            new TypeMatcher(f006\\A::class)\n        );\n\n        $a = new f006\\A;\n        $b = new f006\\B;\n\n        $a->setAProp($b);\n\n        /** @var f006\\A $copy */\n        $copy = $deepCopy->copy($a);\n\n        $this->assertTrue($copy->cloned);\n        $this->assertFalse($copy->getAProp()->cloned);\n        $this->assertSame($b, $copy->getAProp());\n    }\n\n    public function test_it_uses_filters_to_copy_object_properties_if_matcher_matches()\n    {\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(\n            new SetNullFilter(),\n            new PropertyNameMatcher('cloned')\n        );\n\n        $a = new f006\\A;\n        $b = new f006\\B;\n\n        $a->setAProp($b);\n\n        /** @var f006\\A $copy */\n        $copy = $deepCopy->copy($a);\n\n        $this->assertNull($copy->cloned);\n        $this->assertNull($copy->getAProp()->cloned);\n    }\n\n    public function test_it_uses_the_first_filter_matching_for_copying_object_properties()\n    {\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(\n            new SetNullFilter(),\n            new PropertyNameMatcher('cloned')\n        );\n        $deepCopy->addFilter(\n            new KeepFilter(),\n            new PropertyNameMatcher('cloned')\n        );\n\n        $a = new f006\\A;\n        $b = new f006\\B;\n\n        $a->setAProp($b);\n\n        /** @var f006\\A $copy */\n        $copy = $deepCopy->copy($a);\n\n        $this->assertNull($copy->cloned);\n        $this->assertNull($copy->getAProp()->cloned);\n    }\n\n    public function test_it_can_deep_copy_an_array_object()\n    {\n        $foo = new f003\\Foo('foo');\n        $foo->setProp('bar');\n        $object = new ArrayObject(['foo' => $foo, ArrayObject::ARRAY_AS_PROPS, \\RecursiveArrayIterator::class]);\n\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object, $copy);\n        $this->assertEqualButNotSame($foo, $copy['foo']);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/issues/152\n     */\n    public function test_it_clones_objects_extending_array_object()\n    {\n        $object = new f011\\ArrayObjectExtended('foo');\n        $object->setFlags(ArrayObject::ARRAY_AS_PROPS);\n        $object->setIteratorClass(\\RecursiveArrayIterator::class);\n        $object['a'] = new f011\\ArrayObjectExtended('bar');\n\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object, $copy);\n        $this->assertEqualButNotSame($object['a'], $copy['a']);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/pull/49\n     */\n    public function test_it_can_copy_a_SplDoublyLinkedList()\n    {\n        $object = new SplDoublyLinkedList();\n\n        $a = new stdClass();\n        $b = new stdClass();\n\n        $a->b = $b;\n\n        $object->push($a);\n\n        /** @var SplDoublyLinkedList $copy */\n        $copy = deep_copy($object);\n\n        $this->assertEqualButNotSame($object, $copy);\n\n        $aCopy = $copy->pop();\n\n        $this->assertEqualButNotSame($b, $aCopy->b);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/issues/62\n     */\n    public function test_matchers_can_access_to_parent_private_properties()\n    {\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(new SetNullFilter(), new PropertyTypeMatcher(stdClass::class));\n\n        $object = new f008\\B(new stdClass());\n\n        /** @var f008\\B $copy */\n        $copy = $deepCopy->copy($object);\n\n        $this->assertNull($copy->getFoo());\n    }\n\n    public function test_it_can_prepend_filter()\n    {\n        $object = new f008\\A('bar');\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(new KeepFilter(), new PropertyNameMatcher('foo'));\n        $deepCopy->prependFilter(new SetNullFilter(), new PropertyNameMatcher('foo'));\n        $copy = $deepCopy->copy($object);\n        $this->assertNull($copy->getFoo());\n    }\n\n    public function test_it_can_prepend_type_filter()\n    {\n        $object = new f008\\A('bar');\n        $deepCopy = new DeepCopy();\n        $deepCopy->addTypeFilter(new ReplaceFilter(function ($object) {\n            return new f008\\A('baz');\n        }), new TypeMatcher(f008\\A::class));\n        $deepCopy->prependTypeFilter(new ReplaceFilter(function ($object) {\n            return new f008\\A('foo');\n        }), new TypeMatcher(f008\\A::class));\n\n        $copy = $deepCopy->copy($object);\n        $this->assertEquals('foo',$copy->getFoo());\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/issues/143\n     * @requires PHP 7.4\n     */\n    public function test_it_clones_typed_properties()\n    {\n        $object = new f009\\TypedProperty();\n        $object->foo = 123;\n\n        $deepCopy = new DeepCopy();\n        $copy = $deepCopy->copy($object);\n\n        $this->assertSame(123, $copy->foo);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/issues/143\n     * @requires PHP 7.4\n     */\n    public function test_it_ignores_uninitialized_typed_properties()\n    {\n        $object = new f009\\TypedProperty();\n\n        $deepCopy = new DeepCopy();\n        $copy = $deepCopy->copy($object);\n\n        $this->assertFalse(isset($copy->foo));\n    }\n\n    /**\n     * @requires PHP 8.1\n     */\n    public function test_it_keeps_enums()\n    {\n        $enum = Suit::Clubs;\n\n        $copy = (new DeepCopy())->copy($enum);\n\n        $this->assertSame($enum, $copy);\n    }\n\n    /**\n     * @ticket https://github.com/myclabs/DeepCopy/issues/98\n     */\n    public function test_it_can_apply_two_filters_with_chainable_filter()\n    {\n        $object = new f013\\A();\n\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());\n        $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('foo'));\n\n        $copy = $deepCopy->copy($object);\n\n        $this->assertNull($copy->foo);\n    }\n\n    public function test_it_can_copy_property_after_applying_doctrine_proxy_filter_with_chainable_filter()\n    {\n        $object = new f013\\B();\n        $object->setFoo(new f013\\C());\n\n        $deepCopy = new DeepCopy();\n        $deepCopy->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());\n\n        /** @var f013\\B $copy */\n        $copy = $deepCopy->copy($object);\n\n        $this->assertNotEquals($copy->getFoo(), $object->getFoo());\n    }\n\n    /**\n     * @requires PHP 8.1\n     */\n    public function test_it_can_copy_object_with_readonly_property()\n    {\n        $scalarProperties = new f014\\ReadonlyScalarProperty();\n        $objectProperties = new f014\\ReadonlyObjectProperty();\n\n        $deepCopy = new DeepCopy();\n\n        $scalarPropertiesCopy = $deepCopy->copy($scalarProperties);\n        $objectPropertiesCopy = $deepCopy->copy($objectProperties);\n\n        $this->assertEqualButNotSame($scalarProperties, $scalarPropertiesCopy);\n        $this->assertEqualButNotSame($objectProperties, $objectPropertiesCopy);\n    }\n\n    private function assertEqualButNotSame($expected, $val)\n    {\n        $this->assertEquals($expected, $val);\n        $this->assertNotSame($expected, $val);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Filter/Doctrine/DoctrineCollectionFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter\n */\nclass DoctrineCollectionFilterTest extends TestCase\n{\n    public function test_it_copies_the_object_property_array_collection()\n    {\n        $object = new stdClass();\n        $oldCollection = new ArrayCollection();\n        $oldCollection->add($stdClass = new stdClass());\n        $object->foo = $oldCollection;\n\n        $filter = new DoctrineCollectionFilter();\n\n        $filter->apply(\n            $object,\n            'foo',\n            function($item) {\n                return null;\n            }\n        );\n\n        $this->assertInstanceOf(Collection::class, $object->foo);\n        $this->assertNotSame($oldCollection, $object->foo);\n        $this->assertCount(1, $object->foo);\n\n        $objectOfNewCollection = $object->foo->get(0);\n\n        $this->assertNotSame($stdClass, $objectOfNewCollection);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Filter/Doctrine/DoctrineEmptyCollectionFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter\n */\nclass DoctrineEmptyCollectionFilterTest extends TestCase\n{\n    public function test_it_sets_the_object_property_to_an_empty_doctrine_collection()\n    {\n        $object = new stdClass();\n\n        $collection = new ArrayCollection();\n        $collection->add(new stdClass());\n\n        $object->foo = $collection;\n\n        $filter = new DoctrineEmptyCollectionFilter();\n\n        $filter->apply(\n            $object,\n            'foo',\n            function($item) {\n                return null;\n            }\n        );\n\n        $this->assertInstanceOf(Collection::class, $object->foo);\n        $this->assertNotSame($collection, $object->foo);\n        $this->assertTrue($object->foo->isEmpty());\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Filter/Doctrine/DoctrineProxyFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter\\Doctrine;\n\nuse BadMethodCallException;\nuse DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * @covers \\DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter\n */\nclass DoctrineProxyFilterTest extends TestCase\n{\n    public function test_it_loads_the_doctrine_proxy()\n    {\n        $foo = new Foo();\n\n        $filter = new DoctrineProxyFilter();\n\n        $filter->apply(\n            $foo,\n            'unknown',\n            function($item) {\n                throw new BadMethodCallException('Did not expect to be called.');\n            }\n        );\n\n        $this->assertTrue($foo->isLoaded);\n    }\n}\n\nclass Foo\n{\n    public $isLoaded = false;\n\n    public function __load()\n    {\n        $this->isLoaded = true;\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Filter/KeepFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter;\n\nuse DeepCopy\\Filter\\KeepFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Filter\\KeepFilter\n */\nclass KeepFilterTest extends TestCase\n{\n    public function test_it_does_not_change_the_filtered_object_property()\n    {\n        $object = new stdClass();\n        $keepObject = new stdClass();\n        $object->foo = $keepObject;\n\n        $filter = new KeepFilter();\n\n        $filter->apply($object, 'foo', null);\n\n        $this->assertSame($keepObject, $object->foo);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Filter/ReplaceFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter;\n\nuse DeepCopy\\Filter\\ReplaceFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Filter\\ReplaceFilter\n */\nclass ReplaceFilterTest extends TestCase\n{\n    /**\n     * @dataProvider provideCallbacks\n     */\n    public function test_it_applies_the_callback_to_the_specified_property(callable $callback, array $expected)\n    {\n        $object = new stdClass();\n        $object->data = [\n            'foo' => 'bar',\n            'baz' => 'foo'\n        ];\n\n        $filter = new ReplaceFilter($callback);\n\n        $filter->apply(\n            $object,\n            'data',\n            function () {\n                return null;\n            }\n            );\n\n        $this->assertEquals($expected, $object->data);\n    }\n\n    public function provideCallbacks()\n    {\n        return [\n            [\n                function ($data) {\n                    unset($data['baz']);\n\n                    return $data;\n                },\n                ['foo' => 'bar']\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Filter/SetNullFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter;\n\nuse DeepCopy\\Filter\\SetNullFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Filter\\SetNullFilter\n */\nclass SetNullFilterTest extends TestCase\n{\n    public function test_it_sets_the_given_property_to_null()\n    {\n        $filter = new SetNullFilter();\n\n        $object = new stdClass();\n        $object->foo = 'bar';\n        $object->bim = 'bam';\n\n        $filter->apply($object, 'foo', null);\n\n        $this->assertNull($object->foo);\n        $this->assertSame('bam', $object->bim);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Matcher/Doctrine/DoctrineProxyMatcherTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher\\Doctrine;\n\nuse BadMethodCallException;\nuse DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher;\nuse Doctrine\\Persistence\\Proxy;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher\n */\nclass DoctrineProxyMatcherTest extends TestCase\n{\n    /**\n     * @dataProvider providePairs\n     */\n    public function test_it_matches_the_given_objects($object, $expected)\n    {\n        $matcher = new DoctrineProxyMatcher();\n\n        $actual = $matcher->matches($object, 'unknown');\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    public function providePairs()\n    {\n        return [\n            [new FooProxy(), true],\n            [new stdClass(), false],\n        ];\n    }\n}\n\nclass FooProxy implements Proxy\n{\n    /**\n     * @inheritdoc\n     */\n    public function __load(): void\n    {\n        throw new BadMethodCallException();\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function __isInitialized(): bool\n    {\n        throw new BadMethodCallException();\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Matcher/PropertyMatcherTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher;\n\nuse DeepCopy\\Matcher\\PropertyMatcher;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * @covers \\DeepCopy\\Matcher\\PropertyMatcher\n */\nclass PropertyMatcherTest extends TestCase\n{\n    /**\n     * @dataProvider providePairs\n     */\n    public function test_it_matches_the_given_objects($object, $prop, $expected)\n    {\n        $matcher = new PropertyMatcher(X::class, 'foo');\n\n        $actual = $matcher->matches($object, $prop);\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    public function providePairs()\n    {\n        return [\n            'matching case' => [new X(), 'foo', true],\n            'match class, non matching prop' => [new X(), 'bar', false],\n            'match class, unknown prop' => [new X(), 'unknown', false],\n            'non matching class, matching prop' => [new Y(), 'unknown', false],\n        ];\n    }\n}\n\nclass X\n{\n    public $foo;\n    public $bar;\n}\n\nclass Y\n{\n    public $foo;\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Matcher/PropertyNameMatcherTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher;\n\nuse DeepCopy\\Matcher\\PropertyNameMatcher;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Matcher\\PropertyNameMatcher\n */\nclass PropertyNameMatcherTest extends TestCase\n{\n    /**\n     * @dataProvider providePairs\n     */\n    public function test_it_matches_the_given_property($object, $prop, $expected)\n    {\n        $matcher = new PropertyNameMatcher('foo');\n\n        $actual = $matcher->matches($object, $prop);\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    public function providePairs()\n    {\n        return [\n            [new stdClass(), 'foo', true],\n            [new stdClass(), 'unknown', false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Matcher/PropertyTypeMatcherTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher;\n\nuse DeepCopy\\f009;\nuse DeepCopy\\Matcher\\PropertyTypeMatcher;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\Matcher\\PropertyTypeMatcher\n */\nclass PropertyTypeMatcherTest extends TestCase\n{\n    /**\n     * @dataProvider providePairs\n     */\n    public function test_it_matches_the_given_property($object, $expected)\n    {\n        $matcher = new PropertyTypeMatcher(PropertyTypeMatcherTestFixture2::class);\n\n        $actual = $matcher->matches($object, 'foo');\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    /**\n     * @requires PHP 7.4\n     */\n    public function test_it_ignores_uninitialized_typed_properties()\n    {\n        $object = new f009\\TypedObjectProperty();\n\n        $matcher = new PropertyTypeMatcher(\\DateTime::class);\n\n        $this->assertFalse($matcher->matches($object, 'date'));\n    }\n\n    /**\n     * @requires PHP 7.4\n     */\n    public function test_it_matches_initialized_typed_properties()\n    {\n        $object = new f009\\TypedObjectProperty();\n        $object->date = new \\DateTime();\n\n        $matcher = new PropertyTypeMatcher(\\DateTime::class);\n\n        $this->assertTrue($matcher->matches($object, 'date'));\n    }\n\n    public function providePairs()\n    {\n        $object1 = new PropertyTypeMatcherTestFixture1();\n        $object1->foo = new PropertyTypeMatcherTestFixture2();\n\n        $object2 = new PropertyTypeMatcherTestFixture1();\n        $object2->foo = new stdClass();\n\n        $object3 = new PropertyTypeMatcherTestFixture1();\n        $object3->foo = true;\n\n        return [\n            [new PropertyTypeMatcherTestFixture1(), false],\n            [$object1, true],\n            [$object2, false],\n            [$object3, false],\n            [new stdClass(), false],\n        ];\n    }\n}\n\nclass PropertyTypeMatcherTestFixture1\n{\n    public $foo;\n}\n\nclass PropertyTypeMatcherTestFixture2\n{\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/Reflection/ReflectionHelperTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Reflection;\n\nuse DeepCopy\\Exception\\PropertyException;\nuse DeepCopy\\Reflection\\ReflectionHelper;\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionClass;\nuse ReflectionProperty;\n\n/**\n * @covers \\DeepCopy\\Reflection\\ReflectionHelper\n */\nclass ReflectionHelperTest extends TestCase\n{\n    public function test_it_retrieves_the_object_properties()\n    {\n        $child = new ReflectionHelperTestChild();\n        $childReflectionClass = new ReflectionClass($child);\n\n        $expectedProps = array(\n            'a1',\n            'a2',\n            'a3',\n            'a10',\n            'a11',\n            'a12',\n            'a20',\n            'a21',\n            'a22',\n            'a100',\n            'a101',\n            'a102',\n        );\n\n        $actualProps = ReflectionHelper::getProperties($childReflectionClass);\n\n        $this->assertSame($expectedProps, array_keys($actualProps));\n    }\n\n    /**\n     * @dataProvider provideProperties\n     */\n    public function test_it_can_retrieve_an_object_property($name)\n    {\n        $object = new ReflectionHelperTestChild();\n\n        $property = ReflectionHelper::getProperty($object, $name);\n\n        $this->assertInstanceOf(ReflectionProperty::class, $property);\n\n        $this->assertSame($name, $property->getName());\n    }\n\n    public function provideProperties()\n    {\n        return [\n            'public property' => ['a10'],\n            'private property' => ['a102'],\n            'private property of ancestor' => ['a3']\n        ];\n    }\n\n    public function test_it_cannot_retrieve_a_non_existent_prperty()\n    {\n        $object = new ReflectionHelperTestChild();\n\n        $this->expectException(PropertyException::class);\n        ReflectionHelper::getProperty($object, 'non existent property');\n    }\n}\n\nclass ReflectionHelperTestParent\n{\n    public $a1;\n    protected $a2;\n    private $a3;\n\n    public $a10;\n    protected $a11;\n    private $a12;\n\n    public static $a20;\n    protected static $a21;\n    private static $a22;\n}\n\nclass ReflectionHelperTestChild extends ReflectionHelperTestParent\n{\n    public $a1;\n    protected $a2;\n    private $a3;\n\n    public $a100;\n    protected $a101;\n    private $a102;\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeFilter/Date/DateIntervalFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Date;\n\nuse DateInterval;\nuse DeepCopy\\TypeFilter\\Date\\DateIntervalFilter;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * @covers \\DeepCopy\\TypeFilter\\Date\\DateIntervalFilter\n */\nclass DateIntervalFilterTest extends TestCase\n{\n    public function test_it_deep_copies_a_DateInterval()\n    {\n        $object = new DateInterval('P2D');;\n\n        $filter = new DateIntervalFilter();\n\n        $copy = $filter->apply($object);\n\n        $this->assertEquals($object, $copy);\n        $this->assertNotSame($object, $copy);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeFilter/Date/DatePeriodFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Date;\n\nuse DateInterval;\nuse DatePeriod;\nuse DateTime;\nuse DeepCopy\\TypeFilter\\Date\\DateIntervalFilter;\nuse DeepCopy\\TypeFilter\\Date\\DatePeriodFilter;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * @covers \\DeepCopy\\TypeFilter\\Date\\DatePeriodFilter\n */\nclass DatePeriodFilterTest extends TestCase\n{\n    public function test_it_deep_copies_a_DatePeriod()\n    {\n        $object = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3);\n\n        $filter = new DatePeriodFilter();\n\n        $copy = $filter->apply($object);\n\n        $this->assertEquals($object, $copy);\n        $this->assertNotSame($object, $copy);\n    }\n\n    public function test_it_deep_copies_a_DatePeriod_with_exclude_start_date()\n    {\n        $object = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3, DatePeriod::EXCLUDE_START_DATE);\n\n        $filter = new DatePeriodFilter();\n\n        $copy = $filter->apply($object);\n\n        $this->assertEquals($object, $copy);\n        $this->assertNotSame($object, $copy);\n    }\n\n    /**\n     * @requires PHP 8.2\n     */\n    public function test_it_deep_copies_a_DatePeriod_with_include_end_date()\n    {\n        $object = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3, DatePeriod::INCLUDE_END_DATE);\n\n        $filter = new DatePeriodFilter();\n\n        $copy = $filter->apply($object);\n\n        $this->assertEquals($object, $copy);\n        $this->assertNotSame($object, $copy);\n    }\n\n    public function test_it_deep_copies_a_DatePeriod_with_end_date()\n    {\n        $object = new DatePeriod(new DateTime(), new DateInterval('P2D'), new DateTime('+2 days'));\n\n        $filter = new DatePeriodFilter();\n\n        $copy = $filter->apply($object);\n\n        $this->assertEquals($object, $copy);\n        $this->assertNotSame($object, $copy);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeFilter/ReplaceFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter;\n\nuse DeepCopy\\TypeFilter\\ReplaceFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\TypeFilter\\ReplaceFilter\n */\nclass ReplaceFilterTest extends TestCase\n{\n    public function test_it_returns_the_callback_called_with_the_given_object()\n    {\n        $foo = new stdClass();\n\n        $callback = function ($object) {\n            $object = new stdClass();\n            $object->callback = true;\n\n            return $object;\n        };\n\n        $filter = new ReplaceFilter($callback);\n\n        $newFoo = $filter->apply($foo);\n\n        $this->assertNotSame($newFoo, $foo);\n        $this->assertTrue($newFoo->callback);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeFilter/ShallowCopyFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter;\n\nuse DeepCopy\\TypeFilter\\ShallowCopyFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\TypeFilter\\ShallowCopyFilter\n */\nclass ShallowCopyFilterTest extends TestCase\n{\n    public function test_it_shallow_copies_the_given_object()\n    {\n        $foo = new stdClass();\n        $bar = new stdClass();\n\n        $foo->bar = $bar;\n\n        $filter = new ShallowCopyFilter();\n\n        $newFoo = $filter->apply($foo);\n\n        $this->assertNotSame($foo, $newFoo);\n        $this->assertSame($foo->bar, $newFoo->bar);\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeFilter/Spl/ArrayObjectFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Spl;\n\nuse ArrayObject;\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\Spl\\ArrayObjectFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Prophecy\\ObjectProphecy;\nuse RecursiveArrayIterator;\n\n/**\n * @author Dominic Tubach <dominic.tubach@to.com>\n *\n * @covers \\DeepCopy\\TypeFilter\\Spl\\ArrayObjectFilter\n */\nfinal class ArrayObjectFilterTest extends TestCase\n{\n    /**\n     * @var ArrayObjectFilter\n     */\n    private $arrayObjectFilter;\n\n    /**\n     * @var DeepCopy|ObjectProphecy\n     */\n    private $copierProphecy;\n\n    protected function setUp(): void\n    {\n        $this->copierProphecy = $this->prophesize(DeepCopy::class);\n        $this->arrayObjectFilter = new ArrayObjectFilter(\n            $this->copierProphecy->reveal()\n        );\n    }\n\n    public function test_it_deep_copies_an_array_object(): void\n    {\n        $arrayObject = new ArrayObject(['foo' => 'bar'], ArrayObject::ARRAY_AS_PROPS, RecursiveArrayIterator::class);\n        $this->copierProphecy->copy('bar')->willReturn('baz');\n\n        /** @var \\ArrayObject $newArrayObject */\n        $newArrayObject = $this->arrayObjectFilter->apply($arrayObject);\n        $this->assertSame(['foo' => 'baz'], $newArrayObject->getArrayCopy());\n        $this->assertSame(ArrayObject::ARRAY_AS_PROPS, $newArrayObject->getFlags());\n        $this->assertSame(RecursiveArrayIterator::class, $newArrayObject->getIteratorClass());\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeFilter/Spl/SplDoublyLinkedListFilterTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Spl;\n\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter;\nuse PHPUnit\\Framework\\TestCase;\nuse SplDoublyLinkedList;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter\n */\nclass SplDoublyLinkedListFilterTest extends TestCase\n{\n    public function test_it_deep_copies_a_doubly_linked_spl_list()\n    {\n        $foo = new stdClass();\n\n        $list = new SplDoublyLinkedList();\n        $list->push($foo);\n\n        $filter = new SplDoublyLinkedListFilter(new FakeDeepCopy());\n\n        $newList = $filter->apply($list);\n\n        $this->assertCount(1, $newList);\n        $this->assertNotSame($foo, $newList->next());\n    }\n}\n\nclass FakeDeepCopy extends DeepCopy\n{\n    /**\n     * @inheritdoc\n     */\n    public function copy($object)\n    {\n        return new stdClass();\n    }\n}\n"
  },
  {
    "path": "tests/DeepCopyTest/TypeMatcher/TypeMatcherTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeMatcher;\n\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\n/**\n * @covers \\DeepCopy\\TypeMatcher\\TypeMatcher\n */\nclass TypeMatcherTest extends TestCase\n{\n    /**\n     * @dataProvider provideElements\n     */\n    public function test_it_retrieves_the_object_properties($type, $element, $expected)\n    {\n        $matcher = new TypeMatcher($type);\n\n        $actual = $matcher->matches($element);\n\n        $this->assertSame($expected, $actual);\n    }\n\n    public function provideElements()\n    {\n        return [\n            '[class] same class as type' => ['stdClass', new stdClass(), true],\n            '[class] different class as type' => ['stdClass', new Foo(), false],\n            '[class] child class as type' => [Foo::class, new Bar(), true],\n            '[class] interface implementation as type' => [IA::class, new A(), true],\n\n            '[scalar] array match' => ['array', [], true],\n            '[scalar] array no match' => ['array', true, false],\n        ];\n    }\n}\n\nclass Foo\n{\n}\n\nclass Bar extends Foo\n{\n}\n\ninterface IA\n{\n}\n\nclass A implements IA\n{\n}\n"
  }
]