PHP-haastatteluun valmistautuminen

PHP-haastattelukysymykset

15 valittua PHP-taustaohjelmistokehityksen haastattelukysymystä ryhmiteltynä kokemustason mukaan. Käytä niitä perusasioiden, käytännön kompromissien ja senioritason tuotantopäätösten kertaamiseen.

Aloita PHP-tekoälyhaastatteluEi vaadi luottokorttia. 1 ilmainen sessio saatavilla.
Teknisten haastattelujen harjoittelu englanniksiTila, jossa ei-äidinkieliset puhujat voivat harjoitella teknisen haastattelun läpäisemistä.

Junior-tason kysymykset

1Miten PHP erottaa heikon ja tiukan tyypityksen, ja mitä declare(strict_types=1) muuttaa?

PHP sallii yleensä yhteensopivat skalaarimuunnokset ilmoitetuille parametri- ja paluutyypeille, esimerkiksi numeerisen merkkijonon int-parametrille. declare(strict_types=1) ottaa käyttöön tiukat skalaaritarkistukset: yhteensopimaton argumentti tai paluuarvo aiheuttaa TypeErrorin, mutta int hyväksytään yhä floatiksi. Käyttäjän määrittämien funktioiden argumenteissa tiukkuuden määrää kutsuva tiedosto; paluuarvoissa määrittelytiedosto. PHP:n sisäisistä funktioista tulevilla kutsuilla on omat erityisehtonsa, eikä niitä määrää vain ulomman kutsujan strict_types. Direktiivin tulee olla PHP-tiedoston alussa. Se ei tee PHP:stä staattisesti tyypitettyä eikä poista operaattorien muunnoksia koko kielestä.

// THREE SEPARATE FILES: save each PHP block under its indicated filename.
// File: lib.php
<?php
function takesInt(int $x): int { return $x; }
?>

// File: weak.php
<?php
require __DIR__ . '/lib.php';
echo takesInt('123'); // 123: coercive caller
?>

// File: strict.php
<?php
declare(strict_types=1);
require __DIR__ . '/lib.php';
try {
    echo takesInt('123');
} catch (TypeError $e) {
    echo 'TypeError';
}
?>
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

2Selitä PHP:n ==- ja ===-operaattorien ero sekä numeerisiin merkkijonoihin, taulukoihin, olioihin ja in_array()-funktioon liittyvät sudenkuopat.

PHP:n == tekee löyhän vertailun ja tarvittaessa tyyppimuunnoksia; === edellyttää samoja tyyppejä ja arvoja ilman skalaarimuunnoksia. Numeerisia merkkijonoja voidaan verrata lukuina, joten älä käytä löyhää vertailua tarkkaa tyyppiherkkää yhtäläisyyttä vaadittaessa. Taulukot ovat ==-vertailussa samat, jos ne sisältävät vertailusääntöjen mukaan samat avain/arvo-parit järjestyksestä riippumatta. === vaatii lisäksi saman järjestyksen ja vastaavat avainten ja arvojen tyypit. Olioiden == vertaa saman luokan olioiden ominaisuuksia; === vaatii saman instanssin. in_array on oletuksena löyhä; anna kolmanneksi argumentiksi true tiukkaa tarkistusta varten. Lukujen ja merkkijonojen löyhä vertailu muuttui PHP 8:ssa, joten älä luota historiallisiin muunnosesimerkkeihin ilman version mainintaa.

<?php
var_dump(123 == '123');      // loose: numeric string is converted
var_dump(123 === '123');     // strict: int !== string

$values = [0, 1, 2];
var_dump(in_array('0', $values));       // loose
var_dump(in_array('0', $values, true)); // strict
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

3Miten PHP:n paluutyypit mixed, void ja never eroavat ja mitä ne takaavat?

mixed sallii minkä tahansa paluuarvon, myös nullin. void ilmoittaa, ettei funktio palauta merkityksellistä arvoa: se voi päättyä tai käyttää return;-lausetta, mutta ei saa palauttaa lauseketta. Tavallinen void-funktion kutsu arvioituu nulliksi. PHP 8.1:stä saatava never tarkoittaa, ettei funktio koskaan palaa normaalisti kutsujalle. Se voi heittää poikkeuksen, lopettaa exitillä tai jäädä ikuiseen silmukkaan; sen ei tarvitse lopulta lopettaa prosessia. never-funktiossa ei saa olla return-lausetta. Keskeinen ero on, että void voi päättyä normaalisti ja kutsuja jatkaa, kun taas neverillä ei ole normaalia jatkoa.

