A PHP nem engedi definiálni a tipizált tömböket. Bármely tömb bármilyen értéket tartalmazhat, ami bonyolulttá teszi a kodabázis konzisztenciájának kikényszerítését. Íme néhány megoldás, amelyek segítségével a meglévő PHP-szolgáltatások segítségével gépelhet objektumokat.
A probléma azonosítása
A PHP tömbök nagyon rugalmas adatstruktúra. Bármelyet hozzáadhat egy tömbhöz, a skaláris értékektől az összetett objektumokig:
$arr = [ "foobar", 123, new DateTimeImmutable() ];
A gyakorlatban ritka, hogy valóban egy ilyen változatos értéktartományú tömböt szeretne. Valószínűbb, hogy a tömbök több, azonos típusú értékű példányt tartalmaznak.
$times = [ new DateTimeImmutable(), new DateTimeImmutable(), new DateTimeImmutable() ];
Ezután létrehozhat egy metódust, amely a tömb összes értékére hat:
final class Stopwatch { protected array $laps = []; public function recordLaps(array $times) : void { foreach ($times as $time) { $this -> laps[] = $time -> getTimestamp(); } } }
Ez a kód a DateTimeInterface
példányok ban ben $times
. Ezután az idő Unix időbélyeg-ábrázolása (egész számban mért másodperc) tárolásra kerül $laps
.
A baj ezzel a kóddal az, hogy egy feltevés hogy $times
teljes egészében a következőkből áll: DateTimeInterface
példányok. Semmi sem garantálja, hogy ez a helyzet, így a hívó továbbra is átadhat egy vegyes értékű tömböt. Ha az egyik érték nem valósult meg DateTimeInterface
, a hívás getTimestamp()
törvénytelen lenne, és futásidejű hiba lépne fel.
$stopwatch = new Stopwatch(); // OK $stopwatch -> recordLaps([ new DateTimeImmutable(), new DateTimeImmutable() ]); // Crash! $stopwatch -> recordLaps([ new DateTimeImmutable(), 123 // can't call `getTimestamp()` on an integer! ]);
Típuskonzisztencia hozzáadása a változatos argumentumokkal
Ideális esetben a kérdést úgy oldanák meg, hogy meghatározzák, hogy a $times
tömb csak tartalmazhat DateTimeInterface
példányok. Mivel a PHP nem támogatja a tipizált tömböket, helyette alternatív nyelvi jellemzőket kell keresnünk.
Az első lehetőség a variadikus argumentumok használata és a $times
tömb, mielőtt átadnák neki recordLaps()
. A Variadic argumentumok lehetővé teszik a függvény számára, hogy ismeretlen számú argumentumot fogadjon el, amelyeket aztán egyetlen tömbként tesznek elérhetővé. Ami a felhasználási esetünket illeti, a szokásos módon tipizálhat variadikus argumentumokat. Minden átadott argumentumnak a megadott típusúnak kell lennie.
Variadikus argumentumokat szoktak használni a matematikai függvényeknél. Íme egy egyszerű példa, amely összefoglalja az összes argumentumot:
function sumAll(int ...$numbers) { return array_sum($numbers); } echo sumAll(1, 2, 3, 4, 5); // emits 15
sumAll()
nem ad át tömböt. Ehelyett több argumentumot kap, amelyeket a PHP egyesít a $numbers
sor. A int
A typehint azt jelenti, hogy minden értéknek egész számnak kell lennie. Ez garanciát jelent arra $numbers
csak egész számokból áll. Ezt most alkalmazhatjuk a stopperóra példájára:
final class Stopwatch { protected array $laps = []; public function recordLaps(DateTimeInterface ...$times) : void { foreach ($times as $time) { $this -> laps[] = $time -> getTimestamp(); } } } $stopwatch = new Stopwatch(); $stopwatch -> recordLaps( new DateTimeImmutable(), new DateTimeImmutable() );
Nem támogatott típusokat továbbítani már nem lehet recordLaps()
. Az erre irányuló próbálkozások jóval korábban, a getTimestamp()
megkísérli a hívást.
Ha már van egy sor alkalom, hogy átmehessen recordLaps()
, ki kell csomagolnia a splat operátor (...
), amikor meghívja a módszert. Ha megpróbálja átadni közvetlenül, akkor nem sikerül – a változatok egyikének kell tekinteni, amelyeknek kötelezőnek kell lenniük int
és nem egy array
.
$times = [ new DateTimeImmutable(), new DateTimeImmutable() ]; $stopwatch -> recordLaps(...$times);
A változatos érvek korlátai
A Variadic argumentumok nagy segítséget jelenthetnek, ha egy elemtömböt át kell adnia egy függvénynek. Van azonban néhány korlátozás a felhasználásuk módjára vonatkozóan.
A legfontosabb korlátozás az, hogy függvényenként csak egy variadikus argumentumkészlet használható. Ez azt jelenti, hogy minden funkció csak egy „tipizált” tömböt fogadhat el. Ezenkívül a variadikus argumentumot minden szabályos argumentum után utoljára kell meghatározni.
function variadic(string $something, DateTimeInterface ...$times);
A variadikus argumentumok természetüknél fogva csak függvényekkel használhatók. Ez azt jelenti, hogy nem tudnak segíteni, amikor szükség van rá bolt egy tömb tulajdonságként, vagy adja vissza egy függvényből. Láthatjuk ezt a stopper kódban – a Stopwatch
osztálynak van egy laps
tömb, amely csak egész időbélyegek tárolására szolgál. Jelenleg nem tudjuk ezt érvényesíteni.
Gyűjtemény osztályok
Ilyen körülmények között más megközelítést kell választani. Az egyik módja annak, hogy valami hasonlót hozzon létre egy „tipizált tömbhöz” a userland PHP-ben, egy dedikált gyűjtemény osztály írása:
final class User { protected string $Email; public function getEmail() : string { return $this -> Email; } } final class UserCollection implements IteratorAggregate { private array $Users; public function __construct(User ...$Users) { $this -> Users = $Users; } public function getIterator() : ArrayIterator { return new ArrayIterator($this -> Users); } }
A UserCollection
osztály mostantól bárhol használható, amire általában egy tömbre számíthat User
példányok. UserCollection
variadikus argumentumokat használ a sorok elfogadásához User
példányai a konstruktorában. Habár a $Users
tulajdonságot általánosként kell gépírni array
, garantáltan teljes egészében felhasználói példányokból áll, mivel csak a konstruktorban írják.
Csábítónak tűnhet a get() : array
módszer, amely a gyűjtemény összes elemét feltárja. Ezt el kell kerülni, mivel ez visszavezet minket a homályba array
typepint probléma. Ehelyett a gyűjtemény iterálhatóvá tette hogy a fogyasztók a foreach
hurok. Ily módon sikerült létrehoznunk egy tipet-képes „tömböt”, amelyről kódunk biztonságosan feltételezheti, hogy csak felhasználókat tartalmaz.
function sendMailToUsers(UserCollection $Users) : void { foreach ($Users as $User) { mail($user -> getEmail(), "Test Email", "Hello World!"); } } $users = new UserCollection(new User(), new User()); sendMailToUsers($users);
A gyűjtemények tömbszerűbbé tétele
A gyűjtési osztályok megoldják a gépírás problémáját, de azt jelentik, hogy elveszíti a hasznos tömbök funkcionalitása. Beépített PHP funkciók, mint count()
és isset()
nem fog működni az egyéni gyűjtemény osztályával.
Ezen funkciók támogatása további beépített interfészek megvalósításával adható hozzá. Ha megvalósítja Countable
, az osztályod használható lesz count()
:
final class UserCollection implements Countable, IteratorAggregate { private array $Users; public function __construct(User ...$Users) { $this -> Users = $Users; } public function count() : int { return count($this -> Users); } public function getIterator() : ArrayIterator { return new ArrayIterator($this -> Users); } } $users = new UserCollection(new User(), new User()); echo count($users); // 2
Végrehajtás ArrayAccess
lehetővé teszi a tömbszintaxis segítségével a gyűjteménye elemeinek elérését. Ez lehetővé teszi a isset()
és unset()
funkciókat. Négy módszert kell alkalmaznia, hogy a PHP kölcsönhatásba léphessen az elemeivel.
final class UserCollection implements ArrayAccess, IteratorAggregate { private array $Users; public function __construct(User ...$Users) { $this -> Users = $Users; } public function offsetExists(mixed $offset) : bool { return isset($this -> Users[$offset]); } public function offsetGet(mixed $offset) : User { return $this -> Users[$offset]; } public function offsetSet(mixed $offset, mixed $value) : void { if ($value instanceof User) { $this -> Users[$offset] = $value; } else throw new TypeError("Not a user!"); } public function offsetUnset(mixed $offset) : void { unset($this -> Users[$offset]); } public function getIterator() : ArrayIterator { return new ArrayIterator($this -> Users); } } $users = new UserCollection( new User("example@example.com"), new User("hello@world.com") ); echo $users[1] -> getEmail(); // hello@world.com var_dump(isset($users[2])); // false
Most van egy osztálya, amely csak tartalmazhat User
példányok, és amely szintén tömbnek tűnik és érződik. Egy megjegyzendő pont ArrayAccess
az a offsetSet
megvalósítás – mint $value
kell, hogy legyen mixed
, ez lehetővé teheti inkompatibilis értékek hozzáadását a gyűjteményéhez. Kifejezetten ellenőrizzük az átadott típusát $value
ennek megakadályozására.
Következtetés
Friss PHP kiadások a nyelvet erősebb gépelés és nagyobb konzisztencia felé fejlesztették. Ez még nem terjed ki a tömb elemekre. Gépírás ellen array
gyakran túl nyugodt, de megkerülheti a korlátozásokat saját gyűjtőosztályainak felépítésével.
Variadikus argumentumokkal kombinálva a gyűjteményminta életképes módszer az összesített értékek típusainak kikényszerítésére a kódban. Gépelheti géppel a gyűjteményeit, és megismételheti azokat, tudván, hogy csak egy típusú érték lesz jelen.