Google+ Followers

donderdag 12 november 2015

Why will OR DIE() not just... well, die?

"or die()" is possibly the stupidest thing in PHP. Every experienced PHP programmer will tell you that it's a bad idea and that PHP supports exceptions. Pretty much every other language has a structure for stopping a script with some sort of ternary OR operator, but you rarely see it used.

The problem with "or die()" is of course that 'die()' literally stops the script, right there and then. But in just about every case I've ever seen, the programmer did not want that, he wanted to stop further processing.

What's the difference? When you "stop further processing", you simply skip that part of the script that you cannot execute because of the error. All the code that is not affected by the error is still executed. This is important because "the rest of the code" includes the code that reports the error that was encoutered. If you just stop the script then no output is generated and your visitor will see a blank page that does not even tell him that there was an error. He cant report the error to you, he can't continue to a different page, he will press F5 to see if the page just failed to load, and when it doesn't he will go to a different website.

Not stopping the script also prevents you from corrupting your data. For example; if your script opens a file to write new content to it, you will typically (not correctly, but typically) open the file, reset it and then start processing and writing new content. If the orocessing fails and you stop the script outright, you will be left with an incomplete file, which breaks other processes. Brilliant.

When you stop further processing, you abort the processing and writing of the new content, and you will logically come to a point where you wonder ök, so what do I do with the fileif I can't update it?" and then you take the proper route where you write the new content to a new file and do a delete/rename once you have confirmed that the new file is correct.

There is no need for or-die() and it really doesn't matter if you think that it's easy to sue during debugging, because you *will* forget a couple of these statements and the *will* break your data.

Yeah, it's won't hapen to you, of course not, untill it does.

Use exceptions. Start your script with proper error handling, it will save your bacon.

woensdag 11 november 2015

PHP forums, worden ze ooit volwassen?

PHPHulp.nl:

Iemand stelt een vraag: "Als ik een selectbox maak met de namen van mijn gebruikers erin dan worden de spaties vervangen door underscores, waardoor ik na het submitten de namen niet meer kan terugvinden in de database. Hoe komt dat?"

De oplossing die het PHP forum voorstelt: "dan moet je het id gebruiken, niet de naam".

Huh? Als spaties niet goed doorkomen moet je iets gebruiken dat geen spaties bevat? Waarom niet gewoon uitzoeken naar waarom die spaties worden vervangen? Want dat is blijkbaar niet verwacht en niet gewenst. *Dat* is dus het probleem.


Maar moet je opletten wat er wordt gezegd als ik dat voorstel: "dankzij de tips die hij van ons heeft gekregen is hij nu wel een stuk verder".

Ja, een stuk verder in de problemen want de data wordt nog steeds verkeerd doorgegeven en de enige indicatie die hij had dat het *fout gaat* is door jullie brilliante voorstel nu onzichtbaar geworden. Goed werk hoor!


Dit oogkleppen, kop in het zand, als ik het niet zie dan is het er niet, gedrag is kenmerkend voor de PHP community. Nadenken lijkt verboden, oorzaken mogen niet worden aangepakt, elk probleem moet worden opgelost via een vorm van symptoombestrijding. Heb je een undefined-variable warning? Dan zet je gewoon je error_reporting lager, want wat voor kwaad kan een undefined variable nou helemaal? Kan je datase iets niet? Dan doe je het gewoon in een scriptje, dat is toch sneller.


"Werken met het id is de netste manier".

Nee, het is de manier die jij hebt aangeleerd gekregen van iemand anders die het zo deed. Een natural-primary-key is het netst en dat deed de poster ook; hij werkte een gebruiker bij aan de hand van zijn gebruikersnaam, zodat je altijd zeker weet dat je de juiste gebruiker aanspreekt. Een surrogate primary key kan op elk moment veranderen, zie de boeken van Joe Celko. Ik heb dat zelf ook meegemaakt bij webshop waarvan een van de leveranciers die een dump/restore had gedaan van zijn database, en bij de restore  de id kolom van de producttabel had overgeslagen zodat alle producten een nieuw id kregen. Gelukkig synchroniseerde de webshop op EAN code plus SKU, maar blijkbaar hebben andere webshops dagenlang de producten verkocht tegen verkeerde prijzen.


Natuurlijk, een surrogate primary key heeft zo z'n voordelen en in de alledaagse praktijk van de gemiddelde website zul je er geen last van hebben, maar *dat was het probleem niet*.

Als je auto niet wil starten als het koud is dan ga je toch ook niet met de fiets tot het weer zomer is?

Klinkt dit gefrustreerd? Vastwel, want ik wordt kenttergek van die houding.

Tuurlijk PHP is bij uitstek een taal voor beginners, maar als je constant wordt omringd door mensen die het zelf ook niet begrijpen en je voorzien van de domste adviezen, dan kom je ook nooit ergens.

vrijdag 6 november 2015

Waarom ik PostgreSQL leuker vindt dan MySQL: Snel testdata genereren.

Stel je wilt een query testen en je hebt een tabel nodig met 40.000 records. In MySQL kun je dat het snelst doen door een tabel te maken met één record en vervolgens dat record te kopieren via INSERT INTO tabel SELECT * FROM tabel; wat twee records oplevert en als je het nog eens doet 4 dan 8,16 etc, tot je na 15 keer op 32.000 zit. Probleem: dan heb je 32.000 dezelfde records, dus je moet in je SELECT ook nog iets doen om de inhoud van de data te veranderen zodat je data een beetje zinnig is voor een performance test. In elk van de 15 iteraties moet je die data weer bijstellen en gedoe gedoe gedoe. Het komt dan ook veel voor dat programmeurs een scriptje schrijven dat 40.000 rijen aanmaakt, wat tijd kost om te schrijven en uit te voeren.