<?php
function parse(string $raw): mixed {
    return json_decode($raw, true);
}

function logMessage(string $message): void {
    error_log($message);
    return; // allowed
}

function fail(string $message): never {
    throw new RuntimeException($message);
}

logMessage('starting');
fail('stop');
echo 'unreachable';
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

4Mikä on PHP:n iterable ja miten se liittyy tyyppeihin array, Traversable, Iterator ja Generator?

iterable tarkoittaa array|Traversable. Generator toteuttaa Iteratorin, joka laajentaa Traversablea, joten se täyttää iterable-ehdon. Käyttäjän määrittämät luokat tarjoavat Traversable-käyttäytymisen Iteratorin tai IteratorAggregaten kautta eivätkä toteuta Traversablea suoraan konkreettisessa luokassa. Iterator tarjoaa iteraatiota ohjaavat metodit; IteratorAggregate antaa iteraattorin. yieldin sisältävä generaattorifunktio palauttaa Generatorin. Tärkeä ero: foreach voi käydä läpi myös tavallisen, Traversablea toteuttamattoman olion näkyvät ominaisuudet, mutta olio ei siksi täytä iterable-parametrin määrittelyä.

function printItems(iterable $items): void {
    foreach ($items as $item) {
        echo $item, PHP_EOL;
    }
}

printItems(['a', 'b']);

function letters(): Generator {
    yield 'x';
    yield 'y';
}

printItems(letters());
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

5Mitkä ovat PHP:n public-, protected- ja private-näkyvyyssäännöt ja miten ne liittyvät periytymiseen ja traiteihin?

public-jäsenet ovat käytettävissä kaikkialla, missä olioon tai luokkaan pääsee käsiksi. protected-jäsenet ovat käytettävissä määrittelevässä luokassa ja siihen liittyvässä ylä- ja aliluokkahierarkiassa, eivät mielivaltaisesta ulkoisesta koodista. private-jäsenet ovat käytettävissä määrittelevässä luokassaan; aliluokka ei voi käyttää yliluokan yksityistä jäsentä perittynä rajapintanaan. Tavallinen korvaava metodi ei saa kaventaa perittyä näkyvyyttä. Konstruktorien periytymissäännöt ovat kuitenkin erityisiä, joten niitä ei pidä käsitellä ehdottomana esimerkkinä tästä säännöstä. Trait-jäsenet liitetään käyttävään luokkaan ja noudattavat sen näkyvyyssääntöjä. as voi luoda aliaksen, muuttaa trait-metodin näkyvyyttä tai tehdä molemmat; alias ei itsessään poista alkuperäistä nimeä.

<?php
class Base {
    public function a() {}
    protected function b() {}
    private function c() {}
}

trait Logs {
    public function log(string $message): void {}
}

class Child extends Base {
    use Logs { log as protected writeLog; }

    public function test(): void {
        $this->a();              // OK
        $this->b();              // OK
        // $this->c();           // Not accessible: private to Base
        $this->writeLog('ok');   // OK inside class, protected alias
    }
}

$child = new Child();
$child->a();
// $child->b();           // Not accessible from outside
// $child->writeLog('x'); // Not accessible from outside
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

6Miten PHP:n traitit toimivat ja miten saman metodin määrittelevien traitien ristiriidat ratkaistaan?

Traitit mahdollistavat koodin vaakasuuntaisen uudelleenkäytön: luokka tuo niiden jäsenet usella, eikä traitia voi yksinään instansioida. Luokassa määritelty metodi ohittaa trait-metodit, jotka ohittavat perityt yliluokan metodit. Jos useat traitit tarjoavat saman metodin eikä luokan oma metodi jo ratkaise ristiriitaa, valitse toteutus insteadofilla. Anna asilla trait-metodille lisänimi ja haluttaessa eri näkyvyys. Alias ei nimeä alkuperäistä metodia uudelleen eikä poista sitä. Nimiristiriitojen ratkaisu ja syntyvän julkisen rajapinnan selkeys ovat erillisiä vastuita.

