Tutorials

Oplossing voor bekende fouten in PHP

Oplossingen voor de 'meest' gemaakte fouten in php. Deze tutorial is nog niet 100% af zoals te zien is, het moet allemaal nog netjes afgemaakt worden (betere titels?) en een wat betere opmaak, voor zover ik daar invloed op heb met ubb.

Pagina 1

HELP! Mijn POST en GET forms werken niet (meer)

Hoogstwaarschijnlijk ben je onlangs gemigreerd van een oude PHP versie naar een nieuwe, of je bent begonnen aan een PHP tutorial die verouderd is.

In de manual van PHP vind je onder het stukje Predefined variables (http://www.php.net/manual/en/language.variables.predefined.php)e.e.a. over de zogenaamde "superglobals", die in versie 4.1.2 hun intrede deden. Daarnaast werd in versie 4.2.0 de configuration directive 'register_globals' standaard op Off gezet. Dit had als gevolg dat variabelen die van buitenaf komen niet meer automagisch globaal aangemaakt werden.


<?
echo 'Welkom ' . $user . ' op mijn coole pagina!';
?>


Dit stukje php werkte voorheen altijd gewoon zonder problemen, als je het aanriep met index.php?user=drm. Tegenwoordig zijn de variabelen terug te vinden in hun respectievelijke superglobals.

PHP:

	
<?
echo 'Welkom ' . $_GET [ 'user' ] . ' op mijn coole pagina!';
?>

Zie verder de manual voor het gebruik van deze superglobals.

Zie ook dit (http://www.zend.com/zend/art/art-sweat4.php) artikel van Jason E. Sweat voor hulp bij het programmeren met register_globals op Off.
Pagina 2

... is not a valid MySQL resource index

Hoogstwaarschijnlijk heb je een fout staan in je MySQL query. Gebruik de functie mysql_error() om erachter te komen wat de fout precies is.

Een goede gewoonte is het om bij mysql queries altijd even te kijken of er een fout is opgetreden, en zo ja, wat de fout en de query dan was:

	
<?
$query = "
   SELECT
      woei
   FROM
      table
   WHERE
      woei_id={$_REQUEST [ 'id']}
";
mysql_query ( $query );
if ( mysql_errno () > 0 ) {
   trigger_error ( "MySQL Error!!<br><br>" . mysql_error () . "<br>in query:<br>$query" );
}
?>


Zie voor meer informatie over het effectief gebruik van trigger_error() dit (http://www.php.net/ref.errorfunc) stukje.

Pagina 3

Mijn sessie-variabelen worden niet opgeslagen



Gebruik session_register () en $_SESSION constructies niet door elkaar.

Session_register()

<?
session_start ();
session_register ( 'blaat' );

if ( !session_registered ( 'blaat' ) ) {
   $blaat = "Blaat";
} else {
   echo 'Blaat was reeds gedefinieerd: ' . $blaat;
}
?>


$_SESSION

<?
session_start ();

if ( !isset ( $_SESSION [ 'blaat' ] ) ) {
   $_SESSION [ 'blaat' ] = 'Blaat';
} else {
   echo 'Blaat was reeds gedefinieerd: ' . $_SESSION [ 'blaat' ];
}
?>

Als je beide constructies door elkaar gebruikt, werkt het niet. As simple as that. :)

Sterker nog: sinds register_globals default uit staat, is het gebruik van session_register deprecated:


Caution

If you want your script to work regardless of register_globals, you need to instead use the $_SESSION array as $_SESSION entries are automatically registered. If your script uses session_register(), it will not work in environments where the PHP directive register_globals is disabled.
Pagina 4

Mijn file-upload form werkt niet.

1. Controleer of je form-tag juist gedefinieerd hebt:

-method moet "post" zijn
-enctype moet "multipart/form-data" zijn
-Als je de enctype niet goed zet, komt de waarde van het <input type="file"> terug in $_POST, anders kun je hem terugvinden in $_FILES.

2. Controleer de php-configuratie

-Controleer de directives file_uploads, upload_max_filesize, upload_tmp_dir en post_max_size op hun waarden.
Pagina 5

Ik heb een multiple select of meerdere checkboxes,

Je kunt door achter de naam van je input-veld [] te zetten aan PHP vertellen dat je graag wilt dat alle variabelen die met die naam binnen komen als array behandeld moeten worden:
Voorbeeld

<form method="get">

<input type="checkbox" id="my_check_1" name="my_check[]" value="first_check" />
<label for="my_check_1">First check</label><br />
<input type="checkbox" id="my_check_2" name="my_check[]" value="second_check" />
<label for="my_check_2">Second check</label><br />
<input type="checkbox" id="my_check_3" name="my_check[]" value="third_check" />
<label for="my_check_3">Third check</label><br />

<select name="my_select[]" multiple="multiple">

<option value="first_option">First option</option>
<option value="second_option">Second option</option>
<option value="third_option">Third option</option>
</select>
<input type="submit" />
</form>

Het gevolg van een dergelijk formulier is dat in dit geval de $_GET array binnen PHP op zijn beurt weer arrays bevat waarin aangegeven staat welke opties 'aangevinkt' zijn:
Uitvoer


Array
(
    [my_check] => Array
        (
            [0] => first_check
            [1] => second_check
        )
    [my_select] => Array
        (
            [0] => second_option
            [1] => third_option
        )
)


Eveneens kun je de indices van de array op zichzelf ook namen/nummering geven. Als je dat doet kun je eenvoudig de 'value' van checkboxes achterwege laten:


<input type="checkbox" id="my_check_1" name="my_check[first]" />
   <label for="my_check_1">First check</label><br />
<input type="checkbox" id="my_check_2" name="my_check[second]" />
   <label for="my_check_2">Second check</label><br />
<input type="checkbox" id="my_check_3" name="my_check[third]" />
   <label for="my_check_3">Third check</label><br />

Uitvoer:


<?php
print_r ( $_GET )
?>

Array
(
    [my_check] => Array
        (
            [first] => on
            [second] => on
        )
)



Met een beetje creatief gebruik van in_array () en/of foreach constructies is het op die manier heel eenvoudig te achterhalen welke opties aangevinkt zijn.
Pagina 6

Escape problemen met PHP i.c.m. databases

Controleer altijd of get_magic_quotes_gpc() en get_magic_quotes_runtime() aan staan. Als 1 van deze twee aan staat, worden variabelen binnen PHP die enkele (') of dubbele (") quotes of backslashes (\) bevatten automagisch escaped. Dat wil zeggen dat een string die in PHP binnen komt via GET, POST of een cookie (gpc) die een ', " of \ bevat automagisch een \ voor die character krijgt. Bekijk het volgende voorbeeld:


<form method="get">

   <input
      name="test"
      type="text"
      value="Test: ', " and \"
   />
   <input type="submit" />
</form>
<pre>
<?
if ( get_magic_quotes_gpc () ) {
   echo 'Magic quoting with get, post or cookie is enabled', "\n";
}

print_r ( $_GET );
?>


Uitvoer met get_magic_quotes_gpc aan, na submit:


Magic quoting with get, post or cookie is enabled
Array
(
    [test] => Test: \', \" and \\
)


Het is het overwegen waard om zelf de controle te houden op het escapen van variabelen, om te voorkomen dat je bijvoorbeeld in <input ...> velden een ongewenste backslash krijgt.

Mijn voorkeur heeft het dan ook om in de code te checken of er al ge-escaped is, en zo ja: die escape characters weer te verwijderen:

Uiteraard is het ook een optie om je configuratie helemaal naar je hand te zetten, maar je kunt een reden om dat niet globaal te doen en de magic quotes directives kun je niet altijd per script aan of uit zetten, behalve magic_quotes_runtime (zie set_magic_quotes_runtime (). Daarnaast is het het overwegen waard om je scripts zo portable mogelijk te maken (onafhankelijk van configuraties)


<?
set_magic_quotes_runtime ( 0 );

if ( get_magic_quotes_gpc () ) {
   foreach ( $_POST as $key => $value ) {
      $_POST [ $key ] = stripslashes ( $value );
   }
   foreach ( $_GET as $key => $value )
   // etcetera ...
}
?>


Vervolgens kun je met je eigen escape functies de juiste escapes doen:


<?php
define ( 'QUOTE_SINGLE',      39 );
define ( 'QUOTE_DOUBLE',      34 );
define ( 'QUOTE_BACKTICK',    96 );
function mysql_escape ( $string, $quote_char_code = QUOTE_SINGLE ) {
   $quote_char = chr ( $quote_char_code );

   return
      $quote_char .
         str_replace (
            array ( '\\', $quote_char ),
            array ( '\\\\', '\\' . $quote_char ),
            $string
         )
      $quote_char;
}
// ....
$query = "
   INSERT INTO
      table(fieldname)
   VALUES(
      " . mysql_escape_string (
         'Dit " is \' een string \\ met wat \ bullshit \\\' '
      ) . "
   );
";
?>



Overigens is het ook te overwegen de ANSI-SQL wijze van escapen te hanteren, door een enkele quote te escapen met een extra enkele quote:

<?php
function ansi_sql_escape ( $string ) {
   return str_replace ( "'", "''", $string );
}
?>


Om goed te testen of je escaping wel goed werkt, probeer het volgende in te voeren in je database door middel van een formuliertje:

' \' '' \\' \\\' " \" "" \\" \\\" ` \` `` \\` \\\`

Wanneer je bij inserten van deze string geen foutmelding krijgt en je krijgt exact dezelfde string terug bij het uitlezen van die string uit je database, werkt je escaping perfect:


if ( isset ( $_GET [ 'string' ] ) ) {
   $query = '
      INSERT INTO
         table(fieldname)
      VALUES(
         ' . mysql_escape ( $_GET [ 'string' ] ) . '
      );
   ';
   mysql_query ( $query )
      or trigger_error ( "Fout: " . mysql_error () . "<br>query: " . $query );
   $id = mysql_insert_id ();
   $query = '
      SELECT
         fieldname
      FROM
         table
      WHERE
         field_id=' . $id;
   $res = mysql_query ( $query )
      or trigger_error ( "Fout: " . mysql_error () . "<br>query: " . $query );
   $selected_field = mysql_result ( $res, 0, 0 );
   echo 'inserted: ', $_GET [ 'string' ], '<br>';
   echo 'retrieved: ', $selected_field, '<br>';
   echo 'ok?: ', $selected_field == $_GET [ 'string' ], '<br>';
}
?>

<form method="get">
   <input type="text" name="string" />
   <input type="submit" />
</form>

Pagina 7

multiviews

Ik wil index.php?piet=1&sjaak=3 vervangen door index/1/3. Hoe doe ik dat?

Eigenlijk is dit niet echt een PHP vraag, maar een Apache vraag. Maar op de een of andere manier komt het altijd in een PHP context weer terug, vandaar hier maar een stukje uitleg;)
MultiViews

Verreweg de meest eenvoudige manier is om in je Apache configuratie (hetzij in httpd.conf, hetzij in een .htaccess, hetzij nog ergens anders) voor de directory waar je je php'tje hebt staan de MultiViews option aan te zetten:


code:

Options +MultiViews


De Apache 1.3 documentatie zegt er het volgende over:

The effect of MultiViews is as follows: if the server receives a request for /some/dir/foo, if /some/dir has MultiViews enabled, and /some/dir/foo does not exist, then the server reads the directory looking for files named foo.*, and effectively fakes up a type map which names all those files, assigning them the same media types and content-encodings it would have if the client had asked for one of them by name. It then chooses the best match to the client's requirements.

In PHP kun je vervolgens de binnengekomen request afhandelen met de $_SERVER [ 'REQUEST_URI' ] variabele.
Pagina 8

Mod_rewrite

Ik wil index.php?piet=1&sjaak=3 vervangen door index/1/3. Hoe doe ik dat?

Een veel ingewikkelder maar veel flexibeler oplossing is het gebruik maken van mod_rewrite. Deze module geeft je de beschikking over diverse configuratie directives die vertellen hoe een inkomende request "vertaald" (rewritten) moet worden naar een door jou te bepalen URI.

Een eenvoudig voorbeeld is om bijvoorbeeld de url /rewrite-test/index/1/2 te rewriten naar /rewrite-test/index.php?piet=1&sjaak=2


code:

RewriteEngine On
RewriteBase /rewrite-test/
RewriteRule ^index/([0-9]+)/([0-9]+)$ index.php?piet=$1&sjaak=$2


Je kunt dit natuurlijk zo gek maken als je zelf wilt. Een goed opgezette set rewriterules kan ook zorgen voor goede 'default'-afhandeling en shortcuts naar bepaalde pagina's. Zo zou je bijvoorbeeld een rewriterule kunnen maken die doorverwijst naar een search-engine als een url niet bestaat, of /user/piet doorverwijzen naar een profile.php?user=piet, etcetera. Alles bij elkaar genomen is het zeer zeker de moeite waard mod_rewrite te verkiezen boven MultiViews.

Voor meer informatie over het gebruik van mod_rewrite kun je het beste de URL Rewriting Guide en de mod_rewrite reference van Apache doornemen.
Pagina 9

Hoe werkt dat GROUP BY nu eigenlijk?

De GROUP BY clausule is een krachtig onderdeel van SQL, en tegelijk een van de minst begrepen helaas. In dit stukje zal ik proberen helder en duidelijk uit te leggen wat het nu eigenlijk doet, en wat het niet doet. Bij de voorbeelden gaan we even uit van de volgende tabel met data (alle data is integers):


A B C
1 1 1
1 1 2
1 2 3
1 2 4
2 3 5
2 3 6
2 4 7
2 4 8

Wat doet GROUP BY?

Middels een GROUP BY-clausule in je SELECT-query kun je de database vragen om identieke resultaten te groeperen, oftewel 'samen te vatten'. Om het bovenstaande voorbeeld even te pakken met de eenvoudigste GROUP BY query:

sql:
SELECT a FROM Table GROUP BY a

Dit geeft 2 rows, namelijk:
A
1
2

wat alleszins duidelijk en logisch is. Het wordt moeilijker als je meerdere kolommen wil hebben. Stel je de volgende query eens voor:

sql:
SELECT a, b FROM Table GROUP BY a

SQL Server, Oracle en andere fatsoenlijke databases slikken deze query niet. Om SQL2000 vrij te citeren:


Column 'Table.B' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

Waarom zegt ie dat? Kijk eens mee naar de tabel waar we deze query op doen. We vragen hem te groeperen op identieke waarden in kolom A. Echter, als we dat doen, welke waarde van B moeten we dan selecteren bij waarde 1 in A? Daar kan je zowel 1 als 2 bij verwachten in kolom B, en omdat dat niet gespecificeerd is staat de database het terecht niet toe.

MySQL is wat dit betreft dan toch een hele brakke database, die deze laatste constructie wel toestaat. En volgens de handleiding is het 'by design' dat je vervolgens random waardes in kolom B aantreft. Don't do it.
Pagina 10

Bij een javascript-submit of drukken op enter word

Als je in je form een <input type="submit"> een name en een value geeft, worden deze in Internet Explorer niet meegegeven als form-waarde.

Je kunt dan beter 1 van de 2 volgende constructies gebruiken om te controleren of je form gesubmit is:
In het geval van een POST formulier:


<?
if ( strcasecmp ( $_SERVER [ 'REQUEST_METHOD' ], 'post' ) == 0 ) {
   // het post-formulier is verstuurd
} else {
   // het is een 'gewone' (GET-)aanroep.
}
?>

In het geval van een GET formulier:

Uiteraard kun je deze wijze ook toepassen bij een POST. Het is maar net wat jouw voorkeur geniet.

HTML:

<form ...>

   <input type="hidden" name="submitted" value="true" />
   ....
</form>


PHP:
	
<?
if ( isset ( $_GET [ 'submitted' ] ) && $_GET [ 'submitted' ] == 'true' ) {
   // ...
}
?>

Pagina 11

Spaties van een <input ... value=...> worden niet

Spaties van een <input ... value=...> worden niet herkend door de browser

Je moet attributen van een html element om deze reden altijd quoten en de waarde ervan escapen. Zie de volgende voorbeelden:

Waarom quoten?


<?
$my_value = "This is the value of the field";

echo '<input type=text name=text_input value=';
echo $my_value;
echo '>';
?>


Het resultaat is dan:

<input type=text name=text_input value=This is the value of the field>

De browser zal enkel het stukje This als waarde van het veld interpreteren. De rest zal de browser proberen als attributen te behandelen. Als dat niet lukt wordt de informatie gewoon genegeerd. Als je echter quotes om de waarde heenzet, heb je daar geen last van.
Pagina 12

Help! die aggregate functies

Oke, misschien een eng woord voor sommige mensen, maar het levert dan ook problemen op als je niet weet hoe je er mee om moet gaan


ag·gre·ga·tie (de ~ (v.), ~s, ~s, ~s)
1 samenvoeging => vereniging


Om dit probleem op te lossen hebben we 'Aggregate functies'. Waar het in bovenstaand voorbeeld onduidelijk was hoe B samengevat moest worden om bij de unieke A's te passen, kunnen we de DBMS natuurlijk vertellen welke waarde hij dan moet gebruiken. Zie bijvoorbeeld deze query:

sql:
SELECT a, max(b) FROM Table GROUP BY a

Hier komt het volgende resultaat uit:
A | max(B)
1 | 2
2 | 4



Door de max-functie los te laten op kolom B, hebben we de database duidelijk gemaakt hoe hij moet groeperen. Dus in dit geval groupeert hij op A, en vervolgens laat ie de 'aggregerende functie' los op de verzamelde waardes van B die bij die ene A terecht horen. Andere voorbeelden van aggregate functies zijn min(), avg() en count().

Een ander voorbeeld:

sql:
SELECT a, max(b), count(c) FROM Table GROUP BY a

Hier komt uit:
A | max(B) | count(C)
1 | 2 | 4
2 | 4 | 4

Oftewel we grijpen de A's bij elkaar, en selecteren daarbinnen de hoogste B's en het aantal C's dat we hebben 'weggegroepeerd'.


Hoeveel kolommen kan ik aggregeren en groeperen?

Zoveel je wilt :) Maar let op de hoofdregel: zodra je een GROUP BY-clause gebruikt, moeten alle geselecteerde kolommen terugkomen in de GROUP BY list oftewel een aggregate functie hebben. Een voorbeeld van groeperen op meerdere kolommen:

sql:
SELECT a, b, count(c) FROM Table GROUP BY a, b


Levert op:
A | B | count(C)
1 | 1 | 2
1 | 2 | 2
2 | 3 | 2
2 | 4 | 2

Wat is hier nu gebeurd? Allereerst hebben we gegroepeerd op A's, en daarna op de B's. Van de resulterende set hebben we vervolgens het aantal C's genomen dat 'weggeaggregeerd' is, en dat zijn er inderdaad 2 per groep. Klopt exact dus:)
Pagina 13

wordt nog toegevoegt

wordt nog toegevoegd
Pagina 14

Ik kan de fout echt niet vinden!



Heb je error_reporting op E_ALL staan? Nee? Meteen doen! Deze directive / functie op E_ALL zetten zorgt er namelijk voor dat je veel meer bruikbare debug-informatie krijgt. PHP is namelijk van zichzelf heel erg tolerant met bepaalde constructies, terwijl ze eigenlijk niet helemaal netjes zijn of al min of meer verboden zijn maar nog toegelaten worden wegens backward compatibility.

Als je zgn. notices ook toelaat in je error_reporting krijg je o.a. informatie over

* Het gebruik van niet-bestaande variabelen of elementen uit een array

Om te controleren of een bepaalde variabele of element bestaat gebruik je de functie isset().

Vooral zinvol bij tikfouten en controleren op malafide input
* Het gebruik van ongequote strings als array-indices

Ongequote strings worden enkel nog toegelaten bij wijze van backward compatibility. In principe is dit uit de taal geschrapt vanwege mogelijke conflicten met constanten gedefinieerd met define()

De manual zelf omschrijft het als volgt:

Notices are not printed by default, and indicate that the script encountered something that could indicate an error, but could also happen in the normal course of running a script. For example, trying to access the value of a variable which has not been set, or calling stat() on a file that doesn't exist.

Schrik dus niet als je je error_reporting ook op E_ALL hebt staan

Reacties

0
Nog geen reacties.