Repository: myclabs/DeepCopy
Branch: 1.x
Commit: 5ee4e978b7fe
Files: 76
Total size: 80.7 KB
Directory structure:
gitextract_qwehwyz_/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── ci.yaml
├── .gitignore
├── .scrutinizer.yml
├── LICENSE
├── README.md
├── composer.json
├── fixtures/
│ ├── f001/
│ │ ├── A.php
│ │ └── B.php
│ ├── f002/
│ │ └── A.php
│ ├── f003/
│ │ └── Foo.php
│ ├── f004/
│ │ └── UnclonableItem.php
│ ├── f005/
│ │ └── Foo.php
│ ├── f006/
│ │ ├── A.php
│ │ └── B.php
│ ├── f007/
│ │ ├── FooDateInterval.php
│ │ └── FooDateTimeZone.php
│ ├── f008/
│ │ ├── A.php
│ │ └── B.php
│ ├── f009/
│ │ ├── TypedObjectProperty.php
│ │ └── TypedProperty.php
│ ├── f011/
│ │ └── ArrayObjectExtended.php
│ ├── f012/
│ │ └── Suit.php
│ ├── f013/
│ │ ├── A.php
│ │ ├── B.php
│ │ └── C.php
│ └── f014/
│ ├── ReadonlyObjectProperty.php
│ └── ReadonlyScalarProperty.php
├── phpunit.xml.dist
├── src/
│ └── DeepCopy/
│ ├── DeepCopy.php
│ ├── Exception/
│ │ ├── CloneException.php
│ │ └── PropertyException.php
│ ├── Filter/
│ │ ├── ChainableFilter.php
│ │ ├── Doctrine/
│ │ │ ├── DoctrineCollectionFilter.php
│ │ │ ├── DoctrineEmptyCollectionFilter.php
│ │ │ └── DoctrineProxyFilter.php
│ │ ├── Filter.php
│ │ ├── KeepFilter.php
│ │ ├── ReplaceFilter.php
│ │ └── SetNullFilter.php
│ ├── Matcher/
│ │ ├── Doctrine/
│ │ │ └── DoctrineProxyMatcher.php
│ │ ├── Matcher.php
│ │ ├── PropertyMatcher.php
│ │ ├── PropertyNameMatcher.php
│ │ └── PropertyTypeMatcher.php
│ ├── Reflection/
│ │ └── ReflectionHelper.php
│ ├── TypeFilter/
│ │ ├── Date/
│ │ │ ├── DateIntervalFilter.php
│ │ │ └── DatePeriodFilter.php
│ │ ├── ReplaceFilter.php
│ │ ├── ShallowCopyFilter.php
│ │ ├── Spl/
│ │ │ ├── ArrayObjectFilter.php
│ │ │ ├── SplDoublyLinkedList.php
│ │ │ └── SplDoublyLinkedListFilter.php
│ │ └── TypeFilter.php
│ ├── TypeMatcher/
│ │ └── TypeMatcher.php
│ └── deep_copy.php
└── tests/
└── DeepCopyTest/
├── DeepCopyTest.php
├── Filter/
│ ├── Doctrine/
│ │ ├── DoctrineCollectionFilterTest.php
│ │ ├── DoctrineEmptyCollectionFilterTest.php
│ │ └── DoctrineProxyFilterTest.php
│ ├── KeepFilterTest.php
│ ├── ReplaceFilterTest.php
│ └── SetNullFilterTest.php
├── Matcher/
│ ├── Doctrine/
│ │ └── DoctrineProxyMatcherTest.php
│ ├── PropertyMatcherTest.php
│ ├── PropertyNameMatcherTest.php
│ └── PropertyTypeMatcherTest.php
├── Reflection/
│ └── ReflectionHelperTest.php
├── TypeFilter/
│ ├── Date/
│ │ ├── DateIntervalFilterTest.php
│ │ └── DatePeriodFilterTest.php
│ ├── ReplaceFilterTest.php
│ ├── ShallowCopyFilterTest.php
│ └── Spl/
│ ├── ArrayObjectFilterTest.php
│ └── SplDoublyLinkedListFilterTest.php
└── TypeMatcher/
└── TypeMatcherTest.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
*.png binary
.github/ export-ignore
doc/ export-ignore
fixtures/ export-ignore
tests/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.scrutinizer.yml export-ignore
.travis.yml export-ignore
phpunit.xml.dist export-ignore
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: "packagist/myclabs/deep-copy"
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/workflows/ci.yaml
================================================
name: "Continuous Integration"
on:
- pull_request
- push
env:
COMPOSER_ROOT_VERSION: 1.99
jobs:
composer-json-lint:
name: "Lint composer.json"
runs-on: "ubuntu-latest"
strategy:
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout repository"
uses: "actions/checkout@v6"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
tools: "composer-normalize"
- name: "Get composer cache directory"
id: "composercache"
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: "Cache dependencies"
uses: "actions/cache@v4"
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-
- name: "Install dependencies"
run: "composer update --no-interaction --no-progress"
- name: "Validate composer.json"
run: "composer validate --strict"
- name: "Normalize composer.json"
run: "composer-normalize --dry-run"
tests:
name: "Tests"
runs-on: "ubuntu-latest"
strategy:
matrix:
php-version:
- "7.1"
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dependencies:
- "lowest"
- "highest"
steps:
- name: "Checkout repository"
uses: "actions/checkout@v6"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1"
- name: "Get composer cache directory"
id: "composercache"
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: "Cache dependencies"
uses: "actions/cache@v4"
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-
- name: "Install lowest dependencies"
if: ${{ matrix.dependencies == 'lowest' }}
run: "composer update --no-interaction --no-progress --prefer-lowest"
- name: "Install highest dependencies"
if: ${{ matrix.dependencies == 'highest' }}
run: "composer update --no-interaction --no-progress"
- name: "Run tests"
timeout-minutes: 3
run: "vendor/bin/phpunit"
================================================
FILE: .gitignore
================================================
/composer.phar
/composer.lock
/vendor/*
.phpunit.result.cache
================================================
FILE: .scrutinizer.yml
================================================
build:
environment:
variables:
COMPOSER_ROOT_VERSION: '1.8.0'
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2013 My C-Sense
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# DeepCopy
DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph.
[](https://packagist.org/packages/myclabs/deep-copy)
[](https://github.com/myclabs/DeepCopy/actions/workflows/ci.yaml)
## Table of Contents
1. [How](#how)
1. [Why](#why)
1. [Using simply `clone`](#using-simply-clone)
1. [Overriding `__clone()`](#overriding-__clone)
1. [With `DeepCopy`](#with-deepcopy)
1. [How it works](#how-it-works)
1. [Going further](#going-further)
1. [Matchers](#matchers)
1. [Property name](#property-name)
1. [Specific property](#specific-property)
1. [Type](#type)
1. [Filters](#filters)
1. [`SetNullFilter`](#setnullfilter-filter)
1. [`KeepFilter`](#keepfilter-filter)
1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter-filter)
1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter-filter)
1. [`DoctrineProxyFilter`](#doctrineproxyfilter-filter)
1. [`ReplaceFilter`](#replacefilter-type-filter)
1. [`ShallowCopyFilter`](#shallowcopyfilter-type-filter)
1. [Edge cases](#edge-cases)
1. [Contributing](#contributing)
1. [Tests](#tests)
## How?
Install with Composer:
```
composer require myclabs/deep-copy
```
Use it:
```php
use DeepCopy\DeepCopy;
$copier = new DeepCopy();
$myCopy = $copier->copy($myObject);
```
## Why?
- How do you create copies of your objects?
```php
$myCopy = clone $myObject;
```
- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)?
You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior
yourself.
- But how do you handle **cycles** in the association graph?
Now you're in for a big mess :(

### Using simply `clone`

### Overriding `__clone()`

### With `DeepCopy`

## How it works
DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it
keeps a hash map of all instances and thus preserves the object graph.
To use it:
```php
use function DeepCopy\deep_copy;
$copy = deep_copy($var);
```
Alternatively, you can create your own `DeepCopy` instance to configure it differently for example:
```php
use DeepCopy\DeepCopy;
$copier = new DeepCopy(true);
$copy = $copier->copy($var);
```
You may want to roll your own deep copy function:
```php
namespace Acme;
use DeepCopy\DeepCopy;
function deep_copy($var)
{
static $copier = null;
if (null === $copier) {
$copier = new DeepCopy(true);
}
return $copier->copy($var);
}
```
## Going further
You can add filters to customize the copy process.
The method to add a filter is `DeepCopy\DeepCopy::addFilter($filter, $matcher)`,
with `$filter` implementing `DeepCopy\Filter\Filter`
and `$matcher` implementing `DeepCopy\Matcher\Matcher`.
We provide some generic filters and matchers.
### Matchers
- `DeepCopy\Matcher` applies on a object attribute.
- `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements.
#### Property name
The `PropertyNameMatcher` will match a property by its name:
```php
use DeepCopy\Matcher\PropertyNameMatcher;
// Will apply a filter to any property of any objects named "id"
$matcher = new PropertyNameMatcher('id');
```
#### Specific property
The `PropertyMatcher` will match a specific property of a specific class:
```php
use DeepCopy\Matcher\PropertyMatcher;
// Will apply a filter to the property "id" of any objects of the class "MyClass"
$matcher = new PropertyMatcher('MyClass', 'id');
```
#### Type
The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of
[gettype()](http://php.net/manual/en/function.gettype.php) function):
```php
use DeepCopy\TypeMatcher\TypeMatcher;
// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection
$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection');
```
### Filters
- `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher`
- `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher`
By design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied).
Using the ([`ChainableFilter`](#chainablefilter-filter)) won't stop the chain of filters.
#### `SetNullFilter` (filter)
Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have
any ID:
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\SetNullFilter;
use DeepCopy\Matcher\PropertyNameMatcher;
$object = MyClass::load(123);
echo $object->id; // 123
$copier = new DeepCopy();
$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));
$copy = $copier->copy($object);
echo $copy->id; // null
```
#### `KeepFilter` (filter)
If you want a property to remain untouched (for example, an association to an object):
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\KeepFilter;
use DeepCopy\Matcher\PropertyMatcher;
$copier = new DeepCopy();
$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category'));
$copy = $copier->copy($object);
// $copy->category has not been touched
```
#### `ChainableFilter` (filter)
If you use cloning on proxy classes, you might want to apply two filters for:
1. loading the data
2. applying a transformation
You can use the `ChainableFilter` as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e.
the next ones may be applied).
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\ChainableFilter;
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
use DeepCopy\Filter\SetNullFilter;
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;
use DeepCopy\Matcher\PropertyNameMatcher;
$copier = new DeepCopy();
$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());
$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));
$copy = $copier->copy($object);
echo $copy->id; // null
```
#### `DoctrineCollectionFilter` (filter)
If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`:
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter;
use DeepCopy\Matcher\PropertyTypeMatcher;
$copier = new DeepCopy();
$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection'));
$copy = $copier->copy($object);
```
#### `DoctrineEmptyCollectionFilter` (filter)
If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the
`DoctrineEmptyCollectionFilter`
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter;
use DeepCopy\Matcher\PropertyMatcher;
$copier = new DeepCopy();
$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty'));
$copy = $copier->copy($object);
// $copy->myProperty will return an empty collection
```
#### `DoctrineProxyFilter` (filter)
If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a
Doctrine proxy class (...\\\_\_CG\_\_\Proxy).
You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class.
**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded
before other filters are applied!**
We recommend to decorate the `DoctrineProxyFilter` with the `ChainableFilter` to allow applying other filters to the
cloned lazy loaded entities.
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;
$copier = new DeepCopy();
$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());
$copy = $copier->copy($object);
// $copy should now contain a clone of all entities, including those that were not yet fully loaded.
```
#### `ReplaceFilter` (type filter)
1. If you want to replace the value of a property:
```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\ReplaceFilter;
use DeepCopy\Matcher\PropertyMatcher;
$copier = new DeepCopy();
$callback = function ($currentValue) {
return $currentValue . ' (copy)'
};
$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title'));
$copy = $copier->copy($object);
// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)'
```
2. If you want to replace whole element:
```php
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\ReplaceFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
$copier = new DeepCopy();
$callback = function (MyClass $myClass) {
return get_class($myClass);
};
$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass'));
$copy = $copier->copy([new MyClass, 'some string', new MyClass]);
// $copy will contain ['MyClass', 'some string', 'MyClass']
```
The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable.
#### `ShallowCopyFilter` (type filter)
Stop *DeepCopy* from recursively copying element, using standard `clone` instead:
```php
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\ShallowCopyFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use Mockery as m;
$this->deepCopy = new DeepCopy();
$this->deepCopy->addTypeFilter(
new ShallowCopyFilter,
new TypeMatcher(m\MockInterface::class)
);
$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class));
// All mocks will be just cloned, not deep copied
```
## Edge cases
The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are
not applied. There is two ways for you to handle them:
- Implement your own `__clone()` method
- Use a filter with a type matcher
## Contributing
DeepCopy is distributed under the MIT license.
### Tests
Running the tests is simple:
```php
vendor/bin/phpunit
```
### Support
Get 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).
================================================
FILE: composer.json
================================================
{
"name": "myclabs/deep-copy",
"description": "Create deep copies (clones) of your objects",
"license": "MIT",
"type": "library",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"conflict": {
"doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"autoload": {
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
},
"files": [
"src/DeepCopy/deep_copy.php"
]
},
"autoload-dev": {
"psr-4": {
"DeepCopyTest\\": "tests/DeepCopyTest/",
"DeepCopy\\": "fixtures/"
}
},
"config": {
"sort-packages": true
}
}
================================================
FILE: fixtures/f001/A.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f001;
class A
{
private $aProp;
public function getAProp()
{
return $this->aProp;
}
public function setAProp($prop)
{
$this->aProp = $prop;
return $this;
}
}
================================================
FILE: fixtures/f001/B.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f001;
class B extends A
{
private $bProp;
public function getBProp()
{
return $this->bProp;
}
public function setBProp($prop)
{
$this->bProp = $prop;
return $this;
}
}
================================================
FILE: fixtures/f002/A.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f002;
class A
{
private $prop1;
private $prop2;
public function getProp1()
{
return $this->prop1;
}
public function setProp1($prop)
{
$this->prop1 = $prop;
return $this;
}
public function getProp2()
{
return $this->prop2;
}
public function setProp2($prop)
{
$this->prop2 = $prop;
return $this;
}
}
================================================
FILE: fixtures/f003/Foo.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f003;
class Foo
{
private $name;
private $prop;
public function __construct($name)
{
$this->name = $name;
}
public function getProp()
{
return $this->prop;
}
public function setProp($prop)
{
$this->prop = $prop;
return $this;
}
}
================================================
FILE: fixtures/f004/UnclonableItem.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f004;
use BadMethodCallException;
class UnclonableItem
{
private function __clone()
{
throw new BadMethodCallException('Unsupported call.');
}
}
================================================
FILE: fixtures/f005/Foo.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f005;
class Foo
{
public $cloned = false;
public function __clone()
{
$this->cloned = true;
}
}
================================================
FILE: fixtures/f006/A.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f006;
class A
{
public $cloned = false;
private $aProp;
public function getAProp()
{
return $this->aProp;
}
public function setAProp($prop)
{
$this->aProp = $prop;
return $this;
}
public function __clone()
{
$this->cloned = true;
}
}
================================================
FILE: fixtures/f006/B.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f006;
class B
{
public $cloned = false;
private $bProp;
public function getBProp()
{
return $this->bProp;
}
public function setBProp($prop)
{
$this->bProp = $prop;
return $this;
}
public function __clone()
{
$this->cloned = true;
}
}
================================================
FILE: fixtures/f007/FooDateInterval.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f007;
use DateInterval;
class FooDateInterval extends DateInterval
{
public $cloned = false;
public function __clone()
{
$this->cloned = true;
}
}
================================================
FILE: fixtures/f007/FooDateTimeZone.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f007;
use DateTimeZone;
class FooDateTimeZone extends DateTimeZone
{
public $cloned = false;
public function __clone()
{
$this->cloned = true;
}
}
================================================
FILE: fixtures/f008/A.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f008;
class A
{
private $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function getFoo()
{
return $this->foo;
}
}
================================================
FILE: fixtures/f008/B.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f008;
class B extends A
{
}
================================================
FILE: fixtures/f009/TypedObjectProperty.php
================================================
<?php
declare(strict_types=1);
namespace DeepCopy\f009;
class TypedObjectProperty
{
public \DateTime $date;
}
================================================
FILE: fixtures/f009/TypedProperty.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f009;
class TypedProperty
{
public int $foo;
}
================================================
FILE: fixtures/f011/ArrayObjectExtended.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f011;
use ArrayObject;
class ArrayObjectExtended extends ArrayObject
{
public $x;
public function __construct($x)
{
parent::__construct();
$this->x = $x;
}
}
================================================
FILE: fixtures/f012/Suit.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f012;
enum Suit: string
{
case Hearts = 'Hearts';
case Diamonds = 'Diamonds';
case Clubs = 'Clubs';
case Spades = 'Spades';
}
================================================
FILE: fixtures/f013/A.php
================================================
<?php
namespace DeepCopy\f013;
use BadMethodCallException;
use Doctrine\Persistence\Proxy;
class A implements Proxy
{
public $foo = 1;
/**
* @inheritdoc
*/
public function __load(): void
{
}
/**
* @inheritdoc
*/
public function __isInitialized(): bool
{
throw new BadMethodCallException();
}
}
================================================
FILE: fixtures/f013/B.php
================================================
<?php
namespace DeepCopy\f013;
use BadMethodCallException;
use Doctrine\Persistence\Proxy;
class B implements Proxy
{
private $foo;
/**
* @inheritdoc
*/
public function __load(): void
{
}
/**
* @inheritdoc
*/
public function __isInitialized(): bool
{
throw new BadMethodCallException();
}
public function getFoo()
{
return $this->foo;
}
public function setFoo($foo)
{
$this->foo = $foo;
}
}
================================================
FILE: fixtures/f013/C.php
================================================
<?php
namespace DeepCopy\f013;
class C
{
public $foo = 1;
public function __clone()
{
$this->foo = null;
}
}
================================================
FILE: fixtures/f014/ReadonlyObjectProperty.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f014;
class ReadonlyObjectProperty
{
public readonly ReadonlyScalarProperty $foo;
public function __construct()
{
$this->foo = new ReadonlyScalarProperty();
}
}
================================================
FILE: fixtures/f014/ReadonlyScalarProperty.php
================================================
<?php declare(strict_types=1);
namespace DeepCopy\f014;
class ReadonlyScalarProperty
{
public readonly string $foo;
public readonly int $bar;
public readonly array $baz;
public function __construct()
{
$this->foo = 'foo';
$this->bar = 1;
$this->baz = [];
}
}
================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true">
<testsuites>
<testsuite name="Test suite">
<directory>./tests/DeepCopyTest</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
<exclude>
<file>src/DeepCopy/deep_copy.php</file>
</exclude>
</whitelist>
</filter>
</phpunit>
================================================
FILE: src/DeepCopy/DeepCopy.php
================================================
<?php
namespace DeepCopy;
use ArrayObject;
use DateInterval;
use DatePeriod;
use DateTimeInterface;
use DateTimeZone;
use DeepCopy\Exception\CloneException;
use DeepCopy\Filter\ChainableFilter;
use DeepCopy\Filter\Filter;
use DeepCopy\Matcher\Matcher;
use DeepCopy\Reflection\ReflectionHelper;
use DeepCopy\TypeFilter\Date\DateIntervalFilter;
use DeepCopy\TypeFilter\Date\DatePeriodFilter;
use DeepCopy\TypeFilter\Spl\ArrayObjectFilter;
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedListFilter;
use DeepCopy\TypeFilter\TypeFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use ReflectionObject;
use ReflectionProperty;
use SplDoublyLinkedList;
/**
* @final
*/
class DeepCopy
{
/**
* @var object[] List of objects copied.
*/
private $hashMap = [];
/**
* Filters to apply.
*
* @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
*/
private $filters = [];
/**
* Type Filters to apply.
*
* @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
*/
private $typeFilters = [];
/**
* @var bool
*/
private $skipUncloneable = false;
/**
* @var bool
*/
private $useCloneMethod;
/**
* @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used
* instead of the regular deep cloning.
*/
public function __construct($useCloneMethod = false)
{
$this->useCloneMethod = $useCloneMethod;
$this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class));
$this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class));
$this->addTypeFilter(new DatePeriodFilter(), new TypeMatcher(DatePeriod::class));
$this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class));
}
/**
* If enabled, will not throw an exception when coming across an uncloneable property.
*
* @param $skipUncloneable
*
* @return $this
*/
public function skipUncloneable($skipUncloneable = true)
{
$this->skipUncloneable = $skipUncloneable;
return $this;
}
/**
* Deep copies the given object.
*
* @template TObject
*
* @param TObject $object
*
* @return TObject
*/
public function copy($object)
{
$this->hashMap = [];
return $this->recursiveCopy($object);
}
public function addFilter(Filter $filter, Matcher $matcher)
{
$this->filters[] = [
'matcher' => $matcher,
'filter' => $filter,
];
}
public function prependFilter(Filter $filter, Matcher $matcher)
{
array_unshift($this->filters, [
'matcher' => $matcher,
'filter' => $filter,
]);
}
public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
{
$this->typeFilters[] = [
'matcher' => $matcher,
'filter' => $filter,
];
}
public function prependTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
{
array_unshift($this->typeFilters, [
'matcher' => $matcher,
'filter' => $filter,
]);
}
private function recursiveCopy($var)
{
// Matches Type Filter
if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) {
return $filter->apply($var);
}
// Resource
if (is_resource($var)) {
return $var;
}
// Array
if (is_array($var)) {
return $this->copyArray($var);
}
// Scalar
if (! is_object($var)) {
return $var;
}
// Enum
if (PHP_VERSION_ID >= 80100 && enum_exists(get_class($var))) {
return $var;
}
// Object
return $this->copyObject($var);
}
/**
* Copy an array
* @param array $array
* @return array
*/
private function copyArray(array $array)
{
foreach ($array as $key => $value) {
$array[$key] = $this->recursiveCopy($value);
}
return $array;
}
/**
* Copies an object.
*
* @param object $object
*
* @throws CloneException
*
* @return object
*/
private function copyObject($object)
{
$objectHash = spl_object_hash($object);
if (isset($this->hashMap[$objectHash])) {
return $this->hashMap[$objectHash];
}
$reflectedObject = new ReflectionObject($object);
$isCloneable = $reflectedObject->isCloneable();
if (false === $isCloneable) {
if ($this->skipUncloneable) {
$this->hashMap[$objectHash] = $object;
return $object;
}
throw new CloneException(
sprintf(
'The class "%s" is not cloneable.',
$reflectedObject->getName()
)
);
}
$newObject = clone $object;
$this->hashMap[$objectHash] = $newObject;
if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) {
return $newObject;
}
if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) {
return $newObject;
}
foreach (ReflectionHelper::getProperties($reflectedObject) as $property) {
$this->copyObjectProperty($newObject, $property);
}
return $newObject;
}
private function copyObjectProperty($object, ReflectionProperty $property)
{
// Ignore static properties
if ($property->isStatic()) {
return;
}
// Ignore readonly properties
if (method_exists($property, 'isReadOnly') && $property->isReadOnly()) {
return;
}
// Apply the filters
foreach ($this->filters as $item) {
/** @var Matcher $matcher */
$matcher = $item['matcher'];
/** @var Filter $filter */
$filter = $item['filter'];
if ($matcher->matches($object, $property->getName())) {
$filter->apply(
$object,
$property->getName(),
function ($object) {
return $this->recursiveCopy($object);
}
);
if ($filter instanceof ChainableFilter) {
continue;
}
// If a filter matches, we stop processing this property
return;
}
}
if (PHP_VERSION_ID < 80100) {
$property->setAccessible(true);
}
// Ignore uninitialized properties (for PHP >7.4)
if (method_exists($property, 'isInitialized') && !$property->isInitialized($object)) {
return;
}
$propertyValue = $property->getValue($object);
// Copy the property
$property->setValue($object, $this->recursiveCopy($propertyValue));
}
/**
* Returns first filter that matches variable, `null` if no such filter found.
*
* @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and
* 'matcher' with value of type {@see TypeMatcher}
* @param mixed $var
*
* @return TypeFilter|null
*/
private function getFirstMatchedTypeFilter(array $filterRecords, $var)
{
$matched = $this->first(
$filterRecords,
function (array $record) use ($var) {
/* @var TypeMatcher $matcher */
$matcher = $record['matcher'];
return $matcher->matches($var);
}
);
return isset($matched) ? $matched['filter'] : null;
}
/**
* Returns first element that matches predicate, `null` if no such element found.
*
* @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
* @param callable $predicate Predicate arguments are: element.
*
* @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher'
* with value of type {@see TypeMatcher} or `null`.
*/
private function first(array $elements, callable $predicate)
{
foreach ($elements as $element) {
if (call_user_func($predicate, $element)) {
return $element;
}
}
return null;
}
}
================================================
FILE: src/DeepCopy/Exception/CloneException.php
================================================
<?php
namespace DeepCopy\Exception;
use UnexpectedValueException;
class CloneException extends UnexpectedValueException
{
}
================================================
FILE: src/DeepCopy/Exception/PropertyException.php
================================================
<?php
namespace DeepCopy\Exception;
use ReflectionException;
class PropertyException extends ReflectionException
{
}
================================================
FILE: src/DeepCopy/Filter/ChainableFilter.php
================================================
<?php
namespace DeepCopy\Filter;
/**
* Defines a decorator filter that will not stop the chain of filters.
*/
class ChainableFilter implements Filter
{
/**
* @var Filter
*/
protected $filter;
public function __construct(Filter $filter)
{
$this->filter = $filter;
}
public function apply($object, $property, $objectCopier)
{
$this->filter->apply($object, $property, $objectCopier);
}
}
================================================
FILE: src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php
================================================
<?php
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
use DeepCopy\Reflection\ReflectionHelper;
/**
* @final
*/
class DoctrineCollectionFilter implements Filter
{
/**
* Copies the object property doctrine collection.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
if (PHP_VERSION_ID < 80100) {
$reflectionProperty->setAccessible(true);
}
$oldCollection = $reflectionProperty->getValue($object);
$newCollection = $oldCollection->map(
function ($item) use ($objectCopier) {
return $objectCopier($item);
}
);
$reflectionProperty->setValue($object, $newCollection);
}
}
================================================
FILE: src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php
================================================
<?php
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
use DeepCopy\Reflection\ReflectionHelper;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @final
*/
class DoctrineEmptyCollectionFilter implements Filter
{
/**
* Sets the object property to an empty doctrine collection.
*
* @param object $object
* @param string $property
* @param callable $objectCopier
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
if (PHP_VERSION_ID < 80100) {
$reflectionProperty->setAccessible(true);
}
$reflectionProperty->setValue($object, new ArrayCollection());
}
}
================================================
FILE: src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php
================================================
<?php
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
/**
* @final
*/
class DoctrineProxyFilter implements Filter
{
/**
* Triggers the magic method __load() on a Doctrine Proxy class to load the
* actual entity from the database.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$object->__load();
}
}
================================================
FILE: src/DeepCopy/Filter/Filter.php
================================================
<?php
namespace DeepCopy\Filter;
/**
* Filter to apply to a property while copying an object
*/
interface Filter
{
/**
* Applies the filter to the object.
*
* @param object $object
* @param string $property
* @param callable $objectCopier
*/
public function apply($object, $property, $objectCopier);
}
================================================
FILE: src/DeepCopy/Filter/KeepFilter.php
================================================
<?php
namespace DeepCopy\Filter;
class KeepFilter implements Filter
{
/**
* Keeps the value of the object property.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
// Nothing to do
}
}
================================================
FILE: src/DeepCopy/Filter/ReplaceFilter.php
================================================
<?php
namespace DeepCopy\Filter;
use DeepCopy\Reflection\ReflectionHelper;
/**
* @final
*/
class ReplaceFilter implements Filter
{
/**
* @var callable
*/
protected $callback;
/**
* @param callable $callable Will be called to get the new value for each property to replace
*/
public function __construct(callable $callable)
{
$this->callback = $callable;
}
/**
* Replaces the object property by the result of the callback called with the object property.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
if (PHP_VERSION_ID < 80100) {
$reflectionProperty->setAccessible(true);
}
$value = call_user_func($this->callback, $reflectionProperty->getValue($object));
$reflectionProperty->setValue($object, $value);
}
}
================================================
FILE: src/DeepCopy/Filter/SetNullFilter.php
================================================
<?php
namespace DeepCopy\Filter;
use DeepCopy\Reflection\ReflectionHelper;
/**
* @final
*/
class SetNullFilter implements Filter
{
/**
* Sets the object property to null.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
if (PHP_VERSION_ID < 80100) {
$reflectionProperty->setAccessible(true);
}
$reflectionProperty->setValue($object, null);
}
}
================================================
FILE: src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php
================================================
<?php
namespace DeepCopy\Matcher\Doctrine;
use DeepCopy\Matcher\Matcher;
use Doctrine\Persistence\Proxy;
/**
* @final
*/
class DoctrineProxyMatcher implements Matcher
{
/**
* Matches a Doctrine Proxy class.
*
* {@inheritdoc}
*/
public function matches($object, $property)
{
return $object instanceof Proxy;
}
}
================================================
FILE: src/DeepCopy/Matcher/Matcher.php
================================================
<?php
namespace DeepCopy\Matcher;
interface Matcher
{
/**
* @param object $object
* @param string $property
*
* @return boolean
*/
public function matches($object, $property);
}
================================================
FILE: src/DeepCopy/Matcher/PropertyMatcher.php
================================================
<?php
namespace DeepCopy\Matcher;
/**
* @final
*/
class PropertyMatcher implements Matcher
{
/**
* @var string
*/
private $class;
/**
* @var string
*/
private $property;
/**
* @param string $class Class name
* @param string $property Property name
*/
public function __construct($class, $property)
{
$this->class = $class;
$this->property = $property;
}
/**
* Matches a specific property of a specific class.
*
* {@inheritdoc}
*/
public function matches($object, $property)
{
return ($object instanceof $this->class) && $property == $this->property;
}
}
================================================
FILE: src/DeepCopy/Matcher/PropertyNameMatcher.php
================================================
<?php
namespace DeepCopy\Matcher;
/**
* @final
*/
class PropertyNameMatcher implements Matcher
{
/**
* @var string
*/
private $property;
/**
* @param string $property Property name
*/
public function __construct($property)
{
$this->property = $property;
}
/**
* Matches a property by its name.
*
* {@inheritdoc}
*/
public function matches($object, $property)
{
return $property == $this->property;
}
}
================================================
FILE: src/DeepCopy/Matcher/PropertyTypeMatcher.php
================================================
<?php
namespace DeepCopy\Matcher;
use DeepCopy\Reflection\ReflectionHelper;
use ReflectionException;
/**
* Matches a property by its type.
*
* It is recommended to use {@see DeepCopy\TypeFilter\TypeFilter} instead, as it applies on all occurrences
* of given type in copied context (eg. array elements), not just on object properties.
*
* @final
*/
class PropertyTypeMatcher implements Matcher
{
/**
* @var string
*/
private $propertyType;
/**
* @param string $propertyType Property type
*/
public function __construct($propertyType)
{
$this->propertyType = $propertyType;
}
/**
* {@inheritdoc}
*/
public function matches($object, $property)
{
try {
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
} catch (ReflectionException $exception) {
return false;
}
if (PHP_VERSION_ID < 80100) {
$reflectionProperty->setAccessible(true);
}
// Uninitialized properties (for PHP >7.4)
if (method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) {
// null instanceof $this->propertyType
return false;
}
return $reflectionProperty->getValue($object) instanceof $this->propertyType;
}
}
================================================
FILE: src/DeepCopy/Reflection/ReflectionHelper.php
================================================
<?php
namespace DeepCopy\Reflection;
use DeepCopy\Exception\PropertyException;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use ReflectionProperty;
class ReflectionHelper
{
/**
* Retrieves all properties (including private ones), from object and all its ancestors.
*
* Standard \ReflectionClass->getProperties() does not return private properties from ancestor classes.
*
* @author muratyaman@gmail.com
* @see http://php.net/manual/en/reflectionclass.getproperties.php
*
* @param ReflectionClass $ref
*
* @return ReflectionProperty[]
*/
public static function getProperties(ReflectionClass $ref)
{
$props = $ref->getProperties();
$propsArr = array();
foreach ($props as $prop) {
$propertyName = $prop->getName();
$propsArr[$propertyName] = $prop;
}
if ($parentClass = $ref->getParentClass()) {
$parentPropsArr = self::getProperties($parentClass);
foreach ($propsArr as $key => $property) {
$parentPropsArr[$key] = $property;
}
return $parentPropsArr;
}
return $propsArr;
}
/**
* Retrieves property by name from object and all its ancestors.
*
* @param object|string $object
* @param string $name
*
* @throws PropertyException
* @throws ReflectionException
*
* @return ReflectionProperty
*/
public static function getProperty($object, $name)
{
$reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object);
if ($reflection->hasProperty($name)) {
return $reflection->getProperty($name);
}
if ($parentClass = $reflection->getParentClass()) {
return self::getProperty($parentClass->getName(), $name);
}
throw new PropertyException(
sprintf(
'The class "%s" doesn\'t have a property with the given name: "%s".',
is_object($object) ? get_class($object) : $object,
$name
)
);
}
}
================================================
FILE: src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter\Date;
use DateInterval;
use DeepCopy\TypeFilter\TypeFilter;
/**
* @final
*
* @deprecated Will be removed in 2.0. This filter will no longer be necessary in PHP 7.1+.
*/
class DateIntervalFilter implements TypeFilter
{
/**
* {@inheritdoc}
*
* @param DateInterval $element
*
* @see http://news.php.net/php.bugs/205076
*/
public function apply($element)
{
$copy = new DateInterval('P0D');
foreach ($element as $propertyName => $propertyValue) {
$copy->{$propertyName} = $propertyValue;
}
return $copy;
}
}
================================================
FILE: src/DeepCopy/TypeFilter/Date/DatePeriodFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter\Date;
use DatePeriod;
use DeepCopy\TypeFilter\TypeFilter;
/**
* @final
*/
class DatePeriodFilter implements TypeFilter
{
/**
* {@inheritdoc}
*
* @param DatePeriod $element
*
* @see http://news.php.net/php.bugs/205076
*/
public function apply($element)
{
$options = 0;
if (PHP_VERSION_ID >= 80200 && $element->include_end_date) {
$options |= DatePeriod::INCLUDE_END_DATE;
}
if (!$element->include_start_date) {
$options |= DatePeriod::EXCLUDE_START_DATE;
}
if ($element->getEndDate()) {
return new DatePeriod($element->getStartDate(), $element->getDateInterval(), $element->getEndDate(), $options);
}
if (PHP_VERSION_ID >= 70217) {
$recurrences = $element->getRecurrences();
} else {
$recurrences = $element->recurrences - $element->include_start_date;
}
return new DatePeriod($element->getStartDate(), $element->getDateInterval(), $recurrences, $options);
}
}
================================================
FILE: src/DeepCopy/TypeFilter/ReplaceFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter;
/**
* @final
*/
class ReplaceFilter implements TypeFilter
{
/**
* @var callable
*/
protected $callback;
/**
* @param callable $callable Will be called to get the new value for each element to replace
*/
public function __construct(callable $callable)
{
$this->callback = $callable;
}
/**
* {@inheritdoc}
*/
public function apply($element)
{
return call_user_func($this->callback, $element);
}
}
================================================
FILE: src/DeepCopy/TypeFilter/ShallowCopyFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter;
/**
* @final
*/
class ShallowCopyFilter implements TypeFilter
{
/**
* {@inheritdoc}
*/
public function apply($element)
{
return clone $element;
}
}
================================================
FILE: src/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter\Spl;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
/**
* In PHP 7.4 the storage of an ArrayObject isn't returned as
* ReflectionProperty. So we deep copy its array copy.
*/
final class ArrayObjectFilter implements TypeFilter
{
/**
* @var DeepCopy
*/
private $copier;
public function __construct(DeepCopy $copier)
{
$this->copier = $copier;
}
/**
* {@inheritdoc}
*/
public function apply($arrayObject)
{
$clone = clone $arrayObject;
foreach ($arrayObject->getArrayCopy() as $k => $v) {
$clone->offsetSet($k, $this->copier->copy($v));
}
return $clone;
}
}
================================================
FILE: src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php
================================================
<?php
namespace DeepCopy\TypeFilter\Spl;
/**
* @deprecated Use {@see SplDoublyLinkedListFilter} instead.
*/
class SplDoublyLinkedList extends SplDoublyLinkedListFilter
{
}
================================================
FILE: src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter\Spl;
use Closure;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
use SplDoublyLinkedList;
/**
* @final
*/
class SplDoublyLinkedListFilter implements TypeFilter
{
private $copier;
public function __construct(DeepCopy $copier)
{
$this->copier = $copier;
}
/**
* {@inheritdoc}
*/
public function apply($element)
{
$newElement = clone $element;
$copy = $this->createCopyClosure();
return $copy($newElement);
}
private function createCopyClosure()
{
$copier = $this->copier;
$copy = function (SplDoublyLinkedList $list) use ($copier) {
// Replace each element in the list with a deep copy of itself
for ($i = 1; $i <= $list->count(); $i++) {
$copy = $copier->recursiveCopy($list->shift());
$list->push($copy);
}
return $list;
};
return Closure::bind($copy, null, DeepCopy::class);
}
}
================================================
FILE: src/DeepCopy/TypeFilter/TypeFilter.php
================================================
<?php
namespace DeepCopy\TypeFilter;
interface TypeFilter
{
/**
* Applies the filter to the object.
*
* @param mixed $element
*/
public function apply($element);
}
================================================
FILE: src/DeepCopy/TypeMatcher/TypeMatcher.php
================================================
<?php
namespace DeepCopy\TypeMatcher;
class TypeMatcher
{
/**
* @var string
*/
private $type;
/**
* @param string $type
*/
public function __construct($type)
{
$this->type = $type;
}
/**
* @param mixed $element
*
* @return boolean
*/
public function matches($element)
{
return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type;
}
}
================================================
FILE: src/DeepCopy/deep_copy.php
================================================
<?php
namespace DeepCopy;
use function function_exists;
if (false === function_exists('DeepCopy\deep_copy')) {
/**
* Deep copies the given value.
*
* @param mixed $value
* @param bool $useCloneMethod
*
* @return mixed
*/
function deep_copy($value, $useCloneMethod = false)
{
return (new DeepCopy($useCloneMethod))->copy($value);
}
}
================================================
FILE: tests/DeepCopyTest/DeepCopyTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest;
use ArrayObject;
use DateInterval;
use DatePeriod;
use DateTime;
use DateTimeImmutable;
use DateTimeZone;
use DeepCopy\DeepCopy;
use DeepCopy\Exception\CloneException;
use DeepCopy\f001;
use DeepCopy\f002;
use DeepCopy\f003;
use DeepCopy\f004;
use DeepCopy\f005;
use DeepCopy\f006;
use DeepCopy\f007;
use DeepCopy\f008;
use DeepCopy\f009;
use DeepCopy\f011;
use DeepCopy\f012\Suit;
use DeepCopy\f013;
use DeepCopy\f014;
use DeepCopy\Filter\ChainableFilter;
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
use DeepCopy\Filter\KeepFilter;
use DeepCopy\Filter\SetNullFilter;
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;
use DeepCopy\Matcher\PropertyNameMatcher;
use DeepCopy\Matcher\PropertyTypeMatcher;
use DeepCopy\TypeFilter\ReplaceFilter;
use DeepCopy\TypeFilter\ShallowCopyFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use PHPUnit\Framework\TestCase;
use RecursiveArrayIterator;
use SplDoublyLinkedList;
use stdClass;
use function DeepCopy\deep_copy;
/**
* @covers \DeepCopy\DeepCopy
*/
class DeepCopyTest extends TestCase
{
/**
* @dataProvider provideScalarValues
*/
public function test_it_can_copy_scalar_values($value)
{
$copy = deep_copy($value);
$this->assertSame($value, $copy);
}
public function provideScalarValues()
{
return [
[true],
['string'],
[null],
[10],
[-1],
[.5],
];
}
public function test_it_can_copy_an_array_of_scalar_values()
{
$copy = deep_copy([10, 20]);
$this->assertSame([10, 20], $copy);
}
public function test_it_can_copy_an_object()
{
$object = new stdClass();
$copy = deep_copy($object);
$this->assertEqualButNotSame($object, $copy);
}
public function test_it_can_copy_an_array_of_objects()
{
$object = [new stdClass()];
$copy = deep_copy($object);
$this->assertEqualButNotSame($object, $copy);
$this->assertEqualButNotSame($object[0], $copy[0]);
}
/**
* @dataProvider provideObjectWithScalarValues
*/
public function test_it_can_copy_an_object_with_scalar_properties($object, $expectedVal)
{
$copy = deep_copy($object);
$this->assertEqualButNotSame($object, $copy);
$this->assertSame($expectedVal, $copy->prop);
}
public function provideObjectWithScalarValues()
{
$createObject = function ($val) {
$object = new stdClass();
$object->prop = $val;
return $object;
};
return array_map(
function (array $vals) use ($createObject) {
return [$createObject($vals[0]), $vals[0]];
},
$this->provideScalarValues()
);
}
public function test_it_can_copy_an_object_with_an_object_property()
{
$foo = new stdClass();
$bar = new stdClass();
$foo->bar = $bar;
$copy = deep_copy($foo);
$this->assertEqualButNotSame($foo, $copy);
$this->assertEqualButNotSame($foo->bar, $copy->bar);
}
public function test_it_copies_dynamic_properties()
{
$foo = new stdClass();
$bar = new stdClass();
$foo->bar = $bar;
$copy = deep_copy($foo);
$this->assertEqualButNotSame($foo, $copy);
$this->assertEqualButNotSame($foo->bar, $copy->bar);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/issues/38
* @ticket https://github.com/myclabs/DeepCopy/pull/70
* @ticket https://github.com/myclabs/DeepCopy/pull/76
*/
public function test_it_can_copy_an_object_with_a_date_object_property()
{
$object = new stdClass();
$object->d1 = new DateTime();
$object->d2 = new DateTimeImmutable();
$object->dtz = new DateTimeZone('UTC');
$object->di = new DateInterval('P2D');
$object->dp = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3);
$copy = deep_copy($object);
$this->assertEqualButNotSame($object->d1, $copy->d1);
$this->assertEqualButNotSame($object->d2, $copy->d2);
$this->assertEqualButNotSame($object->dtz, $copy->dtz);
$this->assertEqualButNotSame($object->di, $copy->di);
$this->assertEqualButNotSame($object->dp, $copy->dp);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/pull/70
*/
public function test_it_skips_the_copy_for_userland_datetimezone()
{
$deepCopy = new DeepCopy();
$deepCopy->addFilter(
new SetNullFilter(),
new PropertyNameMatcher('cloned')
);
$object = new stdClass();
$object->dtz = new f007\FooDateTimeZone('UTC');
$copy = $deepCopy->copy($object);
$this->assertTrue($copy->dtz->cloned);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/pull/76
*/
public function test_it_skips_the_copy_for_userland_dateinterval()
{
$deepCopy = new DeepCopy();
$deepCopy->addFilter(
new SetNullFilter(),
new PropertyNameMatcher('cloned')
);
$object = new stdClass();
$object->di = new f007\FooDateInterval('P2D');
$copy = $deepCopy->copy($object);
$this->assertFalse($copy->di->cloned);
}
public function test_it_copies_the_private_properties_of_the_parent_class()
{
$object = new f001\B();
$object->setAProp($aStdClass = new stdClass());
$object->setBProp($bStdClass = new stdClass());
/** @var f001\B $copy */
$copy = deep_copy($object);
$this->assertEqualButNotSame($aStdClass, $copy->getAProp());
$this->assertEqualButNotSame($bStdClass, $copy->getBProp());
}
public function test_it_keeps_reference_of_the_copied_objects_when_copying_the_graph()
{
$a = new f002\A();
$b = new stdClass();
$c = new stdClass();
$a->setProp1($b);
$a->setProp2($c);
$b->c = $c;
/** @var f002\A $copy */
$copy = deep_copy($a);
$this->assertEqualButNotSame($a, $copy);
$this->assertEqualButNotSame($b, $copy->getProp1());
$this->assertEqualButNotSame($c, $copy->getProp2());
$this->assertSame($copy->getProp1()->c, $copy->getProp2());
}
public function test_it_can_copy_graphs_with_circular_references()
{
$a = new stdClass();
$b = new stdClass();
$a->prop = $b;
$b->prop = $a;
$copy = deep_copy($a);
$this->assertEqualButNotSame($a, $copy);
$this->assertEqualButNotSame($b, $copy->prop);
$this->assertSame($copy, $copy->prop->prop);
}
public function test_it_can_copy_graphs_with_circular_references_with_userland_class()
{
$a = new f003\Foo('a');
$b = new f003\Foo('b');
$a->setProp($b);
$b->setProp($a);
/** @var f003\Foo $copy */
$copy = deep_copy($a);
$this->assertEqualButNotSame($a, $copy);
$this->assertEqualButNotSame($b, $copy->getProp());
$this->assertSame($copy, $copy->getProp()->getProp());
}
public function test_it_cannot_copy_unclonable_items()
{
$object = new f004\UnclonableItem();
try {
deep_copy($object);
$this->fail('Expected exception to be thrown.');
} catch (CloneException $exception) {
$this->assertSame(
sprintf(
'The class "%s" is not cloneable.',
f004\UnclonableItem::class
),
$exception->getMessage()
);
$this->assertSame(0, $exception->getCode());
$this->assertNull($exception->getPrevious());
}
}
public function test_it_can_skip_uncloneable_objects()
{
$object = new f004\UnclonableItem();
$deepCopy = new DeepCopy();
$deepCopy->skipUncloneable(true);
$copy = $deepCopy->copy($object);
$this->assertSame($object, $copy);
}
public function test_it_uses_the_userland_defined_cloned_method()
{
$object = new f005\Foo();
$copy = deep_copy($object);
$this->assertTrue($copy->cloned);
}
public function test_it_only_uses_the_userland_defined_cloned_method_when_configured_to_do_so()
{
$object = new f005\Foo();
$object->foo = new stdClass();
$copy = deep_copy($object, true);
$this->assertTrue($copy->cloned);
$this->assertSame($object->foo, $copy->foo);
}
public function test_it_uses_type_filter_to_copy_objects_if_matcher_matches()
{
$deepCopy = new DeepCopy();
$deepCopy->addTypeFilter(
new ShallowCopyFilter(),
new TypeMatcher(f006\A::class)
);
$a = new f006\A;
$b = new f006\B;
$a->setAProp($b);
/** @var f006\A $copy */
$copy = $deepCopy->copy($a);
$this->assertTrue($copy->cloned);
$this->assertFalse($copy->getAProp()->cloned);
$this->assertSame($b, $copy->getAProp());
}
public function test_it_uses_filters_to_copy_object_properties_if_matcher_matches()
{
$deepCopy = new DeepCopy();
$deepCopy->addFilter(
new SetNullFilter(),
new PropertyNameMatcher('cloned')
);
$a = new f006\A;
$b = new f006\B;
$a->setAProp($b);
/** @var f006\A $copy */
$copy = $deepCopy->copy($a);
$this->assertNull($copy->cloned);
$this->assertNull($copy->getAProp()->cloned);
}
public function test_it_uses_the_first_filter_matching_for_copying_object_properties()
{
$deepCopy = new DeepCopy();
$deepCopy->addFilter(
new SetNullFilter(),
new PropertyNameMatcher('cloned')
);
$deepCopy->addFilter(
new KeepFilter(),
new PropertyNameMatcher('cloned')
);
$a = new f006\A;
$b = new f006\B;
$a->setAProp($b);
/** @var f006\A $copy */
$copy = $deepCopy->copy($a);
$this->assertNull($copy->cloned);
$this->assertNull($copy->getAProp()->cloned);
}
public function test_it_can_deep_copy_an_array_object()
{
$foo = new f003\Foo('foo');
$foo->setProp('bar');
$object = new ArrayObject(['foo' => $foo, ArrayObject::ARRAY_AS_PROPS, \RecursiveArrayIterator::class]);
$copy = deep_copy($object);
$this->assertEqualButNotSame($object, $copy);
$this->assertEqualButNotSame($foo, $copy['foo']);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/issues/152
*/
public function test_it_clones_objects_extending_array_object()
{
$object = new f011\ArrayObjectExtended('foo');
$object->setFlags(ArrayObject::ARRAY_AS_PROPS);
$object->setIteratorClass(\RecursiveArrayIterator::class);
$object['a'] = new f011\ArrayObjectExtended('bar');
$copy = deep_copy($object);
$this->assertEqualButNotSame($object, $copy);
$this->assertEqualButNotSame($object['a'], $copy['a']);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/pull/49
*/
public function test_it_can_copy_a_SplDoublyLinkedList()
{
$object = new SplDoublyLinkedList();
$a = new stdClass();
$b = new stdClass();
$a->b = $b;
$object->push($a);
/** @var SplDoublyLinkedList $copy */
$copy = deep_copy($object);
$this->assertEqualButNotSame($object, $copy);
$aCopy = $copy->pop();
$this->assertEqualButNotSame($b, $aCopy->b);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/issues/62
*/
public function test_matchers_can_access_to_parent_private_properties()
{
$deepCopy = new DeepCopy();
$deepCopy->addFilter(new SetNullFilter(), new PropertyTypeMatcher(stdClass::class));
$object = new f008\B(new stdClass());
/** @var f008\B $copy */
$copy = $deepCopy->copy($object);
$this->assertNull($copy->getFoo());
}
public function test_it_can_prepend_filter()
{
$object = new f008\A('bar');
$deepCopy = new DeepCopy();
$deepCopy->addFilter(new KeepFilter(), new PropertyNameMatcher('foo'));
$deepCopy->prependFilter(new SetNullFilter(), new PropertyNameMatcher('foo'));
$copy = $deepCopy->copy($object);
$this->assertNull($copy->getFoo());
}
public function test_it_can_prepend_type_filter()
{
$object = new f008\A('bar');
$deepCopy = new DeepCopy();
$deepCopy->addTypeFilter(new ReplaceFilter(function ($object) {
return new f008\A('baz');
}), new TypeMatcher(f008\A::class));
$deepCopy->prependTypeFilter(new ReplaceFilter(function ($object) {
return new f008\A('foo');
}), new TypeMatcher(f008\A::class));
$copy = $deepCopy->copy($object);
$this->assertEquals('foo',$copy->getFoo());
}
/**
* @ticket https://github.com/myclabs/DeepCopy/issues/143
* @requires PHP 7.4
*/
public function test_it_clones_typed_properties()
{
$object = new f009\TypedProperty();
$object->foo = 123;
$deepCopy = new DeepCopy();
$copy = $deepCopy->copy($object);
$this->assertSame(123, $copy->foo);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/issues/143
* @requires PHP 7.4
*/
public function test_it_ignores_uninitialized_typed_properties()
{
$object = new f009\TypedProperty();
$deepCopy = new DeepCopy();
$copy = $deepCopy->copy($object);
$this->assertFalse(isset($copy->foo));
}
/**
* @requires PHP 8.1
*/
public function test_it_keeps_enums()
{
$enum = Suit::Clubs;
$copy = (new DeepCopy())->copy($enum);
$this->assertSame($enum, $copy);
}
/**
* @ticket https://github.com/myclabs/DeepCopy/issues/98
*/
public function test_it_can_apply_two_filters_with_chainable_filter()
{
$object = new f013\A();
$deepCopy = new DeepCopy();
$deepCopy->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());
$deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('foo'));
$copy = $deepCopy->copy($object);
$this->assertNull($copy->foo);
}
public function test_it_can_copy_property_after_applying_doctrine_proxy_filter_with_chainable_filter()
{
$object = new f013\B();
$object->setFoo(new f013\C());
$deepCopy = new DeepCopy();
$deepCopy->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());
/** @var f013\B $copy */
$copy = $deepCopy->copy($object);
$this->assertNotEquals($copy->getFoo(), $object->getFoo());
}
/**
* @requires PHP 8.1
*/
public function test_it_can_copy_object_with_readonly_property()
{
$scalarProperties = new f014\ReadonlyScalarProperty();
$objectProperties = new f014\ReadonlyObjectProperty();
$deepCopy = new DeepCopy();
$scalarPropertiesCopy = $deepCopy->copy($scalarProperties);
$objectPropertiesCopy = $deepCopy->copy($objectProperties);
$this->assertEqualButNotSame($scalarProperties, $scalarPropertiesCopy);
$this->assertEqualButNotSame($objectProperties, $objectPropertiesCopy);
}
private function assertEqualButNotSame($expected, $val)
{
$this->assertEquals($expected, $val);
$this->assertNotSame($expected, $val);
}
}
================================================
FILE: tests/DeepCopyTest/Filter/Doctrine/DoctrineCollectionFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Filter\Doctrine;
use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Filter\Doctrine\DoctrineCollectionFilter
*/
class DoctrineCollectionFilterTest extends TestCase
{
public function test_it_copies_the_object_property_array_collection()
{
$object = new stdClass();
$oldCollection = new ArrayCollection();
$oldCollection->add($stdClass = new stdClass());
$object->foo = $oldCollection;
$filter = new DoctrineCollectionFilter();
$filter->apply(
$object,
'foo',
function($item) {
return null;
}
);
$this->assertInstanceOf(Collection::class, $object->foo);
$this->assertNotSame($oldCollection, $object->foo);
$this->assertCount(1, $object->foo);
$objectOfNewCollection = $object->foo->get(0);
$this->assertNotSame($stdClass, $objectOfNewCollection);
}
}
================================================
FILE: tests/DeepCopyTest/Filter/Doctrine/DoctrineEmptyCollectionFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Filter\Doctrine;
use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter
*/
class DoctrineEmptyCollectionFilterTest extends TestCase
{
public function test_it_sets_the_object_property_to_an_empty_doctrine_collection()
{
$object = new stdClass();
$collection = new ArrayCollection();
$collection->add(new stdClass());
$object->foo = $collection;
$filter = new DoctrineEmptyCollectionFilter();
$filter->apply(
$object,
'foo',
function($item) {
return null;
}
);
$this->assertInstanceOf(Collection::class, $object->foo);
$this->assertNotSame($collection, $object->foo);
$this->assertTrue($object->foo->isEmpty());
}
}
================================================
FILE: tests/DeepCopyTest/Filter/Doctrine/DoctrineProxyFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Filter\Doctrine;
use BadMethodCallException;
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
use PHPUnit\Framework\TestCase;
/**
* @covers \DeepCopy\Filter\Doctrine\DoctrineProxyFilter
*/
class DoctrineProxyFilterTest extends TestCase
{
public function test_it_loads_the_doctrine_proxy()
{
$foo = new Foo();
$filter = new DoctrineProxyFilter();
$filter->apply(
$foo,
'unknown',
function($item) {
throw new BadMethodCallException('Did not expect to be called.');
}
);
$this->assertTrue($foo->isLoaded);
}
}
class Foo
{
public $isLoaded = false;
public function __load()
{
$this->isLoaded = true;
}
}
================================================
FILE: tests/DeepCopyTest/Filter/KeepFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Filter;
use DeepCopy\Filter\KeepFilter;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Filter\KeepFilter
*/
class KeepFilterTest extends TestCase
{
public function test_it_does_not_change_the_filtered_object_property()
{
$object = new stdClass();
$keepObject = new stdClass();
$object->foo = $keepObject;
$filter = new KeepFilter();
$filter->apply($object, 'foo', null);
$this->assertSame($keepObject, $object->foo);
}
}
================================================
FILE: tests/DeepCopyTest/Filter/ReplaceFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Filter;
use DeepCopy\Filter\ReplaceFilter;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Filter\ReplaceFilter
*/
class ReplaceFilterTest extends TestCase
{
/**
* @dataProvider provideCallbacks
*/
public function test_it_applies_the_callback_to_the_specified_property(callable $callback, array $expected)
{
$object = new stdClass();
$object->data = [
'foo' => 'bar',
'baz' => 'foo'
];
$filter = new ReplaceFilter($callback);
$filter->apply(
$object,
'data',
function () {
return null;
}
);
$this->assertEquals($expected, $object->data);
}
public function provideCallbacks()
{
return [
[
function ($data) {
unset($data['baz']);
return $data;
},
['foo' => 'bar']
],
];
}
}
================================================
FILE: tests/DeepCopyTest/Filter/SetNullFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Filter;
use DeepCopy\Filter\SetNullFilter;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Filter\SetNullFilter
*/
class SetNullFilterTest extends TestCase
{
public function test_it_sets_the_given_property_to_null()
{
$filter = new SetNullFilter();
$object = new stdClass();
$object->foo = 'bar';
$object->bim = 'bam';
$filter->apply($object, 'foo', null);
$this->assertNull($object->foo);
$this->assertSame('bam', $object->bim);
}
}
================================================
FILE: tests/DeepCopyTest/Matcher/Doctrine/DoctrineProxyMatcherTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Matcher\Doctrine;
use BadMethodCallException;
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;
use Doctrine\Persistence\Proxy;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher
*/
class DoctrineProxyMatcherTest extends TestCase
{
/**
* @dataProvider providePairs
*/
public function test_it_matches_the_given_objects($object, $expected)
{
$matcher = new DoctrineProxyMatcher();
$actual = $matcher->matches($object, 'unknown');
$this->assertEquals($expected, $actual);
}
public function providePairs()
{
return [
[new FooProxy(), true],
[new stdClass(), false],
];
}
}
class FooProxy implements Proxy
{
/**
* @inheritdoc
*/
public function __load(): void
{
throw new BadMethodCallException();
}
/**
* @inheritdoc
*/
public function __isInitialized(): bool
{
throw new BadMethodCallException();
}
}
================================================
FILE: tests/DeepCopyTest/Matcher/PropertyMatcherTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Matcher;
use DeepCopy\Matcher\PropertyMatcher;
use PHPUnit\Framework\TestCase;
/**
* @covers \DeepCopy\Matcher\PropertyMatcher
*/
class PropertyMatcherTest extends TestCase
{
/**
* @dataProvider providePairs
*/
public function test_it_matches_the_given_objects($object, $prop, $expected)
{
$matcher = new PropertyMatcher(X::class, 'foo');
$actual = $matcher->matches($object, $prop);
$this->assertEquals($expected, $actual);
}
public function providePairs()
{
return [
'matching case' => [new X(), 'foo', true],
'match class, non matching prop' => [new X(), 'bar', false],
'match class, unknown prop' => [new X(), 'unknown', false],
'non matching class, matching prop' => [new Y(), 'unknown', false],
];
}
}
class X
{
public $foo;
public $bar;
}
class Y
{
public $foo;
}
================================================
FILE: tests/DeepCopyTest/Matcher/PropertyNameMatcherTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Matcher;
use DeepCopy\Matcher\PropertyNameMatcher;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Matcher\PropertyNameMatcher
*/
class PropertyNameMatcherTest extends TestCase
{
/**
* @dataProvider providePairs
*/
public function test_it_matches_the_given_property($object, $prop, $expected)
{
$matcher = new PropertyNameMatcher('foo');
$actual = $matcher->matches($object, $prop);
$this->assertEquals($expected, $actual);
}
public function providePairs()
{
return [
[new stdClass(), 'foo', true],
[new stdClass(), 'unknown', false],
];
}
}
================================================
FILE: tests/DeepCopyTest/Matcher/PropertyTypeMatcherTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Matcher;
use DeepCopy\f009;
use DeepCopy\Matcher\PropertyTypeMatcher;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\Matcher\PropertyTypeMatcher
*/
class PropertyTypeMatcherTest extends TestCase
{
/**
* @dataProvider providePairs
*/
public function test_it_matches_the_given_property($object, $expected)
{
$matcher = new PropertyTypeMatcher(PropertyTypeMatcherTestFixture2::class);
$actual = $matcher->matches($object, 'foo');
$this->assertEquals($expected, $actual);
}
/**
* @requires PHP 7.4
*/
public function test_it_ignores_uninitialized_typed_properties()
{
$object = new f009\TypedObjectProperty();
$matcher = new PropertyTypeMatcher(\DateTime::class);
$this->assertFalse($matcher->matches($object, 'date'));
}
/**
* @requires PHP 7.4
*/
public function test_it_matches_initialized_typed_properties()
{
$object = new f009\TypedObjectProperty();
$object->date = new \DateTime();
$matcher = new PropertyTypeMatcher(\DateTime::class);
$this->assertTrue($matcher->matches($object, 'date'));
}
public function providePairs()
{
$object1 = new PropertyTypeMatcherTestFixture1();
$object1->foo = new PropertyTypeMatcherTestFixture2();
$object2 = new PropertyTypeMatcherTestFixture1();
$object2->foo = new stdClass();
$object3 = new PropertyTypeMatcherTestFixture1();
$object3->foo = true;
return [
[new PropertyTypeMatcherTestFixture1(), false],
[$object1, true],
[$object2, false],
[$object3, false],
[new stdClass(), false],
];
}
}
class PropertyTypeMatcherTestFixture1
{
public $foo;
}
class PropertyTypeMatcherTestFixture2
{
}
================================================
FILE: tests/DeepCopyTest/Reflection/ReflectionHelperTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\Reflection;
use DeepCopy\Exception\PropertyException;
use DeepCopy\Reflection\ReflectionHelper;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionProperty;
/**
* @covers \DeepCopy\Reflection\ReflectionHelper
*/
class ReflectionHelperTest extends TestCase
{
public function test_it_retrieves_the_object_properties()
{
$child = new ReflectionHelperTestChild();
$childReflectionClass = new ReflectionClass($child);
$expectedProps = array(
'a1',
'a2',
'a3',
'a10',
'a11',
'a12',
'a20',
'a21',
'a22',
'a100',
'a101',
'a102',
);
$actualProps = ReflectionHelper::getProperties($childReflectionClass);
$this->assertSame($expectedProps, array_keys($actualProps));
}
/**
* @dataProvider provideProperties
*/
public function test_it_can_retrieve_an_object_property($name)
{
$object = new ReflectionHelperTestChild();
$property = ReflectionHelper::getProperty($object, $name);
$this->assertInstanceOf(ReflectionProperty::class, $property);
$this->assertSame($name, $property->getName());
}
public function provideProperties()
{
return [
'public property' => ['a10'],
'private property' => ['a102'],
'private property of ancestor' => ['a3']
];
}
public function test_it_cannot_retrieve_a_non_existent_prperty()
{
$object = new ReflectionHelperTestChild();
$this->expectException(PropertyException::class);
ReflectionHelper::getProperty($object, 'non existent property');
}
}
class ReflectionHelperTestParent
{
public $a1;
protected $a2;
private $a3;
public $a10;
protected $a11;
private $a12;
public static $a20;
protected static $a21;
private static $a22;
}
class ReflectionHelperTestChild extends ReflectionHelperTestParent
{
public $a1;
protected $a2;
private $a3;
public $a100;
protected $a101;
private $a102;
}
================================================
FILE: tests/DeepCopyTest/TypeFilter/Date/DateIntervalFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeFilter\Date;
use DateInterval;
use DeepCopy\TypeFilter\Date\DateIntervalFilter;
use PHPUnit\Framework\TestCase;
/**
* @covers \DeepCopy\TypeFilter\Date\DateIntervalFilter
*/
class DateIntervalFilterTest extends TestCase
{
public function test_it_deep_copies_a_DateInterval()
{
$object = new DateInterval('P2D');;
$filter = new DateIntervalFilter();
$copy = $filter->apply($object);
$this->assertEquals($object, $copy);
$this->assertNotSame($object, $copy);
}
}
================================================
FILE: tests/DeepCopyTest/TypeFilter/Date/DatePeriodFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeFilter\Date;
use DateInterval;
use DatePeriod;
use DateTime;
use DeepCopy\TypeFilter\Date\DateIntervalFilter;
use DeepCopy\TypeFilter\Date\DatePeriodFilter;
use PHPUnit\Framework\TestCase;
/**
* @covers \DeepCopy\TypeFilter\Date\DatePeriodFilter
*/
class DatePeriodFilterTest extends TestCase
{
public function test_it_deep_copies_a_DatePeriod()
{
$object = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3);
$filter = new DatePeriodFilter();
$copy = $filter->apply($object);
$this->assertEquals($object, $copy);
$this->assertNotSame($object, $copy);
}
public function test_it_deep_copies_a_DatePeriod_with_exclude_start_date()
{
$object = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3, DatePeriod::EXCLUDE_START_DATE);
$filter = new DatePeriodFilter();
$copy = $filter->apply($object);
$this->assertEquals($object, $copy);
$this->assertNotSame($object, $copy);
}
/**
* @requires PHP 8.2
*/
public function test_it_deep_copies_a_DatePeriod_with_include_end_date()
{
$object = new DatePeriod(new DateTime(), new DateInterval('P2D'), 3, DatePeriod::INCLUDE_END_DATE);
$filter = new DatePeriodFilter();
$copy = $filter->apply($object);
$this->assertEquals($object, $copy);
$this->assertNotSame($object, $copy);
}
public function test_it_deep_copies_a_DatePeriod_with_end_date()
{
$object = new DatePeriod(new DateTime(), new DateInterval('P2D'), new DateTime('+2 days'));
$filter = new DatePeriodFilter();
$copy = $filter->apply($object);
$this->assertEquals($object, $copy);
$this->assertNotSame($object, $copy);
}
}
================================================
FILE: tests/DeepCopyTest/TypeFilter/ReplaceFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeFilter;
use DeepCopy\TypeFilter\ReplaceFilter;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\TypeFilter\ReplaceFilter
*/
class ReplaceFilterTest extends TestCase
{
public function test_it_returns_the_callback_called_with_the_given_object()
{
$foo = new stdClass();
$callback = function ($object) {
$object = new stdClass();
$object->callback = true;
return $object;
};
$filter = new ReplaceFilter($callback);
$newFoo = $filter->apply($foo);
$this->assertNotSame($newFoo, $foo);
$this->assertTrue($newFoo->callback);
}
}
================================================
FILE: tests/DeepCopyTest/TypeFilter/ShallowCopyFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeFilter;
use DeepCopy\TypeFilter\ShallowCopyFilter;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\TypeFilter\ShallowCopyFilter
*/
class ShallowCopyFilterTest extends TestCase
{
public function test_it_shallow_copies_the_given_object()
{
$foo = new stdClass();
$bar = new stdClass();
$foo->bar = $bar;
$filter = new ShallowCopyFilter();
$newFoo = $filter->apply($foo);
$this->assertNotSame($foo, $newFoo);
$this->assertSame($foo->bar, $newFoo->bar);
}
}
================================================
FILE: tests/DeepCopyTest/TypeFilter/Spl/ArrayObjectFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeFilter\Spl;
use ArrayObject;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\Spl\ArrayObjectFilter;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use RecursiveArrayIterator;
/**
* @author Dominic Tubach <dominic.tubach@to.com>
*
* @covers \DeepCopy\TypeFilter\Spl\ArrayObjectFilter
*/
final class ArrayObjectFilterTest extends TestCase
{
/**
* @var ArrayObjectFilter
*/
private $arrayObjectFilter;
/**
* @var DeepCopy|ObjectProphecy
*/
private $copierProphecy;
protected function setUp(): void
{
$this->copierProphecy = $this->prophesize(DeepCopy::class);
$this->arrayObjectFilter = new ArrayObjectFilter(
$this->copierProphecy->reveal()
);
}
public function test_it_deep_copies_an_array_object(): void
{
$arrayObject = new ArrayObject(['foo' => 'bar'], ArrayObject::ARRAY_AS_PROPS, RecursiveArrayIterator::class);
$this->copierProphecy->copy('bar')->willReturn('baz');
/** @var \ArrayObject $newArrayObject */
$newArrayObject = $this->arrayObjectFilter->apply($arrayObject);
$this->assertSame(['foo' => 'baz'], $newArrayObject->getArrayCopy());
$this->assertSame(ArrayObject::ARRAY_AS_PROPS, $newArrayObject->getFlags());
$this->assertSame(RecursiveArrayIterator::class, $newArrayObject->getIteratorClass());
}
}
================================================
FILE: tests/DeepCopyTest/TypeFilter/Spl/SplDoublyLinkedListFilterTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeFilter\Spl;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedListFilter;
use PHPUnit\Framework\TestCase;
use SplDoublyLinkedList;
use stdClass;
/**
* @covers \DeepCopy\TypeFilter\Spl\SplDoublyLinkedListFilter
*/
class SplDoublyLinkedListFilterTest extends TestCase
{
public function test_it_deep_copies_a_doubly_linked_spl_list()
{
$foo = new stdClass();
$list = new SplDoublyLinkedList();
$list->push($foo);
$filter = new SplDoublyLinkedListFilter(new FakeDeepCopy());
$newList = $filter->apply($list);
$this->assertCount(1, $newList);
$this->assertNotSame($foo, $newList->next());
}
}
class FakeDeepCopy extends DeepCopy
{
/**
* @inheritdoc
*/
public function copy($object)
{
return new stdClass();
}
}
================================================
FILE: tests/DeepCopyTest/TypeMatcher/TypeMatcherTest.php
================================================
<?php declare(strict_types=1);
namespace DeepCopyTest\TypeMatcher;
use DeepCopy\TypeMatcher\TypeMatcher;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @covers \DeepCopy\TypeMatcher\TypeMatcher
*/
class TypeMatcherTest extends TestCase
{
/**
* @dataProvider provideElements
*/
public function test_it_retrieves_the_object_properties($type, $element, $expected)
{
$matcher = new TypeMatcher($type);
$actual = $matcher->matches($element);
$this->assertSame($expected, $actual);
}
public function provideElements()
{
return [
'[class] same class as type' => ['stdClass', new stdClass(), true],
'[class] different class as type' => ['stdClass', new Foo(), false],
'[class] child class as type' => [Foo::class, new Bar(), true],
'[class] interface implementation as type' => [IA::class, new A(), true],
'[scalar] array match' => ['array', [], true],
'[scalar] array no match' => ['array', true, false],
];
}
}
class Foo
{
}
class Bar extends Foo
{
}
interface IA
{
}
class A implements IA
{
}
gitextract_qwehwyz_/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── ci.yaml
├── .gitignore
├── .scrutinizer.yml
├── LICENSE
├── README.md
├── composer.json
├── fixtures/
│ ├── f001/
│ │ ├── A.php
│ │ └── B.php
│ ├── f002/
│ │ └── A.php
│ ├── f003/
│ │ └── Foo.php
│ ├── f004/
│ │ └── UnclonableItem.php
│ ├── f005/
│ │ └── Foo.php
│ ├── f006/
│ │ ├── A.php
│ │ └── B.php
│ ├── f007/
│ │ ├── FooDateInterval.php
│ │ └── FooDateTimeZone.php
│ ├── f008/
│ │ ├── A.php
│ │ └── B.php
│ ├── f009/
│ │ ├── TypedObjectProperty.php
│ │ └── TypedProperty.php
│ ├── f011/
│ │ └── ArrayObjectExtended.php
│ ├── f012/
│ │ └── Suit.php
│ ├── f013/
│ │ ├── A.php
│ │ ├── B.php
│ │ └── C.php
│ └── f014/
│ ├── ReadonlyObjectProperty.php
│ └── ReadonlyScalarProperty.php
├── phpunit.xml.dist
├── src/
│ └── DeepCopy/
│ ├── DeepCopy.php
│ ├── Exception/
│ │ ├── CloneException.php
│ │ └── PropertyException.php
│ ├── Filter/
│ │ ├── ChainableFilter.php
│ │ ├── Doctrine/
│ │ │ ├── DoctrineCollectionFilter.php
│ │ │ ├── DoctrineEmptyCollectionFilter.php
│ │ │ └── DoctrineProxyFilter.php
│ │ ├── Filter.php
│ │ ├── KeepFilter.php
│ │ ├── ReplaceFilter.php
│ │ └── SetNullFilter.php
│ ├── Matcher/
│ │ ├── Doctrine/
│ │ │ └── DoctrineProxyMatcher.php
│ │ ├── Matcher.php
│ │ ├── PropertyMatcher.php
│ │ ├── PropertyNameMatcher.php
│ │ └── PropertyTypeMatcher.php
│ ├── Reflection/
│ │ └── ReflectionHelper.php
│ ├── TypeFilter/
│ │ ├── Date/
│ │ │ ├── DateIntervalFilter.php
│ │ │ └── DatePeriodFilter.php
│ │ ├── ReplaceFilter.php
│ │ ├── ShallowCopyFilter.php
│ │ ├── Spl/
│ │ │ ├── ArrayObjectFilter.php
│ │ │ ├── SplDoublyLinkedList.php
│ │ │ └── SplDoublyLinkedListFilter.php
│ │ └── TypeFilter.php
│ ├── TypeMatcher/
│ │ └── TypeMatcher.php
│ └── deep_copy.php
└── tests/
└── DeepCopyTest/
├── DeepCopyTest.php
├── Filter/
│ ├── Doctrine/
│ │ ├── DoctrineCollectionFilterTest.php
│ │ ├── DoctrineEmptyCollectionFilterTest.php
│ │ └── DoctrineProxyFilterTest.php
│ ├── KeepFilterTest.php
│ ├── ReplaceFilterTest.php
│ └── SetNullFilterTest.php
├── Matcher/
│ ├── Doctrine/
│ │ └── DoctrineProxyMatcherTest.php
│ ├── PropertyMatcherTest.php
│ ├── PropertyNameMatcherTest.php
│ └── PropertyTypeMatcherTest.php
├── Reflection/
│ └── ReflectionHelperTest.php
├── TypeFilter/
│ ├── Date/
│ │ ├── DateIntervalFilterTest.php
│ │ └── DatePeriodFilterTest.php
│ ├── ReplaceFilterTest.php
│ ├── ShallowCopyFilterTest.php
│ └── Spl/
│ ├── ArrayObjectFilterTest.php
│ └── SplDoublyLinkedListFilterTest.php
└── TypeMatcher/
└── TypeMatcherTest.php
SYMBOL INDEX (231 symbols across 66 files)
FILE: fixtures/f001/A.php
class A (line 5) | class A
method getAProp (line 9) | public function getAProp()
method setAProp (line 14) | public function setAProp($prop)
FILE: fixtures/f001/B.php
class B (line 5) | class B extends A
method getBProp (line 9) | public function getBProp()
method setBProp (line 14) | public function setBProp($prop)
FILE: fixtures/f002/A.php
class A (line 5) | class A
method getProp1 (line 10) | public function getProp1()
method setProp1 (line 15) | public function setProp1($prop)
method getProp2 (line 22) | public function getProp2()
method setProp2 (line 27) | public function setProp2($prop)
FILE: fixtures/f003/Foo.php
class Foo (line 5) | class Foo
method __construct (line 10) | public function __construct($name)
method getProp (line 15) | public function getProp()
method setProp (line 20) | public function setProp($prop)
FILE: fixtures/f004/UnclonableItem.php
class UnclonableItem (line 7) | class UnclonableItem
method __clone (line 9) | private function __clone()
FILE: fixtures/f005/Foo.php
class Foo (line 5) | class Foo
method __clone (line 9) | public function __clone()
FILE: fixtures/f006/A.php
class A (line 5) | class A
method getAProp (line 10) | public function getAProp()
method setAProp (line 15) | public function setAProp($prop)
method __clone (line 22) | public function __clone()
FILE: fixtures/f006/B.php
class B (line 5) | class B
method getBProp (line 10) | public function getBProp()
method setBProp (line 15) | public function setBProp($prop)
method __clone (line 22) | public function __clone()
FILE: fixtures/f007/FooDateInterval.php
class FooDateInterval (line 7) | class FooDateInterval extends DateInterval
method __clone (line 11) | public function __clone()
FILE: fixtures/f007/FooDateTimeZone.php
class FooDateTimeZone (line 7) | class FooDateTimeZone extends DateTimeZone
method __clone (line 11) | public function __clone()
FILE: fixtures/f008/A.php
class A (line 5) | class A
method __construct (line 9) | public function __construct($foo)
method getFoo (line 14) | public function getFoo()
FILE: fixtures/f008/B.php
class B (line 5) | class B extends A
FILE: fixtures/f009/TypedObjectProperty.php
class TypedObjectProperty (line 7) | class TypedObjectProperty
FILE: fixtures/f009/TypedProperty.php
class TypedProperty (line 5) | class TypedProperty
FILE: fixtures/f011/ArrayObjectExtended.php
class ArrayObjectExtended (line 7) | class ArrayObjectExtended extends ArrayObject
method __construct (line 11) | public function __construct($x)
FILE: fixtures/f013/A.php
class A (line 8) | class A implements Proxy
method __load (line 15) | public function __load(): void
method __isInitialized (line 22) | public function __isInitialized(): bool
FILE: fixtures/f013/B.php
class B (line 8) | class B implements Proxy
method __load (line 15) | public function __load(): void
method __isInitialized (line 22) | public function __isInitialized(): bool
method getFoo (line 27) | public function getFoo()
method setFoo (line 32) | public function setFoo($foo)
FILE: fixtures/f013/C.php
class C (line 5) | class C
method __clone (line 9) | public function __clone()
FILE: fixtures/f014/ReadonlyObjectProperty.php
class ReadonlyObjectProperty (line 5) | class ReadonlyObjectProperty
method __construct (line 9) | public function __construct()
FILE: fixtures/f014/ReadonlyScalarProperty.php
class ReadonlyScalarProperty (line 5) | class ReadonlyScalarProperty
method __construct (line 11) | public function __construct()
FILE: src/DeepCopy/DeepCopy.php
class DeepCopy (line 28) | class DeepCopy
method __construct (line 63) | public function __construct($useCloneMethod = false)
method skipUncloneable (line 80) | public function skipUncloneable($skipUncloneable = true)
method copy (line 96) | public function copy($object)
method addFilter (line 103) | public function addFilter(Filter $filter, Matcher $matcher)
method prependFilter (line 111) | public function prependFilter(Filter $filter, Matcher $matcher)
method addTypeFilter (line 119) | public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
method prependTypeFilter (line 127) | public function prependTypeFilter(TypeFilter $filter, TypeMatcher $mat...
method recursiveCopy (line 135) | private function recursiveCopy($var)
method copyArray (line 171) | private function copyArray(array $array)
method copyObject (line 189) | private function copyObject($object)
method copyObjectProperty (line 233) | private function copyObjectProperty($object, ReflectionProperty $prope...
method getFirstMatchedTypeFilter (line 294) | private function getFirstMatchedTypeFilter(array $filterRecords, $var)
method first (line 318) | private function first(array $elements, callable $predicate)
FILE: src/DeepCopy/Exception/CloneException.php
class CloneException (line 7) | class CloneException extends UnexpectedValueException
FILE: src/DeepCopy/Exception/PropertyException.php
class PropertyException (line 7) | class PropertyException extends ReflectionException
FILE: src/DeepCopy/Filter/ChainableFilter.php
class ChainableFilter (line 8) | class ChainableFilter implements Filter
method __construct (line 15) | public function __construct(Filter $filter)
method apply (line 20) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php
class DoctrineCollectionFilter (line 11) | class DoctrineCollectionFilter implements Filter
method apply (line 18) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php
class DoctrineEmptyCollectionFilter (line 12) | class DoctrineEmptyCollectionFilter implements Filter
method apply (line 21) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php
class DoctrineProxyFilter (line 10) | class DoctrineProxyFilter implements Filter
method apply (line 18) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Filter/Filter.php
type Filter (line 8) | interface Filter
method apply (line 17) | public function apply($object, $property, $objectCopier);
FILE: src/DeepCopy/Filter/KeepFilter.php
class KeepFilter (line 5) | class KeepFilter implements Filter
method apply (line 12) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Filter/ReplaceFilter.php
class ReplaceFilter (line 10) | class ReplaceFilter implements Filter
method __construct (line 20) | public function __construct(callable $callable)
method apply (line 30) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Filter/SetNullFilter.php
class SetNullFilter (line 10) | class SetNullFilter implements Filter
method apply (line 17) | public function apply($object, $property, $objectCopier)
FILE: src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php
class DoctrineProxyMatcher (line 11) | class DoctrineProxyMatcher implements Matcher
method matches (line 18) | public function matches($object, $property)
FILE: src/DeepCopy/Matcher/Matcher.php
type Matcher (line 5) | interface Matcher
method matches (line 13) | public function matches($object, $property);
FILE: src/DeepCopy/Matcher/PropertyMatcher.php
class PropertyMatcher (line 8) | class PropertyMatcher implements Matcher
method __construct (line 24) | public function __construct($class, $property)
method matches (line 35) | public function matches($object, $property)
FILE: src/DeepCopy/Matcher/PropertyNameMatcher.php
class PropertyNameMatcher (line 8) | class PropertyNameMatcher implements Matcher
method __construct (line 18) | public function __construct($property)
method matches (line 28) | public function matches($object, $property)
FILE: src/DeepCopy/Matcher/PropertyTypeMatcher.php
class PropertyTypeMatcher (line 16) | class PropertyTypeMatcher implements Matcher
method __construct (line 26) | public function __construct($propertyType)
method matches (line 34) | public function matches($object, $property)
FILE: src/DeepCopy/Reflection/ReflectionHelper.php
class ReflectionHelper (line 11) | class ReflectionHelper
method getProperties (line 25) | public static function getProperties(ReflectionClass $ref)
method getProperty (line 58) | public static function getProperty($object, $name)
FILE: src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php
class DateIntervalFilter (line 13) | class DateIntervalFilter implements TypeFilter
method apply (line 23) | public function apply($element)
FILE: src/DeepCopy/TypeFilter/Date/DatePeriodFilter.php
class DatePeriodFilter (line 11) | class DatePeriodFilter implements TypeFilter
method apply (line 20) | public function apply($element)
FILE: src/DeepCopy/TypeFilter/ReplaceFilter.php
class ReplaceFilter (line 8) | class ReplaceFilter implements TypeFilter
method __construct (line 18) | public function __construct(callable $callable)
method apply (line 26) | public function apply($element)
FILE: src/DeepCopy/TypeFilter/ShallowCopyFilter.php
class ShallowCopyFilter (line 8) | class ShallowCopyFilter implements TypeFilter
method apply (line 13) | public function apply($element)
FILE: src/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.php
class ArrayObjectFilter (line 11) | final class ArrayObjectFilter implements TypeFilter
method __construct (line 18) | public function __construct(DeepCopy $copier)
method apply (line 26) | public function apply($arrayObject)
FILE: src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php
class SplDoublyLinkedList (line 8) | class SplDoublyLinkedList extends SplDoublyLinkedListFilter
FILE: src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php
class SplDoublyLinkedListFilter (line 13) | class SplDoublyLinkedListFilter implements TypeFilter
method __construct (line 17) | public function __construct(DeepCopy $copier)
method apply (line 25) | public function apply($element)
method createCopyClosure (line 34) | private function createCopyClosure()
FILE: src/DeepCopy/TypeFilter/TypeFilter.php
type TypeFilter (line 5) | interface TypeFilter
method apply (line 12) | public function apply($element);
FILE: src/DeepCopy/TypeMatcher/TypeMatcher.php
class TypeMatcher (line 5) | class TypeMatcher
method __construct (line 15) | public function __construct($type)
method matches (line 25) | public function matches($element)
FILE: src/DeepCopy/deep_copy.php
function deep_copy (line 16) | function deep_copy($value, $useCloneMethod = false)
FILE: tests/DeepCopyTest/DeepCopyTest.php
class DeepCopyTest (line 45) | class DeepCopyTest extends TestCase
method test_it_can_copy_scalar_values (line 50) | public function test_it_can_copy_scalar_values($value)
method provideScalarValues (line 57) | public function provideScalarValues()
method test_it_can_copy_an_array_of_scalar_values (line 69) | public function test_it_can_copy_an_array_of_scalar_values()
method test_it_can_copy_an_object (line 76) | public function test_it_can_copy_an_object()
method test_it_can_copy_an_array_of_objects (line 85) | public function test_it_can_copy_an_array_of_objects()
method test_it_can_copy_an_object_with_scalar_properties (line 98) | public function test_it_can_copy_an_object_with_scalar_properties($obj...
method provideObjectWithScalarValues (line 106) | public function provideObjectWithScalarValues()
method test_it_can_copy_an_object_with_an_object_property (line 124) | public function test_it_can_copy_an_object_with_an_object_property()
method test_it_copies_dynamic_properties (line 137) | public function test_it_copies_dynamic_properties()
method test_it_can_copy_an_object_with_a_date_object_property (line 155) | public function test_it_can_copy_an_object_with_a_date_object_property()
method test_it_skips_the_copy_for_userland_datetimezone (line 177) | public function test_it_skips_the_copy_for_userland_datetimezone()
method test_it_skips_the_copy_for_userland_dateinterval (line 197) | public function test_it_skips_the_copy_for_userland_dateinterval()
method test_it_copies_the_private_properties_of_the_parent_class (line 214) | public function test_it_copies_the_private_properties_of_the_parent_cl...
method test_it_keeps_reference_of_the_copied_objects_when_copying_the_graph (line 228) | public function test_it_keeps_reference_of_the_copied_objects_when_cop...
method test_it_can_copy_graphs_with_circular_references (line 250) | public function test_it_can_copy_graphs_with_circular_references()
method test_it_can_copy_graphs_with_circular_references_with_userland_class (line 266) | public function test_it_can_copy_graphs_with_circular_references_with_...
method test_it_cannot_copy_unclonable_items (line 283) | public function test_it_cannot_copy_unclonable_items()
method test_it_can_skip_uncloneable_objects (line 304) | public function test_it_can_skip_uncloneable_objects()
method test_it_uses_the_userland_defined_cloned_method (line 316) | public function test_it_uses_the_userland_defined_cloned_method()
method test_it_only_uses_the_userland_defined_cloned_method_when_configured_to_do_so (line 325) | public function test_it_only_uses_the_userland_defined_cloned_method_w...
method test_it_uses_type_filter_to_copy_objects_if_matcher_matches (line 336) | public function test_it_uses_type_filter_to_copy_objects_if_matcher_ma...
method test_it_uses_filters_to_copy_object_properties_if_matcher_matches (line 357) | public function test_it_uses_filters_to_copy_object_properties_if_matc...
method test_it_uses_the_first_filter_matching_for_copying_object_properties (line 377) | public function test_it_uses_the_first_filter_matching_for_copying_obj...
method test_it_can_deep_copy_an_array_object (line 401) | public function test_it_can_deep_copy_an_array_object()
method test_it_clones_objects_extending_array_object (line 416) | public function test_it_clones_objects_extending_array_object()
method test_it_can_copy_a_SplDoublyLinkedList (line 432) | public function test_it_can_copy_a_SplDoublyLinkedList()
method test_matchers_can_access_to_parent_private_properties (line 456) | public function test_matchers_can_access_to_parent_private_properties()
method test_it_can_prepend_filter (line 469) | public function test_it_can_prepend_filter()
method test_it_can_prepend_type_filter (line 479) | public function test_it_can_prepend_type_filter()
method test_it_clones_typed_properties (line 498) | public function test_it_clones_typed_properties()
method test_it_ignores_uninitialized_typed_properties (line 513) | public function test_it_ignores_uninitialized_typed_properties()
method test_it_keeps_enums (line 526) | public function test_it_keeps_enums()
method test_it_can_apply_two_filters_with_chainable_filter (line 538) | public function test_it_can_apply_two_filters_with_chainable_filter()
method test_it_can_copy_property_after_applying_doctrine_proxy_filter_with_chainable_filter (line 551) | public function test_it_can_copy_property_after_applying_doctrine_prox...
method test_it_can_copy_object_with_readonly_property (line 568) | public function test_it_can_copy_object_with_readonly_property()
method assertEqualButNotSame (line 582) | private function assertEqualButNotSame($expected, $val)
FILE: tests/DeepCopyTest/Filter/Doctrine/DoctrineCollectionFilterTest.php
class DoctrineCollectionFilterTest (line 14) | class DoctrineCollectionFilterTest extends TestCase
method test_it_copies_the_object_property_array_collection (line 16) | public function test_it_copies_the_object_property_array_collection()
FILE: tests/DeepCopyTest/Filter/Doctrine/DoctrineEmptyCollectionFilterTest.php
class DoctrineEmptyCollectionFilterTest (line 14) | class DoctrineEmptyCollectionFilterTest extends TestCase
method test_it_sets_the_object_property_to_an_empty_doctrine_collection (line 16) | public function test_it_sets_the_object_property_to_an_empty_doctrine_...
FILE: tests/DeepCopyTest/Filter/Doctrine/DoctrineProxyFilterTest.php
class DoctrineProxyFilterTest (line 12) | class DoctrineProxyFilterTest extends TestCase
method test_it_loads_the_doctrine_proxy (line 14) | public function test_it_loads_the_doctrine_proxy()
class Foo (line 32) | class Foo
method __load (line 36) | public function __load()
FILE: tests/DeepCopyTest/Filter/KeepFilterTest.php
class KeepFilterTest (line 12) | class KeepFilterTest extends TestCase
method test_it_does_not_change_the_filtered_object_property (line 14) | public function test_it_does_not_change_the_filtered_object_property()
FILE: tests/DeepCopyTest/Filter/ReplaceFilterTest.php
class ReplaceFilterTest (line 12) | class ReplaceFilterTest extends TestCase
method test_it_applies_the_callback_to_the_specified_property (line 17) | public function test_it_applies_the_callback_to_the_specified_property...
method provideCallbacks (line 38) | public function provideCallbacks()
FILE: tests/DeepCopyTest/Filter/SetNullFilterTest.php
class SetNullFilterTest (line 12) | class SetNullFilterTest extends TestCase
method test_it_sets_the_given_property_to_null (line 14) | public function test_it_sets_the_given_property_to_null()
FILE: tests/DeepCopyTest/Matcher/Doctrine/DoctrineProxyMatcherTest.php
class DoctrineProxyMatcherTest (line 14) | class DoctrineProxyMatcherTest extends TestCase
method test_it_matches_the_given_objects (line 19) | public function test_it_matches_the_given_objects($object, $expected)
method providePairs (line 28) | public function providePairs()
class FooProxy (line 37) | class FooProxy implements Proxy
method __load (line 42) | public function __load(): void
method __isInitialized (line 50) | public function __isInitialized(): bool
FILE: tests/DeepCopyTest/Matcher/PropertyMatcherTest.php
class PropertyMatcherTest (line 11) | class PropertyMatcherTest extends TestCase
method test_it_matches_the_given_objects (line 16) | public function test_it_matches_the_given_objects($object, $prop, $exp...
method providePairs (line 25) | public function providePairs()
class X (line 36) | class X
class Y (line 42) | class Y
FILE: tests/DeepCopyTest/Matcher/PropertyNameMatcherTest.php
class PropertyNameMatcherTest (line 12) | class PropertyNameMatcherTest extends TestCase
method test_it_matches_the_given_property (line 17) | public function test_it_matches_the_given_property($object, $prop, $ex...
method providePairs (line 26) | public function providePairs()
FILE: tests/DeepCopyTest/Matcher/PropertyTypeMatcherTest.php
class PropertyTypeMatcherTest (line 13) | class PropertyTypeMatcherTest extends TestCase
method test_it_matches_the_given_property (line 18) | public function test_it_matches_the_given_property($object, $expected)
method test_it_ignores_uninitialized_typed_properties (line 30) | public function test_it_ignores_uninitialized_typed_properties()
method test_it_matches_initialized_typed_properties (line 42) | public function test_it_matches_initialized_typed_properties()
method providePairs (line 52) | public function providePairs()
class PropertyTypeMatcherTestFixture1 (line 73) | class PropertyTypeMatcherTestFixture1
class PropertyTypeMatcherTestFixture2 (line 78) | class PropertyTypeMatcherTestFixture2
FILE: tests/DeepCopyTest/Reflection/ReflectionHelperTest.php
class ReflectionHelperTest (line 14) | class ReflectionHelperTest extends TestCase
method test_it_retrieves_the_object_properties (line 16) | public function test_it_retrieves_the_object_properties()
method test_it_can_retrieve_an_object_property (line 44) | public function test_it_can_retrieve_an_object_property($name)
method provideProperties (line 55) | public function provideProperties()
method test_it_cannot_retrieve_a_non_existent_prperty (line 64) | public function test_it_cannot_retrieve_a_non_existent_prperty()
class ReflectionHelperTestParent (line 73) | class ReflectionHelperTestParent
class ReflectionHelperTestChild (line 88) | class ReflectionHelperTestChild extends ReflectionHelperTestParent
FILE: tests/DeepCopyTest/TypeFilter/Date/DateIntervalFilterTest.php
class DateIntervalFilterTest (line 12) | class DateIntervalFilterTest extends TestCase
method test_it_deep_copies_a_DateInterval (line 14) | public function test_it_deep_copies_a_DateInterval()
FILE: tests/DeepCopyTest/TypeFilter/Date/DatePeriodFilterTest.php
class DatePeriodFilterTest (line 15) | class DatePeriodFilterTest extends TestCase
method test_it_deep_copies_a_DatePeriod (line 17) | public function test_it_deep_copies_a_DatePeriod()
method test_it_deep_copies_a_DatePeriod_with_exclude_start_date (line 29) | public function test_it_deep_copies_a_DatePeriod_with_exclude_start_da...
method test_it_deep_copies_a_DatePeriod_with_include_end_date (line 44) | public function test_it_deep_copies_a_DatePeriod_with_include_end_date()
method test_it_deep_copies_a_DatePeriod_with_end_date (line 56) | public function test_it_deep_copies_a_DatePeriod_with_end_date()
FILE: tests/DeepCopyTest/TypeFilter/ReplaceFilterTest.php
class ReplaceFilterTest (line 12) | class ReplaceFilterTest extends TestCase
method test_it_returns_the_callback_called_with_the_given_object (line 14) | public function test_it_returns_the_callback_called_with_the_given_obj...
FILE: tests/DeepCopyTest/TypeFilter/ShallowCopyFilterTest.php
class ShallowCopyFilterTest (line 12) | class ShallowCopyFilterTest extends TestCase
method test_it_shallow_copies_the_given_object (line 14) | public function test_it_shallow_copies_the_given_object()
FILE: tests/DeepCopyTest/TypeFilter/Spl/ArrayObjectFilterTest.php
class ArrayObjectFilterTest (line 17) | final class ArrayObjectFilterTest extends TestCase
method setUp (line 29) | protected function setUp(): void
method test_it_deep_copies_an_array_object (line 37) | public function test_it_deep_copies_an_array_object(): void
FILE: tests/DeepCopyTest/TypeFilter/Spl/SplDoublyLinkedListFilterTest.php
class SplDoublyLinkedListFilterTest (line 14) | class SplDoublyLinkedListFilterTest extends TestCase
method test_it_deep_copies_a_doubly_linked_spl_list (line 16) | public function test_it_deep_copies_a_doubly_linked_spl_list()
class FakeDeepCopy (line 32) | class FakeDeepCopy extends DeepCopy
method copy (line 37) | public function copy($object)
FILE: tests/DeepCopyTest/TypeMatcher/TypeMatcherTest.php
class TypeMatcherTest (line 12) | class TypeMatcherTest extends TestCase
method test_it_retrieves_the_object_properties (line 17) | public function test_it_retrieves_the_object_properties($type, $elemen...
method provideElements (line 26) | public function provideElements()
class Foo (line 40) | class Foo
class Bar (line 44) | class Bar extends Foo
type IA (line 48) | interface IA
class A (line 52) | class A implements IA
Condensed preview — 76 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (92K chars).
[
{
"path": ".gitattributes",
"chars": 313,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n*.png binary\n\n.github/ export-ignore\ndoc/ export-i"
},
{
"path": ".github/FUNDING.yml",
"chars": 665,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".github/workflows/ci.yaml",
"chars": 2909,
"preview": "name: \"Continuous Integration\"\n\non:\n - pull_request\n - push\n\nenv:\n COMPOSER_ROOT_VERSION: 1.99\n\njobs:\n composer-json"
},
{
"path": ".gitignore",
"chars": 62,
"preview": "/composer.phar\n/composer.lock\n/vendor/*\n.phpunit.result.cache\n"
},
{
"path": ".scrutinizer.yml",
"chars": 86,
"preview": "build:\n environment:\n variables:\n COMPOSER_ROOT_VERSION: '1.8.0'\n"
},
{
"path": "LICENSE",
"chars": 1077,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 My C-Sense\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 10767,
"preview": "# DeepCopy\n\nDeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the assoc"
},
{
"path": "composer.json",
"chars": 1022,
"preview": "{\n \"name\": \"myclabs/deep-copy\",\n \"description\": \"Create deep copies (clones) of your objects\",\n \"license\": \"MIT"
},
{
"path": "fixtures/f001/A.php",
"chars": 265,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f001;\n\nclass A\n{\n private $aProp;\n\n public function getAProp()\n"
},
{
"path": "fixtures/f001/B.php",
"chars": 275,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f001;\n\nclass B extends A\n{\n private $bProp;\n\n public function g"
},
{
"path": "fixtures/f002/A.php",
"chars": 460,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f002;\n\nclass A\n{\n private $prop1;\n private $prop2;\n\n public "
},
{
"path": "fixtures/f003/Foo.php",
"chars": 362,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f003;\n\nclass Foo\n{\n private $name;\n private $prop;\n\n public "
},
{
"path": "fixtures/f004/UnclonableItem.php",
"chars": 218,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f004;\n\nuse BadMethodCallException;\n\nclass UnclonableItem\n{\n privat"
},
{
"path": "fixtures/f005/Foo.php",
"chars": 173,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f005;\n\nclass Foo\n{\n public $cloned = false;\n\n public function _"
},
{
"path": "fixtures/f006/A.php",
"chars": 366,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f006;\n\nclass A\n{\n public $cloned = false;\n private $aProp;\n\n "
},
{
"path": "fixtures/f006/B.php",
"chars": 366,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f006;\n\nclass B\n{\n public $cloned = false;\n private $bProp;\n\n "
},
{
"path": "fixtures/f007/FooDateInterval.php",
"chars": 225,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f007;\n\nuse DateInterval;\n\nclass FooDateInterval extends DateInterval\n"
},
{
"path": "fixtures/f007/FooDateTimeZone.php",
"chars": 225,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f007;\n\nuse DateTimeZone;\n\nclass FooDateTimeZone extends DateTimeZone\n"
},
{
"path": "fixtures/f008/A.php",
"chars": 235,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f008;\n\nclass A\n{\n private $foo;\n\n public function __construct($"
},
{
"path": "fixtures/f008/B.php",
"chars": 80,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f008;\n\nclass B extends A\n{\n}\n"
},
{
"path": "fixtures/f009/TypedObjectProperty.php",
"chars": 117,
"preview": "<?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",
"chars": 103,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f009;\n\nclass TypedProperty\n{\n public int $foo;\n}\n"
},
{
"path": "fixtures/f011/ArrayObjectExtended.php",
"chars": 244,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f011;\n\nuse ArrayObject;\n\nclass ArrayObjectExtended extends ArrayObjec"
},
{
"path": "fixtures/f012/Suit.php",
"chars": 194,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f012;\n\nenum Suit: string\n{\n case Hearts = 'Hearts';\n case Diamo"
},
{
"path": "fixtures/f013/A.php",
"chars": 363,
"preview": "<?php\n\nnamespace DeepCopy\\f013;\n\nuse BadMethodCallException;\nuse Doctrine\\Persistence\\Proxy;\n\nclass A implements Proxy\n{"
},
{
"path": "fixtures/f013/B.php",
"chars": 502,
"preview": "<?php\n\nnamespace DeepCopy\\f013;\n\nuse BadMethodCallException;\nuse Doctrine\\Persistence\\Proxy;\n\nclass B implements Proxy\n{"
},
{
"path": "fixtures/f013/C.php",
"chars": 136,
"preview": "<?php\n\nnamespace DeepCopy\\f013;\n\nclass C\n{\n public $foo = 1;\n\n public function __clone()\n {\n $this->foo "
},
{
"path": "fixtures/f014/ReadonlyObjectProperty.php",
"chars": 237,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f014;\n\nclass ReadonlyObjectProperty\n{\n public readonly ReadonlySca"
},
{
"path": "fixtures/f014/ReadonlyScalarProperty.php",
"chars": 309,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopy\\f014;\n\nclass ReadonlyScalarProperty\n{\n public readonly string $foo"
},
{
"path": "phpunit.xml.dist",
"chars": 591,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:noNam"
},
{
"path": "src/DeepCopy/DeepCopy.php",
"chars": 8779,
"preview": "<?php\n\nnamespace DeepCopy;\n\nuse ArrayObject;\nuse DateInterval;\nuse DatePeriod;\nuse DateTimeInterface;\nuse DateTimeZone;\n"
},
{
"path": "src/DeepCopy/Exception/CloneException.php",
"chars": 127,
"preview": "<?php\n\nnamespace DeepCopy\\Exception;\n\nuse UnexpectedValueException;\n\nclass CloneException extends UnexpectedValueExcepti"
},
{
"path": "src/DeepCopy/Exception/PropertyException.php",
"chars": 120,
"preview": "<?php\n\nnamespace DeepCopy\\Exception;\n\nuse ReflectionException;\n\nclass PropertyException extends ReflectionException\n{\n}\n"
},
{
"path": "src/DeepCopy/Filter/ChainableFilter.php",
"chars": 449,
"preview": "<?php\n\nnamespace DeepCopy\\Filter;\n\n/**\n * Defines a decorator filter that will not stop the chain of filters.\n */\nclass "
},
{
"path": "src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php",
"chars": 840,
"preview": "<?php\n\nnamespace DeepCopy\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Filter;\nuse DeepCopy\\Reflection\\ReflectionHelper;\n\n/**\n "
},
{
"path": "src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php",
"chars": 757,
"preview": "<?php\n\nnamespace DeepCopy\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Filter;\nuse DeepCopy\\Reflection\\ReflectionHelper;\nuse Do"
},
{
"path": "src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php",
"chars": 403,
"preview": "<?php\n\nnamespace DeepCopy\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Filter;\n\n/**\n * @final\n */\nclass DoctrineProxyFilter imp"
},
{
"path": "src/DeepCopy/Filter/Filter.php",
"chars": 348,
"preview": "<?php\n\nnamespace DeepCopy\\Filter;\n\n/**\n * Filter to apply to a property while copying an object\n */\ninterface Filter\n{\n "
},
{
"path": "src/DeepCopy/Filter/KeepFilter.php",
"chars": 263,
"preview": "<?php\n\nnamespace DeepCopy\\Filter;\n\nclass KeepFilter implements Filter\n{\n /**\n * Keeps the value of the object pro"
},
{
"path": "src/DeepCopy/Filter/ReplaceFilter.php",
"chars": 964,
"preview": "<?php\n\nnamespace DeepCopy\\Filter;\n\nuse DeepCopy\\Reflection\\ReflectionHelper;\n\n/**\n * @final\n */\nclass ReplaceFilter impl"
},
{
"path": "src/DeepCopy/Filter/SetNullFilter.php",
"chars": 534,
"preview": "<?php\n\nnamespace DeepCopy\\Filter;\n\nuse DeepCopy\\Reflection\\ReflectionHelper;\n\n/**\n * @final\n */\nclass SetNullFilter impl"
},
{
"path": "src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php",
"chars": 360,
"preview": "<?php\n\nnamespace DeepCopy\\Matcher\\Doctrine;\n\nuse DeepCopy\\Matcher\\Matcher;\nuse Doctrine\\Persistence\\Proxy;\n\n/**\n * @fina"
},
{
"path": "src/DeepCopy/Matcher/Matcher.php",
"chars": 213,
"preview": "<?php\n\nnamespace DeepCopy\\Matcher;\n\ninterface Matcher\n{\n /**\n * @param object $object\n * @param string $prope"
},
{
"path": "src/DeepCopy/Matcher/PropertyMatcher.php",
"chars": 691,
"preview": "<?php\n\nnamespace DeepCopy\\Matcher;\n\n/**\n * @final\n */\nclass PropertyMatcher implements Matcher\n{\n /**\n * @var str"
},
{
"path": "src/DeepCopy/Matcher/PropertyNameMatcher.php",
"chars": 504,
"preview": "<?php\n\nnamespace DeepCopy\\Matcher;\n\n/**\n * @final\n */\nclass PropertyNameMatcher implements Matcher\n{\n /**\n * @var"
},
{
"path": "src/DeepCopy/Matcher/PropertyTypeMatcher.php",
"chars": 1368,
"preview": "<?php\n\nnamespace DeepCopy\\Matcher;\n\nuse DeepCopy\\Reflection\\ReflectionHelper;\nuse ReflectionException;\n\n/**\n * Matches a"
},
{
"path": "src/DeepCopy/Reflection/ReflectionHelper.php",
"chars": 2174,
"preview": "<?php\n\nnamespace DeepCopy\\Reflection;\n\nuse DeepCopy\\Exception\\PropertyException;\nuse ReflectionClass;\nuse ReflectionExce"
},
{
"path": "src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php",
"chars": 639,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Date;\n\nuse DateInterval;\nuse DeepCopy\\TypeFilter\\TypeFilter;\n\n/**\n * @final\n *\n * @"
},
{
"path": "src/DeepCopy/TypeFilter/Date/DatePeriodFilter.php",
"chars": 1098,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Date;\n\nuse DatePeriod;\nuse DeepCopy\\TypeFilter\\TypeFilter;\n\n/**\n * @final\n */\nclass"
},
{
"path": "src/DeepCopy/TypeFilter/ReplaceFilter.php",
"chars": 524,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter;\n\n/**\n * @final\n */\nclass ReplaceFilter implements TypeFilter\n{\n /**\n * @var"
},
{
"path": "src/DeepCopy/TypeFilter/ShallowCopyFilter.php",
"chars": 223,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter;\n\n/**\n * @final\n */\nclass ShallowCopyFilter implements TypeFilter\n{\n /**\n * "
},
{
"path": "src/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.php",
"chars": 720,
"preview": "<?php\nnamespace DeepCopy\\TypeFilter\\Spl;\n\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\TypeFilter;\n\n/**\n * In PHP 7.4 "
},
{
"path": "src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php",
"chars": 176,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Spl;\n\n/**\n * @deprecated Use {@see SplDoublyLinkedListFilter} instead.\n */\nclass Sp"
},
{
"path": "src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php",
"chars": 1040,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter\\Spl;\n\nuse Closure;\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\TypeFilter;\nuse S"
},
{
"path": "src/DeepCopy/TypeFilter/TypeFilter.php",
"chars": 194,
"preview": "<?php\n\nnamespace DeepCopy\\TypeFilter;\n\ninterface TypeFilter\n{\n /**\n * Applies the filter to the object.\n *\n "
},
{
"path": "src/DeepCopy/TypeMatcher/TypeMatcher.php",
"chars": 468,
"preview": "<?php\n\nnamespace DeepCopy\\TypeMatcher;\n\nclass TypeMatcher\n{\n /**\n * @var string\n */\n private $type;\n\n /"
},
{
"path": "src/DeepCopy/deep_copy.php",
"chars": 396,
"preview": "<?php\n\nnamespace DeepCopy;\n\nuse function function_exists;\n\nif (false === function_exists('DeepCopy\\deep_copy')) {\n /*"
},
{
"path": "tests/DeepCopyTest/DeepCopyTest.php",
"chars": 15940,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest;\n\nuse ArrayObject;\nuse DateInterval;\nuse DatePeriod;\nuse DateTime"
},
{
"path": "tests/DeepCopyTest/Filter/Doctrine/DoctrineCollectionFilterTest.php",
"chars": 1152,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Doctrine\\DoctrineCollection"
},
{
"path": "tests/DeepCopyTest/Filter/Doctrine/DoctrineEmptyCollectionFilterTest.php",
"chars": 1048,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter\\Doctrine;\n\nuse DeepCopy\\Filter\\Doctrine\\DoctrineEmptyColle"
},
{
"path": "tests/DeepCopyTest/Filter/Doctrine/DoctrineProxyFilterTest.php",
"chars": 805,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter\\Doctrine;\n\nuse BadMethodCallException;\nuse DeepCopy\\Filter"
},
{
"path": "tests/DeepCopyTest/Filter/KeepFilterTest.php",
"chars": 566,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter;\n\nuse DeepCopy\\Filter\\KeepFilter;\nuse PHPUnit\\Framework\\Te"
},
{
"path": "tests/DeepCopyTest/Filter/ReplaceFilterTest.php",
"chars": 1072,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter;\n\nuse DeepCopy\\Filter\\ReplaceFilter;\nuse PHPUnit\\Framework"
},
{
"path": "tests/DeepCopyTest/Filter/SetNullFilterTest.php",
"chars": 586,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Filter;\n\nuse DeepCopy\\Filter\\SetNullFilter;\nuse PHPUnit\\Framework"
},
{
"path": "tests/DeepCopyTest/Matcher/Doctrine/DoctrineProxyMatcherTest.php",
"chars": 1091,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher\\Doctrine;\n\nuse BadMethodCallException;\nuse DeepCopy\\Match"
},
{
"path": "tests/DeepCopyTest/Matcher/PropertyMatcherTest.php",
"chars": 973,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher;\n\nuse DeepCopy\\Matcher\\PropertyMatcher;\nuse PHPUnit\\Frame"
},
{
"path": "tests/DeepCopyTest/Matcher/PropertyNameMatcherTest.php",
"chars": 728,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher;\n\nuse DeepCopy\\Matcher\\PropertyNameMatcher;\nuse PHPUnit\\F"
},
{
"path": "tests/DeepCopyTest/Matcher/PropertyTypeMatcherTest.php",
"chars": 1924,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Matcher;\n\nuse DeepCopy\\f009;\nuse DeepCopy\\Matcher\\PropertyTypeMat"
},
{
"path": "tests/DeepCopyTest/Reflection/ReflectionHelperTest.php",
"chars": 2216,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\Reflection;\n\nuse DeepCopy\\Exception\\PropertyException;\nuse DeepCo"
},
{
"path": "tests/DeepCopyTest/TypeFilter/Date/DateIntervalFilterTest.php",
"chars": 579,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Date;\n\nuse DateInterval;\nuse DeepCopy\\TypeFilter\\Date\\"
},
{
"path": "tests/DeepCopyTest/TypeFilter/Date/DatePeriodFilterTest.php",
"chars": 1836,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Date;\n\nuse DateInterval;\nuse DatePeriod;\nuse DateTime;"
},
{
"path": "tests/DeepCopyTest/TypeFilter/ReplaceFilterTest.php",
"chars": 716,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter;\n\nuse DeepCopy\\TypeFilter\\ReplaceFilter;\nuse PHPUnit\\F"
},
{
"path": "tests/DeepCopyTest/TypeFilter/ShallowCopyFilterTest.php",
"chars": 611,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter;\n\nuse DeepCopy\\TypeFilter\\ShallowCopyFilter;\nuse PHPUn"
},
{
"path": "tests/DeepCopyTest/TypeFilter/Spl/ArrayObjectFilterTest.php",
"chars": 1458,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Spl;\n\nuse ArrayObject;\nuse DeepCopy\\DeepCopy;\nuse Deep"
},
{
"path": "tests/DeepCopyTest/TypeFilter/Spl/SplDoublyLinkedListFilterTest.php",
"chars": 892,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeFilter\\Spl;\n\nuse DeepCopy\\DeepCopy;\nuse DeepCopy\\TypeFilter\\S"
},
{
"path": "tests/DeepCopyTest/TypeMatcher/TypeMatcherTest.php",
"chars": 1156,
"preview": "<?php declare(strict_types=1);\n\nnamespace DeepCopyTest\\TypeMatcher;\n\nuse DeepCopy\\TypeMatcher\\TypeMatcher;\nuse PHPUnit\\F"
}
]
About this extraction
This page contains the full source code of the myclabs/DeepCopy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 76 files (80.7 KB), approximately 22.8k tokens, and a symbol index with 231 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.