<?php
trait JsonLogger {
    public function log(string $message): void {
        echo "json:$message\n";
    }
}

trait FileLogger {
    public function log(string $message): void {
        echo "file:$message\n";
    }
}

class Service {
    use JsonLogger, FileLogger {
        JsonLogger::log insteadof FileLogger;
        FileLogger::log as logToFile;
        JsonLogger::log as protected logJson;
    }
}

$service = new Service();
$service->log('hello');
$service->logToFile('hello');
// $service->logJson('hello'); // not accessible from outside; protected alias
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

7Mitä ovat PHP-luetelmointityypit (enums), mukaan lukien puhtaat ja tuetut luetelmointityypit, ja miten ne parantavat toimialueen mallinnusta?

PHP-luetelmointityypit (enums) määrittelevät suljetun joukon nimettäviä tapauksia tietylle tyypille, kuten tilausten tilat tai käyttäjäroolit. Puhtaissa luetelmointityypeissä on vain nimettävät tapaukset; tuetut luetelmointityypit antavat jokaiselle tapaukselle yksilöllisen merkkijono- tai kokonaisluku-arvon, mikä on hyödyllistä tallennusta, API-rajapintoja tai yhteentoimivuutta varten. Luetelmointityypit parantavat toimialueen mallinnusta korvaamalla hauraat raa'at merkkijonot, kokonaisluvut tai vakiot tyyppiturvallisilla, itseään dokumentoivilla arvoilla. PHP-luetelmointityypeillä voi olla myös metodeja ja ne voivat toteuttaa rajapintoja; kaikki luetelmointityypit tukevat tapaustensa luettelointia, ja tuetut luetelmointityypit tukevat muuntamista skalaariarvoihinsa ja niistä takaisin.

enum OrderState
{
    case Draft;
    case Paid;
    case Shipped;
}

enum PaymentStatus: string
{
    case Pending = 'pending';
    case Complete = 'complete';
    case Failed = 'failed';
}

$status = PaymentStatus::tryFrom('complete');
var_dump($status === PaymentStatus::Complete);
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Middle-tason kysymykset

8Miten skalaari-, nullable-, yhdiste-, leikkaus- ja DNF-tyypit ilmaisevat PHP-rajapintojen sopimuksia ja milloin niitä käytetään?

PHP:n määrittelyt kuvaavat hyväksyttyjä ja palautettuja arvoja. Skalaarit int, float, string ja bool valitsevat arvokategorian; yhteensopivien skalaarimuunnosten salliminen riippuu myös strict_types-asetuksesta ja kontekstista. ?T tai T|null sallii nullin. PHP 8.0:sta saatavat yhdistetyypit sallivat vaihtoehtoja kuten int|string. PHP 8.1:stä saatavat puhtaat leikkaustyypit vaativat olion täyttävän kaikki luetellut luokka-/rajapintatyypit, kuten Cacheable&JsonSerializable; int&string ei ole kelvollinen leikkaus. PHP 8.2:sta saatavat DNF-tyypit (disjunktiivinen normaalimuoto) yhdistävät vaihtoehtoja ja sulkeistettuja leikkauksia, kuten (Cacheable&JsonSerializable)|string. Valitse kapein ymmärrettävä sopimus kelvollisille syötteille lisäämättä tarpeettomia vaihtoehtoja.

<?php
interface Cacheable {}

class Report implements Cacheable, JsonSerializable {
    public function jsonSerialize(): mixed { return []; }
}

function findName(int $id): ?string {
    return $id > 0 ? 'Alice' : null;
}

function normalize(int|string $id): string {
    return (string) $id;
}

function store(Cacheable&JsonSerializable $item): void {
    // must satisfy both interfaces
}

