Skip to content

Commit e7f85be

Browse files
committed
Merge branch 'cleanup'
2 parents 52d86a0 + 798596d commit e7f85be

File tree

26 files changed

+626
-9
lines changed

26 files changed

+626
-9
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ jobs:
4949
- name: Check generated files
5050
run: scripts/generate.php --check
5151

52+
- name: Run PHP CS Fixer
53+
run: tools/php-cs-fixer check --diff --verbose
54+
5255
- name: Run PrettyPHP
5356
run: tools/pretty-php --diff
5457

.phive/phars.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phive xmlns="https://phar.io/phive">
33
<phar name="lkrms/pretty-php" version="^0.4.48" installed="0.4.48" location="./tools/pretty-php" copy="false"/>
4-
<phar name="php-cs-fixer" version="^3.46.0" installed="3.46.0" location="./tools/php-cs-fixer" copy="false"/>
5-
<phar name="salient-labs/php-changelog" version="^1.0.0" installed="1.0.0" location="./tools/changelog" copy="false"/>
4+
<phar name="php-cs-fixer" version="^3.46.0" installed="3.47.1" location="./tools/php-cs-fixer" copy="false"/>
5+
<phar name="salient-labs/php-changelog" version="^1.0.0" installed="1.0.1" location="./tools/changelog" copy="false"/>
66
</phive>

lk-util/Command/Http/SendHttpRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,6 @@ protected function run(string ...$args)
156156
$result = $array;
157157
}
158158

159-
echo Json::prettyPrint($result) . PHP_EOL;
159+
echo Json::prettyPrint($result) . \PHP_EOL;
160160
}
161161
}

scripts/run-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function run_with_php_versions() {
3535
die "must run from root of package folder"
3636

3737
run scripts/generate.php --check
38+
run tools/php-cs-fixer check --diff --verbose
3839
run tools/pretty-php --diff
3940
run_with_php_versions 83 74 vendor/bin/phpstan
4041
run scripts/stop-mockoon.sh || (($? == 1)) || die 'error stopping mockoon'

src/Cli/CliApplication.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ public function run()
396396
if ($args && $args[0] === '_json_schema') {
397397
array_shift($args);
398398
$schema = $command->getJsonSchema($args[0] ?? trim($this->getProgramName() . " $name") . ' options');
399-
echo Json::prettyPrint($schema) . PHP_EOL;
399+
echo Json::prettyPrint($schema) . \PHP_EOL;
400400
return $this;
401401
}
402402

src/Cli/CliCommand.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,17 @@ final public function __invoke(string ...$args): int
236236
return $this->ExitStatus;
237237
}
238238

239+
final public function __clone()
240+
{
241+
$this->Options = null;
242+
$this->OptionsByName = [];
243+
$this->PositionalOptions = [];
244+
$this->SchemaOptions = [];
245+
$this->DeferredOptionErrors = [];
246+
247+
$this->loadOptions();
248+
}
249+
239250
/**
240251
* @inheritDoc
241252
*/
@@ -1178,6 +1189,7 @@ private function loadOptions()
11781189
$this->Options = null;
11791190
$this->OptionsByName = [];
11801191
$this->PositionalOptions = [];
1192+
$this->SchemaOptions = [];
11811193
$this->DeferredOptionErrors = [];
11821194

11831195
throw $ex;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Lkrms\Exception;
4+
5+
/**
6+
* Thrown when an object cannot be cloned
7+
*/
8+
class UncloneableObjectException extends Exception {}

src/Sync/Command/GetSyncEntities.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ protected function run(string ...$args)
194194
$result = array_shift($result);
195195
}
196196

197-
echo Json::prettyPrint($result) . PHP_EOL;
197+
echo Json::prettyPrint($result) . \PHP_EOL;
198198
} else {
199199
$count = 0;
200200
foreach ($result as $entity) {
201-
echo Json::prettyPrint($entity->toArrayWith($rules)) . PHP_EOL;
201+
echo Json::prettyPrint($entity->toArrayWith($rules)) . \PHP_EOL;
202202
$count++;
203203
}
204204
}

src/Utility/Catalog/CopyFlag.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Lkrms\Utility\Catalog;
4+
5+
use Lkrms\Concept\Enumeration;
6+
use Lkrms\Utility\Get;
7+
8+
/**
9+
* Deep copy flags
10+
*
11+
* @extends Enumeration<int>
12+
*
13+
* @see Get::copy()
14+
*/
15+
final class CopyFlag extends Enumeration
16+
{
17+
/**
18+
* Do not throw an exception if an uncloneable object is encountered
19+
*/
20+
public const SKIP_UNCLONEABLE = 1;
21+
22+
/**
23+
* Assign values to properties by reference
24+
*
25+
* Required if an object graph contains nodes with properties passed or
26+
* assigned by reference.
27+
*/
28+
public const ASSIGN_PROPERTIES_BY_REFERENCE = 2;
29+
30+
/**
31+
* Take a shallow copy of objects with a __clone method
32+
*/
33+
public const TRUST_CLONE_METHODS = 4;
34+
35+
/**
36+
* Copy service containers
37+
*/
38+
public const COPY_CONTAINERS = 8;
39+
40+
/**
41+
* Copy singletons
42+
*/
43+
public const COPY_SINGLETONS = 16;
44+
}

src/Utility/Get.php

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44

