├── .php_cs.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── psalm.xml.dist ├── src └── Cloneable.php └── undo_configure.sh /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `php-cloneable` will be documented in this file. 4 | 5 | ## 1.0.2 - 2024-02-06 6 | 7 | ### What's Changed 8 | 9 | * Fix README badges by @giggsey in https://github.com/spatie/php-cloneable/pull/9 10 | * Allow setting of uninitalised properties by @andrewnicols in https://github.com/spatie/php-cloneable/pull/10 11 | 12 | ### New Contributors 13 | 14 | * @giggsey made their first contribution in https://github.com/spatie/php-cloneable/pull/9 15 | * @andrewnicols made their first contribution in https://github.com/spatie/php-cloneable/pull/10 16 | 17 | **Full Changelog**: https://github.com/spatie/php-cloneable/compare/1.0.1...1.0.2 18 | 19 | ## 1.0.1 - 2023-02-20 20 | 21 | ### What's Changed 22 | 23 | - Use correct scope to assign inherited properties by @klkvsk in https://github.com/spatie/php-cloneable/pull/6 24 | 25 | ### New Contributors 26 | 27 | - @klkvsk made their first contribution in https://github.com/spatie/php-cloneable/pull/6 28 | 29 | **Full Changelog**: https://github.com/spatie/php-cloneable/compare/1.0.0...1.0.1 30 | 31 | ## 1.0.0 - 2021-11-23 32 | 33 | - Initial release 34 | 35 | ## 1.0.0 - 202X-XX-XX 36 | 37 | - initial release 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A trait that allows you to clone readonly properties in PHP 8.1 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/php-cloneable.svg?style=flat-square)](https://packagist.org/packages/spatie/php-cloneable) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/php-cloneable/run-tests.yml?label=tests&branch=main)](https://github.com/spatie/php-cloneable/actions?query=workflow%3ATests+branch%3Amaster) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/spatie/php-cloneable/php-cs-fixer.yml?label=code%20style&branch=main)](https://github.com/spatie/php-cloneable/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amaster) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/php-cloneable.svg?style=flat-square)](https://packagist.org/packages/spatie/php-cloneable) 7 | 8 | This package provides a trait that allows you to clone objects with readonly properties in PHP 8.1. You can read an in-depth explanation as to why this is necessary [here](https://stitcher.io/blog/cloning-readonly-properties-in-php-81). 9 | 10 | ## Support us 11 | 12 | [](https://spatie.be/github-ad-click/php-cloneable) 13 | 14 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 15 | 16 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 17 | 18 | ## Installation 19 | 20 | You can install the package via composer: 21 | 22 | ```bash 23 | composer require spatie/php-cloneable 24 | ``` 25 | 26 | ## Usage 27 | 28 | In PHP 8.1, readonly properties aren't allowed to be overridden as soon as they are initialized. That also means that cloning an object and changing one of its readonly properties isn't allowed. It's likely that PHP will get some kind of `clone with` functionality in the future, but for now you can work around this issue by using this package. 29 | 30 | ```php 31 | class Post 32 | { 33 | use Cloneable; 34 | 35 | public readonly string $title; 36 | 37 | public readonly string $author; 38 | 39 | public function __construct(string $title, string $author) 40 | { 41 | $this->title = $title; 42 | $this->author = $author; 43 | } 44 | } 45 | ``` 46 | 47 | The `Spatie\Cloneable\Cloneable` adds a `with` method to whatever class you want to be cloneable, which you can pass one or more parameters. Note that you're required to use named arguments. 48 | 49 | ```php 50 | $postA = new Post(title: 'a', author: 'Brent'); 51 | 52 | $postB = $postA->with(title: 'b'); 53 | $postC = $postA->with(title: 'c', author: 'Freek'); 54 | ``` 55 | 56 | A common practice would be to implement specific `with*` methods on the classes themselves: 57 | 58 | ```php 59 | class Post 60 | { 61 | // … 62 | 63 | public function withTitle(string $title): self 64 | { 65 | return $this->with(title: $title); 66 | } 67 | 68 | public function withAuthor(string $author): self 69 | { 70 | return $this->with(author: $author); 71 | } 72 | } 73 | ``` 74 | 75 | ### Caveats 76 | 77 | - This package will skip calling the constructor when cloning an object, meaning any logic in the constructor won't be executed. 78 | - The `with` method will do a shallow clone, meaning that nested objects aren't cloned as well. 79 | 80 | ## Testing 81 | 82 | ```bash 83 | composer test 84 | ``` 85 | 86 | ## Changelog 87 | 88 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 89 | 90 | ## Contributing 91 | 92 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 93 | 94 | ## Security Vulnerabilities 95 | 96 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 97 | 98 | ## Credits 99 | 100 | - [Brent Roose](https://github.com/brendt_gd) 101 | - [All Contributors](../../contributors) 102 | 103 | ## License 104 | 105 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 106 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/php-cloneable", 3 | "description": "A trait that allows you to clone readonly properties in PHP 8.1", 4 | "keywords": [ 5 | "spatie", 6 | "php_cloneable" 7 | ], 8 | "homepage": "https://github.com/spatie/php-cloneable", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Brent Roose", 13 | "email": "brent@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.1" 19 | }, 20 | "require-dev": { 21 | "friendsofphp/php-cs-fixer": "^2.17", 22 | "phpunit/phpunit": "^9.5", 23 | "vimeo/psalm": "^4.3" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Spatie\\Cloneable\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Spatie\\Cloneable\\Tests\\": "tests" 33 | } 34 | }, 35 | "scripts": { 36 | "psalm": "vendor/bin/psalm", 37 | "test": "vendor/bin/phpunit", 38 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 39 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" 40 | }, 41 | "config": { 42 | "sort-packages": true 43 | }, 44 | "minimum-stability": "dev", 45 | "prefer-stable": true 46 | } 47 | -------------------------------------------------------------------------------- /psalm.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Cloneable.php: -------------------------------------------------------------------------------- 1 | newInstanceWithoutConstructor(); 13 | 14 | foreach ($refClass->getProperties() as $property) { 15 | if ($property->isStatic()) { 16 | continue; 17 | } 18 | 19 | $objectField = $property->getName(); 20 | 21 | if (array_key_exists($objectField, $values)) { 22 | $objectValue = $values[$objectField]; 23 | } elseif ($property->isInitialized($this)) { 24 | $objectValue = $property->getValue($this); 25 | } else { 26 | continue; 27 | } 28 | 29 | $declarationScope = $property->getDeclaringClass()->getName(); 30 | if ($declarationScope === self::class) { 31 | $clone->$objectField = $objectValue; 32 | } else { 33 | (fn () => $this->$objectField = $objectValue) 34 | ->bindTo($clone, $declarationScope)(); 35 | } 36 | } 37 | 38 | return $clone; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /undo_configure.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "## revert to last commit" 3 | git reset head --hard 4 | git clean -f -d 5 | rm -fr vendor 6 | rm composer.lock 7 | --------------------------------------------------------------------------------