@@ -33,6 +33,7 @@ final class DeepClone
3333 private static array $ instantiableWithoutConstructor = [];
3434 private static array $ needsFullUnserialize = [];
3535 private static array $ hydrators = [];
36+ private static array $ simpleHydrators = [];
3637 private static array $ scopeMaps = [];
3738 private static array $ protos = [];
3839 private static array $ classInfo = [];
@@ -1002,7 +1003,7 @@ private static function getClassReflector($class, $instantiableWithoutConstructo
10021003
10031004 if ($ instantiableWithoutConstructor ) {
10041005 $ proto = $ reflector ->newInstanceWithoutConstructor ();
1005- } elseif (!$ isClass || $ reflector ->isAbstract ()) {
1006+ } elseif (!$ isClass || $ reflector ->isAbstract () || $ reflector -> isEnum () ) {
10061007 throw new \DeepClone \NotInstantiableException ('Type " ' .$ class .'" is not instantiable. ' );
10071008 } elseif ($ reflector ->name !== $ class ) {
10081009 $ reflector = self ::$ reflectors [$ name = $ reflector ->name ] ??= self ::getClassReflector ($ name , false , $ cloneable );
@@ -1183,6 +1184,202 @@ private static function getHydrator($class)
11831184 }
11841185 };
11851186 }
1187+
1188+ public static function deepclone_hydrate (object |string $ objectOrClass , array $ properties = [], array $ scopedProperties = []): object
1189+ {
1190+ if (\is_string ($ objectOrClass )) {
1191+ if (!\array_key_exists ($ objectOrClass , self ::$ cloneable )) {
1192+ self ::getClassReflector ($ objectOrClass );
1193+ }
1194+ $ r = self ::$ reflectors [$ objectOrClass ] ?? new \ReflectionClass ($ objectOrClass );
1195+ if (self ::$ cloneable [$ objectOrClass ]) {
1196+ $ object = clone self ::$ prototypes [$ objectOrClass ];
1197+ } elseif (self ::$ instantiableWithoutConstructor [$ objectOrClass ]) {
1198+ $ object = $ r ->newInstanceWithoutConstructor ();
1199+ } elseif (null === self ::$ prototypes [$ objectOrClass ]) {
1200+ throw new \DeepClone \NotInstantiableException ('Class " ' .$ objectOrClass .'" is not instantiable. ' );
1201+ } elseif ($ r ->implementsInterface ('Serializable ' ) && !method_exists ($ objectOrClass , '__unserialize ' )) {
1202+ $ object = unserialize ('C: ' .\strlen ($ objectOrClass ).':" ' .$ objectOrClass .'":0:{} ' );
1203+ } else {
1204+ $ object = unserialize ('O: ' .\strlen ($ objectOrClass ).':" ' .$ objectOrClass .'":0:{} ' );
1205+ }
1206+ } else {
1207+ $ object = $ objectOrClass ;
1208+ }
1209+
1210+ if ($ properties ) {
1211+ $ class = $ object ::class;
1212+ $ r ??= new \ReflectionClass ($ class );
1213+
1214+ foreach ($ properties as $ name => &$ value ) {
1215+ if (!\is_string ($ name )) {
1216+ throw new \ValueError ('deepclone_hydrate(): Argument #2 ($properties) must have only string keys ' );
1217+ }
1218+ if ("\0" === $ name ) {
1219+ $ scopedProperties [$ class ][$ name ] = &$ value ;
1220+ continue ;
1221+ }
1222+ if (\str_starts_with ($ name , "\0" )) {
1223+ $ sep = \strpos ($ name , "\0" , 1 );
1224+ if (false === $ sep ) {
1225+ continue ;
1226+ }
1227+ $ scopeName = \substr ($ name , 1 , $ sep - 1 );
1228+ $ realName = \substr ($ name , $ sep + 1 );
1229+
1230+ if ('* ' === $ scopeName ) {
1231+ $ scopeName = $ r ->hasProperty ($ realName ) ? $ r ->getProperty ($ realName )->class : $ class ;
1232+ }
1233+ } else {
1234+ $ realName = $ name ;
1235+ $ scopeName = $ r ->hasProperty ($ name ) ? $ r ->getProperty ($ name )->class : $ class ;
1236+ }
1237+
1238+ $ scopedProperties [$ scopeName ][$ realName ] = &$ value ;
1239+ }
1240+ unset($ value );
1241+ }
1242+
1243+ foreach ($ scopedProperties as $ scope => $ properties ) {
1244+ if (!\is_array ($ properties )) {
1245+ throw new \ValueError (\sprintf ('deepclone_hydrate(): Argument #3 ($scopedProperties) must have only array values, %s given for key "%s" ' , get_debug_type ($ properties ), $ scope ));
1246+ }
1247+ if (isset ($ properties ["\0" ]) && \is_array ($ properties ["\0" ])) {
1248+ $ special = $ properties ["\0" ];
1249+ unset($ properties ["\0" ]);
1250+
1251+ if ($ object instanceof \SplObjectStorage) {
1252+ for ($ i = 0 , $ c = \count ($ special ); $ i + 1 < $ c ; $ i += 2 ) {
1253+ $ object [$ special [$ i ]] = $ special [$ i + 1 ];
1254+ }
1255+ } elseif ($ object instanceof \ArrayObject || $ object instanceof \ArrayIterator) {
1256+ (new \ReflectionClass ($ object ))->getConstructor ()->invokeArgs ($ object , $ special );
1257+ }
1258+ }
1259+ if ($ properties ) {
1260+ (self ::$ simpleHydrators [$ scope ] ??= self ::getSimpleHydrator ($ scope ))($ properties , $ object );
1261+ }
1262+ }
1263+
1264+ return $ object ;
1265+ }
1266+
1267+ private static function getSimpleHydrator (string $ class ): \Closure
1268+ {
1269+ $ baseHydrator = self ::$ simpleHydrators ['stdClass ' ] ??= static function ($ properties , $ object ) {
1270+ foreach ($ properties as $ name => &$ value ) {
1271+ $ object ->$ name = $ value ;
1272+ $ object ->$ name = &$ value ;
1273+ }
1274+ };
1275+
1276+ switch ($ class ) {
1277+ case 'stdClass ' :
1278+ return $ baseHydrator ;
1279+
1280+ case 'TypeError ' :
1281+ $ class = 'Error ' ;
1282+ break ;
1283+
1284+ case 'ErrorException ' :
1285+ $ class = 'Exception ' ;
1286+ break ;
1287+
1288+ case 'SplObjectStorage ' :
1289+ return static function ($ properties , $ object ) {
1290+ foreach ($ properties as $ name => &$ value ) {
1291+ if ("\0" !== $ name ) {
1292+ $ object ->$ name = $ value ;
1293+ $ object ->$ name = &$ value ;
1294+ continue ;
1295+ }
1296+ for ($ i = 0 ; $ i < \count ($ value ); ++$ i ) {
1297+ $ object [$ value [$ i ]] = $ value [++$ i ];
1298+ }
1299+ }
1300+ };
1301+ }
1302+
1303+ if (!class_exists ($ class ) && !interface_exists ($ class , false ) && !trait_exists ($ class , false )) {
1304+ throw new \DeepClone \ClassNotFoundException ('Class " ' .$ class .'" not found. ' );
1305+ }
1306+ $ classReflector = new \ReflectionClass ($ class );
1307+
1308+ switch ($ class ) {
1309+ case 'ArrayIterator ' :
1310+ case 'ArrayObject ' :
1311+ $ constructor = $ classReflector ->getConstructor ()->invokeArgs (...);
1312+
1313+ return static function ($ properties , $ object ) use ($ constructor ) {
1314+ foreach ($ properties as $ name => &$ value ) {
1315+ if ("\0" === $ name ) {
1316+ $ constructor ($ object , $ value );
1317+ } else {
1318+ $ object ->$ name = $ value ;
1319+ $ object ->$ name = &$ value ;
1320+ }
1321+ }
1322+ };
1323+ }
1324+
1325+ if (!$ classReflector ->isInternal ()) {
1326+ $ notByRef = new \stdClass ();
1327+ foreach ($ classReflector ->getProperties () as $ propertyReflector ) {
1328+ if ($ propertyReflector ->isStatic ()) {
1329+ continue ;
1330+ }
1331+ if (\PHP_VERSION_ID >= 80400 && !$ propertyReflector ->isAbstract () && $ propertyReflector ->getHooks ()) {
1332+ $ notByRef ->{$ propertyReflector ->name } = $ propertyReflector ->setRawValue (...);
1333+ } elseif ($ propertyReflector ->isReadOnly ()) {
1334+ $ notByRef ->{$ propertyReflector ->name } = static function ($ object , $ value ) use ($ propertyReflector ) {
1335+ if (!$ propertyReflector ->isInitialized ($ object )) {
1336+ $ propertyReflector ->setValue ($ object , $ value );
1337+ }
1338+ };
1339+ }
1340+ }
1341+
1342+ return (function ($ properties , $ object ) {
1343+ $ notByRef = (array ) $ this ;
1344+
1345+ foreach ($ properties as $ name => &$ value ) {
1346+ if (!$ noRef = $ notByRef [$ name ] ?? false ) {
1347+ $ object ->$ name = $ value ;
1348+ $ object ->$ name = &$ value ;
1349+ } elseif (true !== $ noRef ) {
1350+ $ noRef ($ object , $ value );
1351+ } else {
1352+ $ object ->$ name = $ value ;
1353+ }
1354+ }
1355+ })->bindTo ($ notByRef , $ class );
1356+ }
1357+
1358+ if ($ classReflector ->name !== $ class ) {
1359+ return self ::$ simpleHydrators [$ classReflector ->name ] ??= self ::getSimpleHydrator ($ classReflector ->name );
1360+ }
1361+
1362+ $ propertySetters = [];
1363+ foreach ($ classReflector ->getProperties () as $ propertyReflector ) {
1364+ if (!$ propertyReflector ->isStatic ()) {
1365+ $ propertySetters [$ propertyReflector ->name ] = $ propertyReflector ->setValue (...);
1366+ }
1367+ }
1368+
1369+ if (!$ propertySetters ) {
1370+ return $ baseHydrator ;
1371+ }
1372+
1373+ return static function ($ properties , $ object ) use ($ propertySetters ) {
1374+ foreach ($ properties as $ name => $ value ) {
1375+ if ($ setValue = $ propertySetters [$ name ] ?? null ) {
1376+ $ setValue ($ object , $ value );
1377+ continue ;
1378+ }
1379+ $ object ->$ name = $ value ;
1380+ }
1381+ };
1382+ }
11861383}
11871384
11881385/**
0 commit comments