Mysqli/PDO/Doctrine wel/geen prepared statements en character encoding
Thomas van den Heuvel op 29/09/2019 16:49:24:
Dit is simpelweg niet waar. Het enige wat veiligheid ten goede komt is een juist gebruik. Wanneer je prepared statements verkeerd gebruikt is dit even onveilig als enige andere methode.
Ook dit klopt niet (helemaal). Je moet gewoon alle DATA in je query escapen. Het maakt dan niet uit of deze onveilig was of niet.
Frank Nietbelangrijk op 29/09/2019 14:45:53:
Huh? prepared statements zijn een stuk veiliger als er zoals hier een get variabele in de query wordt opgenomen...
Dit is simpelweg niet waar. Het enige wat veiligheid ten goede komt is een juist gebruik. Wanneer je prepared statements verkeerd gebruikt is dit even onveilig als enige andere methode.
- Ariën - op 29/09/2019 14:51:10:
Maar bij beiden moet je ervoor zorgen dat de invoer veilig is.
Ook dit klopt niet (helemaal). Je moet gewoon alle DATA in je query escapen. Het maakt dan niet uit of deze onveilig was of niet.
Ik denk dat Thomas gelijk heeft in het opzicht van veiligheid. Toch ben ik wel een beetje nieuwsgierig waarom Thomas schrijft dat je gewoon alle DATA in je query moet escapen. Naar deze onderbouwing ben ik wel nieuwsgierig.
Ik denk dat het wel beter en makkelijker is om prepared statements te gebruiken omdat je niet na hoeft te denken over quotes en omdat je query zelf geen variabelen bevat maar placeholders. Placeholders zijn er overigens weer in twee of drie smaken: De vraagtekens waarbij de volgorde erg belangrijk is en de :name placeholders ofwel de named placeholders. Daarnaast zijn er nog de vraagtekens welke gevolgd kunnen worden door een nummer. (Deze informatie heb ik hier vandaan). Uit het artikel begrijp ik tevens dat de character encoding ook voor problemen kan zorgen. utf-8 was voor mij de afgelopen jaren de encoding om te gebruiken maar nu las ik in een ander artikel dat je voor mysql databases beter utf8mb4 kunt gebruiken. Ik ben dus benieuwd wie er liever named placehoders gebruikt en waarom en ook wie er met utf8mb4 werkt en wat de ervaringen zijn.
Gewijzigd op 30/09/2019 18:20:53 door Frank Nietbelangrijk
Zelf zit ik ook in het "altijd alles escapen" kamp. Gewoon omdat je dan niet na hoeft te denken. Zelf werk ik met een soort DB wrapper die dit allemaal redelijk makkelijk voor me maakt.
Daar werk ik dan met named placeholders omdat ik er dan gewoon een array met variabelen achteraan kan gooien (zonder dat ze allemaal strict noodzakelijk zijn, of in de goede volgorde in de array zitten). Stel dat ik een array $user heb, met alle data voor de ingelogde gebruiker. Als ik dan een query wil draaien waarbij ik "een" waarde (of meer) uit deze array nodig heb geef ik 'm gewoon integraal mee bij de argumenten.
Andersom wordt een query vaak uit losse stukken samengesteld (afhankelijk van gemaakt keuzes). Placeholders komen dus niet altijd in de uiteindelijke query terecht. Met named placeholders hoef je je hier niet zo druk om te maken (gewoon meegeven, en de query wrapper zoekt wel uit of ie echt nodig is/was).
Merk op: met standaard PDO kom je hier niet zo makkelijk mee weg, omdat je geen argumenten mag binden die je vervolgens niet in de query gebruikt.
Rob Doemaarwat op 30/09/2019 19:49:41:
utf8mb4 is aan PHP zijde volgens mij gewoon utf8, alleen kan ie nu echt alle karakters uit de Unicode set opslaan (waar dat voorheen beperkt was tot de karakters met max 3 bytes).
Bedankt voor je reactie Rob.
Gebruik jij de utf8mb4 set in mysql? Het nadeel is volgens mij dat utf8mb4 dan maar 180 karakters kan plaatsen in een VARCHAR. En moet je dan met mysqli_set_charset() utf8 opgeven of utf8mb4? Hoe belangrijk is het om utf8mb4 te gebruiken voor laten we zeggen Nederlands of Engels?
Rob Doemaarwat op 30/09/2019 19:49:41:
Zelf zit ik ook in het "altijd alles escapen" kamp. Gewoon omdat je dan niet na hoeft te denken. Zelf werk ik met een soort DB wrapper die dit allemaal redelijk makkelijk voor me maakt.
Oke alles escapen om zeker te weten dat je je niet ergens per ongeluk vergist. Verder geen andere reden?
(Ik zeg niet dat het fout is maar ik wil dit draadje graag gebruiken om deze zaken zo veel mogelijk uit te diepen).
Rob Doemaarwat op 30/09/2019 19:49:41:
Daar werk ik dan met named placeholders omdat ik er dan gewoon een array met variabelen achteraan kan gooien (zonder dat ze allemaal strict noodzakelijk zijn, of in de goede volgorde in de array zitten). Stel dat ik een array $user heb, met alle data voor de ingelogde gebruiker. Als ik dan een query wil draaien waarbij ik "een" waarde (of meer) uit deze array nodig heb geef ik 'm gewoon integraal mee bij de argumenten.
Dat vind ik interessant. je geeft gewoon een associatieve array mee en dan vergelijkt de wrapper de array keys met de named placeholders? Jouw wrapper gebruikt uiteindelijk PDO?
Rob Doemaarwat op 30/09/2019 19:49:41:
Andersom wordt een query vaak uit losse stukken samengesteld (afhankelijk van gemaakt keuzes). Placeholders komen dus niet altijd in de uiteindelijke query terecht. Met named placeholders hoef je je hier niet zo druk om te maken (gewoon meegeven, en de query wrapper zoekt wel uit of ie echt nodig is/was).
Ik herken dat denk ik van filters op een listview. De basis SELECT query ga je dan uitbreiden met een WHERE claus met geen of één of meerdere ANDs afhankelijk van hoeveel filters er gebruikt worden. Dat deed ik dus in twee stappen. Als eerste bouwde ik de query string (met named placeholders) op en een array met de placeholders en de variabelen die dan met setParameters() doorgegeven kon worden. Maar nog steeds is de string die ik uiteindelijk door geef aan de prepare() functie één geheel.
* alles escapen: Ja, puur gemak. Ook bij teksten die je volledig zelf in de hand hebt, en waar nooooit een quootje in zal staan zul je zien dat er op den duur een quootje in komt ... (de "Kia Cee'd" was in mijn branche bijvoorbeeld een "leuke" eye opener).
* wrapper: Die gebruikt inderdaad PDO. Meestal heb je te maken met MySQL, maar soms komt er een ander "merk" voorbij (MS-SQL, Oracle), dus ik wil me niet vastpinnen op de mysqli extensie (en PDO en MySQL gaat prima).
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
//single row = assoc.array retourneren
$user = $db->single('select * from user where id = :id',['id' => 5]);
/* array(
'id' => 5,
'name' => 'Rob',
'profile_id' => 21,
...
) */
//array met key = right.id en value = right.code
$rights = $db->record('
select r.id,r.code
from `profile_right` pr
join `right` r on r.id = pr.right_id
where pr.profile_id = :profile_id',
$user //argumenten; hier zit dus veel meer in, maar in ieder geval 'profile_id'
);
?>
//single row = assoc.array retourneren
$user = $db->single('select * from user where id = :id',['id' => 5]);
/* array(
'id' => 5,
'name' => 'Rob',
'profile_id' => 21,
...
) */
//array met key = right.id en value = right.code
$rights = $db->record('
select r.id,r.code
from `profile_right` pr
join `right` r on r.id = pr.right_id
where pr.profile_id = :profile_id',
$user //argumenten; hier zit dus veel meer in, maar in ieder geval 'profile_id'
);
?>
* samenstellen: Inderdaad, uiteindelijk houd je 1 stuk SQL over, maar omdat de inhoud en volgorde van de argumenten niet van belang is hoef je er niet zo panisch mee te doen.
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$sql = 'select * from items i';
$where = $args = [];
if($args['foo'] = $_GET['foo'] ?? null)
$where[] = 'i.foo = :foo';
if($args['bar'] = $_GET['bar'] ?? null)
$sql .= "\n join `other` o on o.id = i.other_id and o.bar = :bar";
if($args['enz'] = $_GET['enz'] ?? null)
$where[] = 'i.enz = :enz';
if($where)
$sql .= "\nwhere " . implode("\n and ",$where);
$items = $db->all($sql,$args);
?>
$sql = 'select * from items i';
$where = $args = [];
if($args['foo'] = $_GET['foo'] ?? null)
$where[] = 'i.foo = :foo';
if($args['bar'] = $_GET['bar'] ?? null)
$sql .= "\n join `other` o on o.id = i.other_id and o.bar = :bar";
if($args['enz'] = $_GET['enz'] ?? null)
$where[] = 'i.enz = :enz';
if($where)
$sql .= "\nwhere " . implode("\n and ",$where);
$items = $db->all($sql,$args);
?>
Quote:
utf8mb4 is aan PHP zijde volgens mij gewoon utf8
Waarschijnlijk bedoel je:
Quote:
utf8mb4 in MySQL is aan de PHP zijde "equivalent" aan UTF-8
UTF-8 karakters bestaan maximaal uit 4 octets en utf8mb4 biedt hier ruimte aan:
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
> show character set where Charset like 'utf%';
+---------+------------------+--------------------+--------+
| Charset | Description | Default collation | Maxlen |
+---------+------------------+--------------------+--------+
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| utf16 | UTF-16 Unicode | utf16_general_ci | 4 |
| utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 |
| utf32 | UTF-32 Unicode | utf32_general_ci | 4 |
+---------+------------------+--------------------+--------+
+---------+------------------+--------------------+--------+
| Charset | Description | Default collation | Maxlen |
+---------+------------------+--------------------+--------+
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| utf16 | UTF-16 Unicode | utf16_general_ci | 4 |
| utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 |
| utf32 | UTF-32 Unicode | utf32_general_ci | 4 |
+---------+------------------+--------------------+--------+
(maar let dus op het verschil tussen UTF-8 en utf8, deze zijn niet equivalent, het tweede artikel waar je naar linkt gebruikt deze twee termen nogal klakkeloos door elkaar...)
Dan bestaat er nog het idee dat VARCHAR nog rekent met bytes, maar vanaf MySQL versie 5 worden karakters gebruikt. Let wel op dat de totale mogelijke geheugenruimte voor VARCHAR vastligt (65.535 bytes). Dat bepaalt op zijn beurt hoeveel karakters deze (worst case) kan bevatten (ca. 16382).
Vervolgens heerst er nog steeds een misverstand over set_charset(). Je moet het als volgt zien. set_charset() is in wezen een contract tussen jouw PHP-applicatie en de database, die uit twee delen bestaat:
- enerzijds dien jij er zorg voor te dragen dat alle data die je vanuit je applicatie de database in schiet van deze voorgeschreven character encoding is, en
- anderzijds doet MySQL haar best om de data in de database in de voorgeschreven character encoding (terug) te serveren
Maar dit is niet noodzakelijkerwijs de character encoding van de gebruikte database-tabellen of -kolommen! Natuurlijk is het handiger dat alles qua character encoding in de pas loopt (en daar zou je dus ook naar moeten streven) maar dit is geen noodzakelijke voorwaarde. MySQL voert, zo goed en zo kwaad als dat kan uiteraard, zelf vertalingen uit tussen character encodingen als die ziet dat er een discrepantie bestaat tussen de definities en de waarde in set_charset(). En ja, als dat niet past kan dat resulteren in vraagtekens - karakters die niet ondersteund worden in de gewenste character encoding. Maar je zou dus prima een UTF-8 applicatie kunnen hebben die middels set_charset('utf8') communiceert met een sec latin1 database. latin1 is in zekere zin een "subset" van utf8 dus dit levert bij het uitlezen waarschijnlijk geen problemen op. Maar als je dus utf8 (of dus zelfs UTF-8) dingen probeert weg te schrijven in een latin1 database, dat is natuurlijk
Quote:
Toch ben ik wel een beetje nieuwsgierig waarom Thomas schrijft dat je gewoon alle DATA in je query moet escapen. Naar deze onderbouwing ben ik wel nieuwsgierig.
Heel simpel. Je wilt niet dat DATA als SQL geïnterpreteerd kan worden. Dat is namelijk de definitie van SQL-injectie. Alles wat je niet kunt escapen zou je via whitelists moeten laten verlopen (lijst van toegestane waarden). En dat valt eigenlijk gewoon onder validatie, die altijd, waar dat relevant is, plaats zou moeten vinden nog voordat je een query uitvoert. Als de validatie mislukt hoef je niet eens te proberen om een query te draaien en dat zou je dan dus ook nooit moeten doen.
Het eerste artikel waar je naar linkte gaat van simpel naar geavanceerd en legt uit wat de voordelen van prepared statements zijn. Maar hier kleven weer andere "nadelen" aan. De prepared statements variant van mysqli is wat mij betreft veel te "clunky".
Dan de PDO versie (waarbij de bovenstaande afbeelding weer min of meer van toepassing is). Dat lijkt allemaal simpel, een handjevol classes etc, maar de echte leercurve zit in de PDO_MYSQL driver, waar legio instellingen in zitten waar je vertrouwd mee dient te zijn. Daarnaast simuleert PDO standaard de "native" prepared statements voorziening die MySQL zelf heeft, dus dat zijn in wezen geen "echte" prepared statements. Wat op zich niet erg is, want de meeste queries herhaal je toch niet waarbij je 1x een template naar de database stuurt en vervolgens meerdere queries uitvoert met gebruikmaking van dat template.
De "gripes" die ik heb met PDO zijn als volgt:
- PDO is niet specifiek geschreven voor MySQLi, en als zodanig ook niet (out-of-the-box) geoptimaliseerd voor MySQL
- als je toch alleen maar van MySQL gebruik maakt in je applicatie, waarom zou je dan geen gebruik maken van een extensie die specifiek geschreven is voor MySQL (mysqli)
- debugging van queries; geen idee hoe dat gaat in mysqli+prepared statements / PDO? moet je dan je query log aanzetten en dan in je logs duiken om een (concrete) query op te snorren? f*ck that :)
Iedereen moet zelf maar weten wat ie gebruikt (choose your poison), maar met een eenvoudige wrapper om mysqli die werk uit handen neemt kom je echt al een heel eind. Wat je ook gebruikt, het is natuurlijk wel zaak dat je heel goed vertrouwd bent met de spelregels van de constructie die je gebruikt. En je kunt dan in principe altijd nog besluiten om deze wrapper te implementeren via PDO, of je gaat nog een stap verder en je gaat met Database Abstraction Layers aan de slag. Je kunt dan in principe steeds meer dingen doen op een abstract niveau, maar deze abstractie heeft ook een prijs (en mogelijk een snel afnemende meerwaarde). Voor een heleboel applicaties is die abstractie gewoon niet interessant (genoeg).
Gewijzigd op 01/10/2019 00:42:35 door Thomas van den Heuvel
Mijn tip: gebruik gewoon een reeds bestaande ORM, bij voorkeur Doctrine (of Eloquent). Zitten een hoop zaken in die zeker interessant zijn voor je webapplicatie.
Thomas bedankt voor je uitgebreide reactie.
Thomas van den Heuvel op 01/10/2019 00:31:08:
(maar let dus op het verschil tussen UTF-8 en utf8, deze zijn niet equivalent, het tweede artikel waar je naar linkt gebruikt deze twee termen nogal klakkeloos door elkaar...)
Deze begrijp ik even niet. UTF8 utf8 UTF-8 en utf-8... Waar zit het verschil in behalve dan in de hoofdletters en al dan geen minteken?
Thomas van den Heuvel op 01/10/2019 00:31:08:
Dan bestaat er nog het idee dat VARCHAR nog rekent met bytes, maar vanaf MySQL versie 5 worden karakters gebruikt. Let wel op dat de totale mogelijke geheugenruimte voor VARCHAR vastligt (65.535 bytes). Dat bepaalt op zijn beurt hoeveel karakters deze (worst case) kan bevatten (ca. 16382).
Hier lees ik weer iets nieuws waarvoor dank. Echter begrijp ik van stackoverflow dat de 65.535 bytes de maximale lengte is van alle kolommen bij elkaar. Een soort maximale regellengte dus.
Over de set_charset() functie binnen PHP zou je dus kunnen zeggen dat als je database is ingesteld utf8 of utf8mb4 je utf8 meegeeft als parameter:
Dat de mysqli prepared statements een beetje klungelig in elkaar steken dat is nu wel duidelijk.
Thomas van den Heuvel op 01/10/2019 00:31:08:
- debugging van queries; geen idee hoe dat gaat in mysqli+prepared statements / PDO? moet je dan je query log aanzetten en dan in je logs duiken om een (concrete) query op te snorren? f*ck that :)
In PDO kun je de exceptions natuurlijk zien als je ze niet opvangt. Bij mysqli blijf je telkens met onhandige if statements klooien zover ik het kan beoordelen. Of doel je hier op iets anders?
Jij gebruikt dus een eigen mysqli wrapper?
Jelle,
Fijn om te lezen dat er nog een Doctrine fan aanwezig is :-) Eloquent ken ik van naam maar ik heb er nog nooit naar gekeken. Doctrine daartegen ken ik ondertussen zeer goed. Dit komt denk ik vooral omdat ik graag met Symfony werk. Wel herken ik iets in het laatste stukje van Thomas zijn opmerking als we het over Doctrine hebben: Het feit dat je een prijs betaald voor een abstraction layer. Maar met alle voordelen daar tegenover vind ik Doctrine nog steeds erg fijn om mee te werken. Vooral omdat de data in een Entity (een class) wordt aangeleverd. En met doctrine ORM werk je erg makkelijk met prepared statements.
Quote:
UTF8 utf8 UTF-8 en utf-8... Waar zit het verschil in behalve dan in de hoofdletters en al dan geen minteken?
UTF-8 is een ISO-standaard, utf8 is een interpretatie (subset) hiervan specifiek voor MySQL. Ging niet zozeer over case maar meer over het minteken.
Neemt niet weg dat je de case ook overal consequent zou moeten gebruiken, zoals:
In een HTML-document maakt het niet zoveel uit, maar in andere gevallen (XML?) kan het mogelijk weer wel uitmaken. Als je hier eens op Googled zijn de resultaten min of meer "maakt niet uit / zou niet uit moeten maken, maar UTF-8 verdient de voorkeur boven utf-8". Als je verwarring kunt voorkomen door gewoon gebruik te maken van UTF-8 lijkt mij dat gratis winst.
Quote:
Een soort maximale regellengte dus.
Precies, en hoeveel karakters je hier (theoretisch) in kunt proppen hangt van het maximaal aantal bytes af waar een karakter uit opgebouwd is. Als je niet tegen deze restricties aan wilt of dreigt te hikken kun je beter een ander kolomtype gebruiken die deze harde bovengrens niet heeft (of iig veel ruimer is).
Quote:
In PDO kun je de exceptions natuurlijk zien als je ze niet opvangt. Bij mysqli blijf je telkens met onhandige if statements klooien zover ik het kan beoordelen. Of doel je hier op iets anders?
Ik bedoel meer: je kunt met prepared statements niet (of iig verre van makkelijk) direct zien welke query uiteindelijk wordt uitgevoerd. Misschien is dat inmiddels veranderd, maar prepared statements in mysqli ga ik niet gebruiken en in PDO moet (zou) je eigenlijk een heleboel (moeten) inregelen om dit intuïtief in MySQL te laten werken want zoals gezegd is PDO hier niet specifiek voor geschreven.
Quote:
Jij gebruikt dus een eigen mysqli wrapper?
Ja, en als ik de keuze heb zou ik deze ook gewoon altijd gebruiken. Omdat deze gewoon het meeste rechttoe rechtaan is.
Ik zie vaak wat nuttige code van je in het forum komen die naar mijn idee prima in de scripts-database past.
staat in principe al een tijdje op PHPhulp. Heb deze ondertussen wel wat uitgebouwd en uitgebreid met het tracken van (specifieke) queries en een wat andere aanpak qua transacties maar deze is in grote lijnen hetzelfde. Kan de laatste versie desgewenst hier plaatsen als men interesse heeft.
Deze Dus als je daar het script wilt plaatsen, graag :-)
Ik heb het idee dat er dan teveel "magic" tussen zit. In plaats van een direct SQL statement (wel zo leesbaar, makkelijk te testen) ga je via allerlei omwegen (query builder, annotation, enz) een query in mekaar zitten draaien (waar je uiteindelijk altijd via "raw" nog de puntjes op de i moet zetten), en dan hoop je maar dat ie precies doet wat je bedoelt (en als ie het niet doet zoek je jezelf een ongeluk).
Performance voor een enkel record is natuurlijk geen enkel probleem (en geeft natuurlijk ook wel wat programmeer gemak - als je het eenmaal allemaal ingeregeld/geconfigureerd hebt), maar ga je op deze manier "vele records" inlezen/bijwerken, dan gaat een ORM uiteindelijk over z'n nek (out of memory + traag). Dezelfde actie met gewoon een update statement in een transaction schaalt vele malen verder, en is veel sneller klaar (qua processortijd; qua programmeertijd is afhankelijk van de programmeur ;-) .
Nadeel is dat die performance in eerste instantie niet zo'n probleem is (kleine/lege database), maar als het project eenmaal begint te lopen (meer data) loop je op een gegeven moment tegen de limieten aan - en dan moet je de boel alsnog omschrijven naar "echte queries" of enorm gaan lopen "tweaken" in je ORM omgeving. De initiële winst van een paar regels code minder verdampt dan al weer snel.
ORM is dus slecht schaalbaar?
Natuurlijk heeft het als voordeel dat je getter/setter code ook uitgevoerd wordt, en dus alle randvoorwaarden gecontroleerd/bijgewerkt, maar voor grote operaties/aanpassingen is het niet altijd handig. Een trigger in een database is dan veel efficiënter (maar die heeft weer als nadeel dat je code over meerdere lagen verdeel wordt - zo is het altijd wat :-) ).
Het werkt met een reeks CLI commando's:
1) bin/console make:entity Maak een entity. Geef op welke variabelen er in de entity mogen komen en van welk type deze variabelen zijn. En dat is inclusief relaties met andere tabellen. en je geeft ook per variabele de lengte op en of hij ook leeg mag zijn.
2) bin/console make:migration Dit maakt een migration bestand met daarin de query om de Product tabel aan te maken of te wijzigen.
3) bin/console doctrine:migrations:migrate Execute the query. De tabel is nu aangemaakt in de database.
4) bin/console make:crud Er wordt een controller, een formulier en vier templates aangemaakt. De vier zorgen ieder voor een deel van de CRUD. Het formulier wordt gebruikt door de CREATE en UPDATE pagina. De controller kennen we allemaal wel van het CMV model.
Andere veel gebruikte commando's zijn ook
1) bin/console make:controller Deze maakt een controller class aan
2) bin/console make:form Deze maakt een formulier aan
De commando's creëren automatisch een boel code en je gaat dan eigenlijk aan een flinke verbouwing beginnen om het naar je eigen hand te zetten. Het formulier bevat meestal te veel velden en soms ook te weinig. De pagina's zijn nog zonder CSS dus daar moet je ook wat mee doen. En ga zo maar door. Maar het scheelt wel tijd.
Erg fijn is dat de database voor je wordt ingericht zonder dat je daar verder iets voor nodig hebt.
Dit is allemaal prima voor een CRUD applicatie en dus vaak prima voor de voorkant van een website.
Wil je zoals Rob aangeeft grote hoeveelheden DATA uitlezen, kopiëren of aanpassen dan is dit veel minder geschikt. Ook kom je met Doctrine soms in de problemen (althans met het ORM gedeelte van Doctrine) als je een heel erg fancy query wilt schrijven. In deze gevallen kun je terugvallen op DBAL wat weer een gewone wrapper is en dus een stuk flexibeler is. Het leuke is dan dat Doctrine wat tools heeft om de data die je ophaalt uit de database alsnog in je entity te zetten.
Kortom is er veel mogelijk met Doctrine maar voor een aantal gevallen is het niet geschikt.
Ook hier is het (zoals Thomas dat mooi kan zeggen) een kwestie van het juiste gereedschap kiezen voor de klus die geklaard moet worden.
Gewijzigd op 02/10/2019 14:50:19 door Frank Nietbelangrijk
Maak gewoon een basis class waar je tabelnaam + velden in kunt stellen (in een of andere definitie vorm*, zodat je weet dat het een integer, boolean, datum, enz is). En vervolgens maak je je aanpassingen in een afgeleide (veldje d'r bij, veldje d'r af, dwarsverbanden, getters, setters, enz - kan allemaal).
Voordeel is dat als je een keer wat in de basis wilt veranderen je niet al je reeds gegenereerde classes af moet om die aanpassing ook daar door te voeren. Maar gewoon OO: basis class veranderen = alle afgeleiden profiteren in 1 klap mee.
* maar het liefst wel gewoon in PHP, en niet in een of andere tekst formaat (docblock annotation, XML, YAML) wat daarna weer geparsed + gecached; en dus gerefreshed na aanpassen) moet worden, en waar je stiekem ook in kunt "programmeren", maar dan zonder hulp van de syntax check / code completion / enz van je editor (ja ik weet het, die zijn er wel, maar waarom allemaal met van die omwegen terwijl het allemaal zo simpel kan!?)
</rant>
Rob Doemaarwat op 02/10/2019 16:24:30:
O ja, dat was het: code genereren, ook al zo'n reden waarom ik niet zo'n fan ben van dit soort dingen.
Maak gewoon een basis class waar je tabelnaam + velden in kunt stellen (in een of andere definitie vorm*, zodat je weet dat het een integer, boolean, datum, enz is). En vervolgens maak je je aanpassingen in een afgeleide (veldje d'r bij, veldje d'r af, dwarsverbanden, getters, setters, enz - kan allemaal).
Voordeel is dat als je een keer wat in de basis wilt veranderen je niet al je reeds gegenereerde classes af moet om die aanpassing ook daar door te voeren. Maar gewoon OO: basis class veranderen = alle afgeleiden profiteren in 1 klap mee.
* maar het liefst wel gewoon in PHP, en niet in een of andere tekst formaat (docblock annotation, XML, YAML) wat daarna weer geparsed + gecached; en dus gerefreshed na aanpassen) moet worden, en waar je stiekem ook in kunt "programmeren", maar dan zonder hulp van de syntax check / code completion / enz van je editor (ja ik weet het, die zijn er wel, maar waarom allemaal met van die omwegen terwijl het allemaal zo simpel kan!?)
</rant>
Maak gewoon een basis class waar je tabelnaam + velden in kunt stellen (in een of andere definitie vorm*, zodat je weet dat het een integer, boolean, datum, enz is). En vervolgens maak je je aanpassingen in een afgeleide (veldje d'r bij, veldje d'r af, dwarsverbanden, getters, setters, enz - kan allemaal).
Voordeel is dat als je een keer wat in de basis wilt veranderen je niet al je reeds gegenereerde classes af moet om die aanpassing ook daar door te voeren. Maar gewoon OO: basis class veranderen = alle afgeleiden profiteren in 1 klap mee.
* maar het liefst wel gewoon in PHP, en niet in een of andere tekst formaat (docblock annotation, XML, YAML) wat daarna weer geparsed + gecached; en dus gerefreshed na aanpassen) moet worden, en waar je stiekem ook in kunt "programmeren", maar dan zonder hulp van de syntax check / code completion / enz van je editor (ja ik weet het, die zijn er wel, maar waarom allemaal met van die omwegen terwijl het allemaal zo simpel kan!?)
</rant>
Ja, voor een simpele website is PDO meer dan voldoende en een ORM waarschijnlijk wel een overkill. Maar dan nog wegen de voordelen van een Doctrine ORM niet op tegen je eigen database wrapper schrijven. Je kan met Doctrine ook nog perfect plain SQL executen als je dat per sé wil.
Snap niet waarom je het warm water opnieuw zou willen uitvinden als er dergelijke ORM's al het werk doen voor je.
Ik snap dat het een mooie drop-in is om snel op weg te komen. Zeker bij nieuwe projecten, waarbij je het datamodel helemaal naar je hand kunt zetten (en niet aan een reeds bestaande database vast zit, met alle - met de kennis van nu - onlogische keuzes die daarbij gemaakt zijn). Ik maak ook graag gebruik van componenten die anderen volledig voor me uitgewerkt hebben, en waar ik dus met een eenvoudige Composer require een enorme klap code "cadeau" krijg***. Maar specifiek in dit geval (ORM) heb ik altijd het idee dat ik aan een spons sta te draaien, en dan maar hoop dat het andere uiteinde op de juiste manier meedraait. Ik zit graag wat directer aan de knoppen, heb er ook geen probleem mee om SQL queries te schrijven (en optimaliseren!), en dan werkt een minimale tussenlaag toch net even lekkerder.
<opa vertelt>
*) Ik heb nog in assembler geprogrammeerd waarbij je elke instructie afwoog om te kijken welke combinatie de minste cycles koste.
Je had bijvoorbeeld de "mul" instructie om een vermedigvuldiging te doen. Maar dat koste iets van 42 klok cycles op een 486 CPU. Voor grafische schermen moest je vaak met 320 vermenigvuldigen (de breedte van het scherm) om een x,y coördinaat naar een plek in het geheugen om te rekenen. daar was een "trucje" voor:
Code (php)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
mov ax,x //ax is een 16 bit register = plekje in de CPU
mov bx,y //bx dito; y is max 200, dus de bovenste 8 bit zijn altijd leeg
add ah,bl //bl is de onderste 8 bit van bx = gewoon y
//door deze nu bij de bovenste 8 bit (ah) van de x waarde op te tellen
//hebben we nu y * 256 bij x opgeteld
shl bx,6 //de y waarde 6 posities naar links shiften = met 64 vermedigvuldigen
add ax,bx //en dat ook bij x optellen
//effectief heb je nu x + 256 * y + 64 * y = x + 320 * y
mov bx,y //bx dito; y is max 200, dus de bovenste 8 bit zijn altijd leeg
add ah,bl //bl is de onderste 8 bit van bx = gewoon y
//door deze nu bij de bovenste 8 bit (ah) van de x waarde op te tellen
//hebben we nu y * 256 bij x opgeteld
shl bx,6 //de y waarde 6 posities naar links shiften = met 64 vermedigvuldigen
add ax,bx //en dat ook bij x optellen
//effectief heb je nu x + 256 * y + 64 * y = x + 320 * y
Al die shift en add instructies gaan allemaal binnen een klok cycle, dus dit is vele malen efficiënter dan die "mul".
**) Het ergste vond ik dit altijd bij Visual Basic: in eerste instantie klikte je een lekker eind weg, componentjes op hun plek slepen, snel ontwikkelen, super - klant tevreden, alles toppie. Vervolgens moest er nog "een klein dingetje anders", maar dat kon dan niet in die VB component (want: geen broncode), en dan moest je weer helemaal van voor af aan beginnen om het "zelf" te doen.
</opa vertelt>
***) Maar de autoloader van Composer is (was) dan ook weer zo'n draak. D'r werden veel te veel files al op voorhand ingeladen (de autoload_files.php). Het is maar een paar ms, maar wel een paar ms voor elke call. Dus zelf maar wat voor gemaakt. Inmiddels is het wel verbeterd geloof ik, maar dan blijf ik toch maar op m'n eigen ding hangen. Niet omdat ik graag het "warm water opnieuw uitvind", maar omdat ik "warmer water" wil(de) hebben.
Ik denk dat je programmeren daar een beetje mee kunt vergelijken. Ik zie mezelf in ieder geval niet een eigen database wrapper (en wat the heck nog meer allemaal) bijvijlen en schaven tot ik er die F1
Ben zelf ooit begonnen met een eigen framework. Ik heb er één website mee gemaakt daarna liep ik tegen Symfony op. Versie 4 heeft me helemaal blij gemaakt. Wat ik ook een hele fijne gedachte vind is dat de structuur van mappen en classen min of meer vast ligt. Als ik morgen niet meer wakker wordt kan een ander het van me overnemen. Wat ik ben tegen gekomen aan spaghetti html+code van anderen is werkelijk verschrikkelijk.
Je kunt de broncode meteen wegkiepen en mag je klant vertellen dat je opnieuw gaat beginnen. En dat is wat mij betreft een groot probleem in PHP land: de standaardisatie. Hoe grappig dat velen hun eigen wieltjes blijven uitvinden...