In PostgreSQL kan het allemaal een stuk eenvoudiger, zoals gewoonlijk, via GENERATE_SERIES().

De GENERATE_SERIES() functie doet wat de naam zegt; het genereert een serie. Dat kan een serie getallen zijn:

SELECT GENERATE_SERIES(5, 20, 1);

Genereert een serie van 5 tot en met 10  in stapjes van 1.


Het kan ook met datums:

SELECT GENERATE_SERIES('2010-01-01'::timestamptz, '2010-01-10'::timestamptz, interval '1 hour');

Dat genereert een lijst van datums beginnende op 2010-01-01 00:00:00 en eindigend op 2010-01-10 00:00:00, in stapjes van één uur.


Het aanmaken van 40.000 records is hiermee ineens kinderspel:

INSERT INTO tabel SELECT id  FROM GENERATE_SERIES(1,40000) AS id;

En als elk record een eigen datum moet krijgen dan kun je die ook laten berekenen via het id:

INSERT INTO tabel SELECT id, '2010-01-01'::TIMESTAMP + id * INTERVAL '1 hour'
FROM GENERATE_SERIES(1,40000) AS id;

Om het iets realistischer te maken kun je er met RANDOM() nog wat willekeur in brengen:
INSERT INTO tabel SELECT id, '2010-01-01'::TIMESTAMP + RANDOM() * INTERVAL '1 month'
FROM GENERATE_SERIES(1,40000) AS id;

Deze vult de 40.000 records met willekeurige datums die vallen tussen 2010-01-01 00:00:00 en 2010-02-01 00:00:00.


Willekeurige strings toevoegen gaat kan natuurlijk ook. Je kunt bijvoorbeeld volledig willekeurige strings opbouwen uit een characterset:

SELECT id
, '2010-01-01'::TIMESTAMP + RANDOM() * INTERVAL '1 month'
, (SELECT string_agg(x, '')
  FROM (
    SELECT substr('          abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', floor(random() * 46)::integer,1)
    FROM generate_series(1, (140*random())::int + id * 0)
  ) AS y(x)) as g
FROM GENERATE_SERIES(1,40000) AS id;

Dit ziet er ingewikkeld uit maar de substr() pakt gewoon 1 letter uit de lange string waar het hele alphabet in staat, met tien spaties om wat meer kans op een spate te geven. De 140*random() zorg voor een willekeurige lengte en de 'id*0' is een truukje om te zorgen dat de subquery niet wordt weg-geoptimaliseerd door de queryplanner.

Het uitvoeren van die laatste query duurt op mijn Intel -i7 laptop 1.8 seconden, dus snel genoeg om onderdeel te maken van een unittest.

donderdag 5 november 2015

Input validatie en query-escaping

Onlangs had ik op phphulp een discussie met iemand die de uitspraak deed dat "een waarde die door je input-validatie heen komt in principe niet ge-escaped hoeft te worden in de query" .


Wat hij bedoelde, zo bleek later, was dat getallen geen tekens bevatten die behandeld moeten worden om in een query gebruikt te kunnen worden. Ergo; als je een input hebt kunnen valideren als zijnde een getal, dan is escapen voor die waarde niet strikt noodzakelijk.


Strict genomen is dat waar; je hoeft getallen niet te bewerken om ze in een query te zetten.
Maar wat heb je aan deze kennis?

Helemaal niets.






Zie hoe ik het woord getallen overal benadruk: de stelling gaat alleen op voor
getallen, niet voor strings of expressies die uit meer dan alleen cijfers bestaan. Het is niet mogelijk om een achternaam zo te valideren dat er alleen waarden uit komen die veilig zijn in een query. Dat zou Jaques 'd Ancona niet leuk vinden.


Daarbij valideer je de data voordat je de data gaat bewerken en tijdens die bewerking kan de gevalideerde data zo worden aangepast dat hij niet meer veilig is voor een query.

Tot slot is er altijd de mogelijkheid dat de data die de query ingeduwd wordt niet eens uit de validatie vandaankomt, een simpele tikfout in de naam van een variabele en je zit ongevalideerde data in te voeren.


In de praktijk zul je dus altijd moeten escapen, ongeacht welk type data je validatie zegt te hebben gevonden. Om die reden hebben databases het principe van het prepared-statement bedacht. Bij een prepared statement geef je de query syntax en de waarden die je in de query wilt gebruiken apart op, zodat de queryparser niet zelf hoeft uit te zoeken waar de data begint en eindigt.

"Jamaar, hij zegt toch ook 'in principe'"?

Klopt, en of je begrijpt wat hij daarmee bedoelt hangt helemaal van jou af. Er zwerven op het net heel wat PHP-classes rond die de data die naar een query wordt gestuurd door dingen als is_numeric heen halen om te bepalen of ze wel of niet moeten escapen en quoten. Waarom? Omdat ze ooit hebben gehoord dat getallen "in principe" niet ge-escapet of gequote hoeven te worden. Er worden potentiele beveiligingsgaten ingebouwd door dit soort slechtgeformuleerde uitspraken.

"Wat is dan het nut van input-validatie?"

Het nut van input validatie staat buiten kijf, het is *de* manier om te zorgen dat de data die je krijgt aangeleverd zinnig is. Als je een postcode verwacht en er staat "hallo hier Hilversum" in, dan kun je de verdere verwerking overslaan want de data is onzin.

Maar input validatie heeft *niets* te maken met queries of escaping. het is puur en alleen bedoeld als eerste controle om te zien of je data is wat je verwacht.