{"id":13562652,"url":"https://github.com/myclabs/DeepCopy","last_synced_at":"2025-04-03T19:31:18.883Z","repository":{"id":9649328,"uuid":"11584853","full_name":"myclabs/DeepCopy","owner":"myclabs","description":"Create deep copies (clones) of your objects","archived":false,"fork":false,"pushed_at":"2024-06-12T14:40:45.000Z","size":265,"stargazers_count":8764,"open_issues_count":18,"forks_count":103,"subscribers_count":26,"default_branch":"1.x","last_synced_at":"2024-10-29T19:18:35.993Z","etag":null,"topics":["clone","clone-deep"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/myclabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":"packagist/myclabs/deep-copy","community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2013-07-22T15:28:03.000Z","updated_at":"2024-10-29T14:30:25.000Z","dependencies_parsed_at":"2022-07-31T23:38:59.403Z","dependency_job_id":"b557a8c9-98f0-4ef7-90e8-7c5c20cbac8c","html_url":"https://github.com/myclabs/DeepCopy","commit_stats":{"total_commits":140,"total_committers":27,"mean_commits":5.185185185185185,"dds":0.6857142857142857,"last_synced_commit":"928a96f585b86224ebc78f8f09d0482cf15b04f5"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myclabs%2FDeepCopy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myclabs%2FDeepCopy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myclabs%2FDeepCopy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myclabs%2FDeepCopy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/myclabs","download_url":"https://codeload.github.com/myclabs/DeepCopy/tar.gz/refs/heads/1.x","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222113284,"owners_count":16933624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clone","clone-deep"],"created_at":"2024-08-01T13:01:10.789Z","updated_at":"2024-11-04T15:30:27.846Z","avatar_url":"https://github.com/myclabs.png","language":"PHP","readme":"# 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-\u003ecopy($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-\u003ecopy($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-\u003ecopy($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-\u003eid; // 123\n\n$copier = new DeepCopy();\n$copier-\u003eaddFilter(new SetNullFilter(), new PropertyNameMatcher('id'));\n\n$copy = $copier-\u003ecopy($object);\n\necho $copy-\u003eid; // 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-\u003eaddFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category'));\n\n$copy = $copier-\u003ecopy($object);\n// $copy-\u003ecategory 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-\u003eaddFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());\n$copier-\u003eaddFilter(new SetNullFilter(), new PropertyNameMatcher('id'));\n\n$copy = $copier-\u003ecopy($object);\n\necho $copy-\u003eid; // 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-\u003eaddFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\\Common\\Collections\\Collection'));\n\n$copy = $copier-\u003ecopy($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-\u003eaddFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty'));\n\n$copy = $copier-\u003ecopy($object);\n\n// $copy-\u003emyProperty 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-\u003eaddFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());\n\n$copy = $copier-\u003ecopy($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-\u003eaddFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title'));\n\n$copy = $copier-\u003ecopy($object);\n\n// $copy-\u003etitle 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-\u003eaddTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass'));\n\n$copy = $copier-\u003ecopy([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-\u003edeepCopy = new DeepCopy();\n$this-\u003edeepCopy-\u003eaddTypeFilter(\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\u0026utm_medium=referral\u0026utm_campaign=readme).\n","funding_links":["https://tidelift.com/funding/github/packagist/myclabs/deep-copy","https://tidelift.com/subscription/pkg/packagist-myclabs-deep-copy?utm_source=packagist-myclabs-deep-copy\u0026utm_medium=referral\u0026utm_campaign=readme"],"categories":["PHP","配置 Configuration","数据结构和存储( Data Structure and Storage )"],"sub_categories":["杂项 Miscellaneous"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyclabs%2FDeepCopy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmyclabs%2FDeepCopy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyclabs%2FDeepCopy/lists"}