55
use Lkrms\Concept\Utility;
66
use Lkrms\Contract\Arrayable;
7+
use Lkrms\Contract\IServiceShared;
8+
use Lkrms\Contract\IServiceSingleton;
9+
use Lkrms\Exception\UncloneableObjectException;
10+
use Lkrms\Utility\Catalog\CopyFlag;
11+
use Psr\Container\ContainerInterface;
12+
use DateTimeInterface;
13+
use DateTimeZone;
714
use ReflectionClass;
15+
use ReflectionObject;
16+
use UnitEnum;
817

918
/**
1019
* Get values from other values
@@ -82,6 +91,7 @@ public static function type($value): string
8291
? 'class@anonymous'
8392
: get_class($value);
8493
}
94+
8595
if (is_resource($value)) {
8696
return sprintf('resource (%s)', get_resource_type($value));
8797
}
@@ -109,15 +119,155 @@ public static function type($value): string
109119
public static function eol(string $string): ?string
110120
{
111121
$lfPos = strpos($string, "\n");
122+
112123
if ($lfPos === false) {
113124
return strpos($string, "\r") === false
114125
? null
115126
: "\r";
116127
}
128+
117129
if ($lfPos && $string[$lfPos - 1] === "\r") {
118130
return "\r\n";
119131
}
120132

121133
return "\n";
122134
}
135+
136+
/**
137+
* Get a deep copy of an object
138+
*
139+
* @template T of object
140+
*
141+
* @param T $object
142+
* @param class-string[] $skip
143+
* @param int-mask-of<CopyFlag::*> $flags
144+
* @return T
145+
*/
146+
public static function copy(
147+
object $object,
148+
array $skip = [],
149+
int $flags = CopyFlag::SKIP_UNCLONEABLE | CopyFlag::ASSIGN_PROPERTIES_BY_REFERENCE
150+
): object {
151+
return self::doCopy($object, $skip, $flags);
152+
}
153+
154+
/**
155+
* @template T of resource|mixed[]|object|int|float|string|bool|null
156+
*
157+
* @param T $var
158+
* @param class-string[] $skip
159+
* @param int-mask-of<CopyFlag::*> $flags
160+
* @param array<int,object> $map
161+
* @return T
162+
*/
163+
private static function doCopy(
164+
$var,
165+
array $skip,
166+
int $flags,
167+
array &$map = []
168+
) {
169+
if (is_resource($var)) {
170+
return $var;
171+
}
172+
173+
if (is_array($var)) {
174+
/** @var array<resource|mixed[]|object|int|float|string|bool|null> $var */
175+
foreach ($var as $key => $value) {
176+
$array[$key] = self::doCopy($value, $skip, $flags, $map);
177+
}
178+
return $array ?? [];
179+
}
180+
181+
if (!is_object($var) || $var instanceof UnitEnum) {
182+
return $var;
183+
}
184+
185+
$id = spl_object_id($var);
186+
if (isset($map[$id])) {
187+
return $map[$id];
188+
}
189+
190+
if ((
191+
!($flags & CopyFlag::COPY_CONTAINERS) &&
192+
$var instanceof ContainerInterface
193+
) || (
194+
!($flags & CopyFlag::COPY_SINGLETONS) && (
195+
$var instanceof IServiceSingleton ||
196+
$var instanceof IServiceShared
197+
)
198+
)) {
199+
$map[$id] = $var;
200+
return $var;
201+
}
202+
203+
foreach ($skip as $class) {
204+
if (is_a($var, $class)) {
205+
$map[$id] = $var;
206+
return $var;
207+
}
208+
}
209+
210+
$_var = new ReflectionObject($var);
211+
212+
if (!$_var->isCloneable()) {
213+
if ($flags & CopyFlag::SKIP_UNCLONEABLE) {
214+
$map[$id] = $var;
215+
return $var;
216+
}
217+
218+
throw new UncloneableObjectException(
219+
sprintf('%s cannot be copied', $_var->getName())
220+
);
221+
}
222+
223+
$clone = clone $var;
224+
$map[$id] = $clone;
225+
$id = spl_object_id($clone);
226+
$map[$id] = $clone;
227+
228+
if (
229+
$flags & CopyFlag::TRUST_CLONE_METHODS &&
230+
$_var->hasMethod('__clone')
231+
) {
232+
return $clone;
233+
}
234+
235+
if (
236+
$clone instanceof DateTimeInterface ||
237+
$clone instanceof DateTimeZone
238+
) {
239+
return $clone;
240+
}
241+
242+
$byRef = (bool) ($flags & CopyFlag::ASSIGN_PROPERTIES_BY_REFERENCE) &&
243+
!$_var->isInternal();
244+
foreach (Reflect::getAllProperties($_var) as $property) {
245+
if ($property->isStatic()) {
246+
continue;
247+
}
248+
249+
$property->setAccessible(true);
250+
251+
if (!$property->isInitialized($clone)) {
252+
continue;
253+
}
254+
255+
$name = $property->getName();
256+
/** @var resource|mixed[]|object|int|float|string|bool|null */
257+
$value = $property->getValue($clone);
258+
$value = self::doCopy($value, $skip, $flags, $map);
259+
260+
if (!$byRef) {
261+
$property->setValue($clone, $value);
262+
continue;
263+
}
264+
265+
(function () use ($name, $value): void {
266+
// @phpstan-ignore-next-line
267+
$this->$name = &$value;
268+
})->bindTo($clone, $property->getDeclaringClass()->getName())();
269+
}
270+
271+
return $clone;
272+
}
123273
}

0 commit comments

Comments
 (0)