function handle((Cacheable&JsonSerializable)|string $input): void {
    // either an object satisfying both contracts, or a string
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

9Miten callable, Closure, ensimmäisen luokan kutsuttavat arvot, kutsuttavat oliot ja callable-taulukot eroavat PHP:ssä?

callable kuvaa kutsuttavia arvoja: funktionimimerkkijonoja, Closure-olioita, viittauksia saavutettaviin metodeihin ja __invoke-metodin sisältäviä olioita. Closure on konkreettinen kutsuttava olio, joka voi syntyä anonyymista funktiosta tai Closureksi muunnetusta nimetystä funktiosta/metodista. Ensimmäisen luokan kutsuttavien syntaksi, kuten strlen(...) tai $object->method(...), luo Closuren kutsumatta kohdetta. [$object, 'method'] viittaa saavutettavaan instanssimetodiin; [ClassName::class, 'method'] vaatii nykyisessä PHP:ssä sopivan saavutettavan staattisen metodin. is_callable tarkistaa kutsuttavuuden kyseisessä näkyvyysalueessa. callable on laaja sopimus, Closure tietty esitysmuoto.

class Greeter {
    public function hello(string $name): string { return "Hello $name"; }
    public function __invoke(string $name): string { return "Hi $name"; }
}

$g = new Greeter();

$closure = fn(string $name) => "Hey $name";
$arrayCallable = [$g, 'hello'];
$firstClass = $g->hello(...);
$invokable = $g;

foreach ([$closure, $arrayCallable, $firstClass, $invokable] as $cb) {
    echo $cb('Sam'), PHP_EOL;
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

10Miten PHP käsittelee kokonaislukujen ylivuotoa, liukulukutarkkuutta ja tarkkoja desimaalilaskelmia?

PHP:n kokonaisluvut ovat alustariippuvaisia, ja niiden rajat ilmoitetaan vakioilla, kuten `PHP_INT_MAX`. PHP ei automaattisesti pidä natiiveja kokonaislukutuloksia mielivaltaisen tarkkuuden kokonaislukuina ylivuodon jälkeen; ylivuotoa aiheuttava kokonaislukuaritmetiikka voi muuttua `float`-tyyppiseksi tai muuten menettää tarkan kokonaislukusemantiikan. PHP:n liukuluvut ovat binäärisiä liukulukuja, joten monet desimaalimurtoluvut, kuten `0.1`, ovat likimääräisiä. Tarkkoja desimaali- tai rahalaskelmia varten vältä liukulukuja ja käytä tarkkaa lähestymistapaa, kuten kokonaislukupohjaisia pienimpiä yksiköitä (esim. sentit) tarvittaessa, tai kiinteän/mielivaltaisen tarkkuuden desimaalityökaluja, kuten BCMathia. GMP on hyödyllinen mielivaltaisen tarkkuuden kokonaislukuaritmetiikassa.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

11Selitä PHP:n viitteet (`&`), copy-on-write-mekanismi ja miten viitteet voivat aiheuttaa odottamattomia sivuvaikutuksia.

PHP-muuttujat esitetään sisäisesti `zval`-rakenteilla. Arvojen, kuten taulukoiden ja merkkijonojen, kohdalla PHP käyttää copy-on-write-menetelmää: `$b = $a` -sijoitus jakaa yleensä saman pohjalla olevan arvon, kunnes toista muuttujaa muutetaan, jolloin PHP erottaa/kopioi sen. Merkillä `&` luotu viite tekee muuttujista aliaksia samaan säilöön, joten toisen muuttaminen muuttaa myös toista ja voi ohittaa erottelukäyttäytymisen, jota ihmiset odottavat normaalista sijoituksesta. Viitteenä välittäminen antaa funktion muokata kutsujan muuttujaa. Viitteet voivat aiheuttaa yllättäviä sivuvaikutuksia, erityisesti `foreach ($array as &$value)` -silmukan kanssa: silmukkamuuttuja jää viitteeksi viimeiseen elementtiin silmukan jälkeen, joten sen uudelleenkäyttö myöhemmin voi vahingossa ylikirjoittaa kyseisen elementin, ellet kutsu `unset($value)`.

$a = [1, 2];
$b = $a;      // shared until modification
$b[0] = 99;   // separates; $a is unchanged
var_dump($a, $b);

$c =& $a;     // alias/reference
$c[0] = 42;   // modifies $a too
var_dump($a, $c);
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

12Miten olion käsittelyviittaukset, sijoitus (assignment), kloonaus (clone), `__clone`-metodi, matala kopio (shallow copy) ja syvä kopio (deep copy) toimivat PHP:ssä?

PHP:ssä olion muuttuja tallentaa olioon osoittavan viittauksen, ei täydellistä kopioita oliosta. Kun suoritetaan sijoitus kuten `$b = $a`, kopioidaan viittaus, jolloin molemmat muuttujat osoittavat samaan olioon. Olion muuttaminen kummankin muuttujan kautta on näkyvissä toisen kautta, ilman että käytetään `&`-operaattoria. `clone $a` luo uuden olioinstanssin, jossa on matala kopio alkuperäisen olion ominaisuuksista. Jos ominaisuus on itsessään olio, kloonatun olion ominaisuus osoittaa edelleen samaan sisäkkäiseen olioon, ellet erikseen kloonaa sitä. `__clone()`-metodia kutsutaan uudessa oliossa matalan kopion jälkeen, ja se on oikea paikka tunnusten nollaamiseen, resurssien irrottamiseen tai sisäkkäisten olioiden syvän kopion tekemiseen. Syvä kopio tarkoittaa sisäkkäisten muutettavissa olevien (mutable) olioiden rekursiivista kopiointia, joita ei tulisi jakaa.

class Address {
    public function __construct(public string $city) {}
}

class User {
    public function __construct(public string $name, public Address $address) {}

    public function __clone() {
        $this->address = clone $this->address; // make nested object independent
    }
}

$u1 = new User('Ann', new Address('Paris'));
$u2 = $u1;          // same object handle
$u2->name = 'Beth';
echo $u1->name, PHP_EOL;

$u3 = clone $u1;    // new User object; __clone deep-clones address
$u3->address->city = 'Rome';
echo $u1->address->city, PHP_EOL;
echo $u3->address->city, PHP_EOL;
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Senior-tason kysymykset

13Miten vianmäärittäisit tietokantayhteyksien ehtymisen (connection exhaustion) ja suunnittelisit yhteyshallinnan PHP-FPM:lle ja pitkäkestoisille työsäikeille?

Vianmääritykseen vertaisin tietokannan yhteysrajoituksia ja nykyisiä istuntoja PHP:n kokonaisrinnakkaisuuteen: jokainen aktiivinen PHP-FPM-lapsiprosessi voi pitää yllä omaa tietokantayhteyttään, ja useat sovellusaltaat (app pools), isännät, cron-tehtävät, jonotyöntekijät (queue workers), migraatiot ja käyttöönotot lisäävät kysyntää. Tarkistaisin tietokannan istuntonäkymät, yhteyksien tilat, `idle-in-transaction`-istunnot, hitaat kyselyt, lukitusodottamiset (lock waits), virhelokit, poolin metatiedot ja viimeaikaiset liikenne- tai työntekijämäärän muutokset. PHP-FPM:n osalta pitäisin `max_children`-arvon ja isäntäkohtaisen rinnakkaisuuden tietokantabudjetin puitteissa, käyttäisin lyhyitä pyyntökohtaisia transaktioita/yhteyksiä, tekisin `commit`- tai `roll back` -toimintoja nopeasti ja välttäisin yhteyksien pitämistä auki hitaiden ulkoisten kutsujen aikana. Pysyvät PDO-yhteydet voivat vähentää yhteydenmuodostuksen yleiskustannuksia, mutta ne voivat sitoa useita joutokäynnillä olevia yhteyksiä FPM-prosessia kohden ja säilyttää istuntotilan, joten ne vaativat varovaisuutta. Yhteyspooleri tai välityspalvelin (proxy), kuten PgBouncer, voi multipleksoida (multipleksata) useita PHP-asiakkaita pienemmäksi määräksi tietokantapalvelimen yhteyksiä, kun sovellus on yhteensopiva. Pitkäkestoiset työsäikeet tarvitsevat eksplisiittistä yhteyden elinkaaren hallintaa: kunnontarkistukset, uudelleenyhteyden muodostaminen joutokäyntiaikakatkaisujen/uudelleenkäynnistysten/vikatilanteiden/forkkausten jälkeen, istuntotilan nollaaminen ja vain turvallisten tai idempotenttisten töiden uudelleenyrittäminen.

Connection-budget example:
- 6 application servers
- 2 PHP-FPM pools on EACH server
- pm.max_children = 40 for EACH pool
- Assume at most 1 database connection per child

Potential request connections = 6 * 2 * 40 = 480.
If 40 were the TOTAL children per server across both pools, it would be 240.
Budget separately for queue workers, cron, deployments and administrators.
A limit of 300 database connections cannot accommodate 480 simultaneous connections.
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

14Miten käsittelisit lukureplikoita, lukujen ja kirjoitusten erottelua, replikointiviivettä ja ensisijaisen tietokannan vikasietoista vaihtoa PHP:ssä?

Erota ensisijaisen tietokannan ja replikoiden yhteydet operaation merkityksen mukaan, älä vain SQL:n SELECT-alkuisuuden perusteella: lukitsevat luvut, kirjoitukset ja ensisijaisen kannan konsistenssia vaativat transaktiot käyttävät ensisijaista kantaa. Pelkät lukutransaktiot voivat käyttää sopivaa replikaa konsistenssivaatimusten salliessa. Asynkroninen replikointi voi viivästyä. Laravelin sticky ohjaa kirjoituksen jälkeiset saman pyynnön luvut kirjoitusyhteydelle; kiinteä sitomisaika pyyntöjen välillä vain lieventää ongelmaa eikä ehdottomasti takaa omien kirjoitusten lukemista. Takuu edellyttää ensisijaisesta kannasta lukemista tai odottamista, kunnes replika on soveltanut vaaditun kirjoitusposition, aikarajalla ja paluulla ensisijaiseen kantaan. Välityspalvelimet eroavat: RDS Proxy käyttää erillisiä luku-/kirjoituspäätepisteitä eikä yleisesti jaa jokaista SQL-lausetta automaattisesti. Hylkää vanhentuneet yhteydet vikatilanteen vaihdossa ja yhdistä uudelleen rajatulla kasvavalla viiveellä. Kadonnut commit-vastaus jättää tuloksen epäselväksi: älä toista ei-idempotentteja kirjoituksia sokeasti; käytä idempotenssiavaimia tai selvitä ensin tulos.

<?php
// config/database.php
return [
    'mysql' => [
        'read' => [
            'host' => [env('DB_REPLICA_HOST_1'), env('DB_REPLICA_HOST_2')],
        ],
        'write' => [
            'host' => [env('DB_PRIMARY_HOST')],
        ],
        'sticky'    => true,
        'driver'    => 'mysql',
        'database'  => env('DB_DATABASE'),
        'username'  => env('DB_USERNAME'),
        'password'  => env('DB_PASSWORD'),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
    ],
];
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

15Miten tietokantamigraatiot ja skeemamuutokset otetaan turvallisesti käyttöön PHP-sovelluksissa ilman katkosta tai vähäisellä katkoksella?

Käytä laajennusta ja myöhempää vanhojen rakenteiden poistoa, jotka sallivat vanhan ja uuden sovellusversion asteittaisen käyttöönoton aikana. Lisää ensin yhteensopivat rakenteet, ota sitten käyttöön molempia esityksiä tukeva koodi. Jos tarvitaan kaksoiskirjoitusta, pidä se johdonmukaisena ja suunnittele jatkettava, nopeusrajoitettu historiatietojen täyttö, joka ei ylikirjoita uudempia kirjoituksia. Validoi kopioidut tiedot ja vaihda luvut ennen vanhojen koodipolkujen poistoa. Online-DDL (tiedon määrittelykieli) vähentää tiettyjä estymisriskejä mutta ei ole aina lukoton: tutki tietokanta- ja versiokohtaiset lukot, transaktiorajoitukset, resurssikulutus ja replikointivaikutukset. PostgreSQL:n CREATE INDEX CONCURRENTLY sisältää esimerkiksi rajoituksia ja voi epäonnistuessaan jättää virheellisen indeksin. Poista vanhat sarakkeet vasta vanhojen instanssien poistuttua ja vaaditun palautusajan päätyttyä. Tuhoavan poiston jälkeen paluu poistettuja tietoja tarvitsevaan koodiin ei enää ole automaattisesti turvallista.

<?php
// Step 1: Migration (Expand) -> Add new column as nullable
Schema::table('users', function (Blueprint $table) {
    $table->string('full_name')->nullable()->after('name');
});

// Step 2: Code deployment -> Write to both, read with fallback
class User extends Model {
    public function setNameAttribute($value) {
        $this->attributes['name'] = $value;
        $this->attributes['full_name'] = $value; // Dual write
    }
}

// Step 3: Background worker backfills existing rows in small batches
// Step 4: Code deployment -> Read/write exclusively from 'full_name'
// Step 5: Migration (Contract) -> Drop legacy 'name' column
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa