Na każde pytanie istnieje odpowiedź - xandros15/IntoPHP GitHub Wiki
Aby zacząć pracę, potrzebujemy stworzyć parę narzedzi. Przed tym należy wyjaśnić, jak działa przeplyw danych oczami programisty PHP.
Kamil, chce wejść na strone https://forum.pasja-informatyki.pl/programowanie/php-symfony-zend
W tym celu wpisuje w pasku przeglądarki adres, lub klika w odnośnik.
Przeglądarka odpowiada na zapytanie całą stroną internetową w postaci HTML.
Nasza przeglądarka łączy się z serwerem po adresie ip na odpowiednim porcie, następnie wysyła mu następujące zapytanie:
GET /programowanie/php-symfony-zend HTTP/1.1
Host: forum.pasja-informatyki.pl
Connection: Close
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
DNT: 1
W przypadku zapytania typu post, wyglądało by na przyklad tak:
POST /login HTTP/1.1
Host: forum.pasja-informatyki.pl
Connection: Close
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
DNT: 1
------WebKitFormBoundaryETartzVsjB66hs6B
Content-Disposition: form-data; name="emailhandle"
admin
------WebKitFormBoundaryETartzVsjB66hs6B
Content-Disposition: form-data; name="password"
admin
------WebKitFormBoundaryETartzVsjB66hs6B
Content-Disposition: form-data; name="remember"
1
------WebKitFormBoundaryETartzVsjB66hs6B--
Content-Disposition: form-data; name="code"
0-1513266630-a8c678802999679899d138bbac6f50856a9d0552
------WebKitFormBoundaryETartzVsjB66hs6B--
Content-Disposition: form-data; name="dologin"
1
------WebKitFormBoundaryETartzVsjB66hs6B--
Serwer przetwarza to zapytanie i wysyła w odpowiedzi:
HTTP/1.1 200 OK
Date: Thu, 14 Dec 2017 15:35:29 GMT
Server: Apache
X-Powered-By: PHP/5.6.31
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip
Vary: Accept-Encoding,User-Agent
Set-Cookie: PHPSESSID=lubieplacki; path=/; secure; HttpOnly
Set-Cookie: qa_key=cycki_tez; expires=Sat, 16-Dec-2017 15:35:29 GMT; Max-Age=172800; path=/; secure; httponly
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
<TUTAJ JAKIS KOD HTML>
HTML jest interpretowany przez przeglądarke i wyświetla się strona.
Uwaga: dodałem pewien rodzaj uproszczenia, gdyż na tym poziomie, pewne rzeczy są zbędne (np. sockety).
Dobra, to jak to wygląda po stronie serwera:
Serwer nginx/apache/php/httpd/IIS/etc
wysyła to wszystko do interpretera PHP.
PHP parsuje dane i dodaje do zmiennych globalnych $_SERVER/$_GET/$_POST/$_FILES/$_REQUEST/$_COOKIE
Aplikacja w PHP ma za zadanie zinterpretowac te dane i przygotować odpowiednią odpowiedź.
Prosty przykład:
<?php
$method = $_SERVER['REQUEST_METHOD']; //pobieramy 1. wartość z 1. lini naglowka
$uri = $_SERVER['REQUEST_URI']; //pobieramy 2. wartość z 1. lini naglowka
$foo = $_GET['foo']; //pobieramy przeparsowaną wartość 2. z 1. lini naglowka po znaku ?
header('Content-Type: text/html');
echo 'Wysłane zostało zapytanie: ' . $method . "\r\n";
echo 'Na adres: ' . $uri . "\r\n";
echo 'Wartość zmiennej w uri "foo" jest równa: ' . $foo . "\r\n";
Uwaga: Znak "\r\n"
jest interpretowany jako znak końca lini
Co nam zwróci, jeśli wpiszemy w przeglądarke http://localhost/pokacycki?foo=bar
:
HTTP/1.1 200 OK
Host: localhost
Date: Thu, 14 Dec 2017 16:20:09 +0000
Connection: close
X-Powered-By: PHP/7.2.0
Content-type: text/html;charset=UTF-8
Wysłane zostało zapytanie: GET
Na adres: /pokacycki?foo=bar
Wartość zmiennej w uri "foo" jest równa: bar
Z ciekawości rzućmy sobie takim skryptem:
<pre><?php
var_dump($_SERVER, $_GET, $_POST, $_FILES, $_REQUEST, $_COOKIE);
Widzimy teraz wszystkie przeparsowane wartość dodane do globalnych tablic w PHP.
Dzięki temu możemy swobodnie zinterpretować zapytanie i stworzyć na jego podstawie odpowiedź. Np.
- dodać rekord do bazy.
- zalogować użytkownika.
- wyświetlić ostatnie posty.
Przeparsujmy zapytanie i wyślijmy odpowiedz. Do tego celu stworzymy przykładowe funkcje,
które nam ułatwią życie w późniejszych etapach tworzenia aplikacji.
/**
* Zwraca przeformatowane zapytanie w formie tablicy asocjacyjnej
* @return array
*/
function request(): array
{
/**
* Forma cachowania.
* Gdy następnym razem wywołamy tę funkcje,
* parametry zostaną pobrane właśnie ze zmiennej statycznej
*/
static $request;
if (null === $request) {
/**
* W niektórych serwerach wystarczyłoby pobrać $_SERVER['PATH_INFO'];
*/
$uri = parse_url('http://example.com' . $_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
$files = $_FILES;
$query = $_GET;
$params = $_POST;
$headers = [];
/**
* niektóre nagłówki nie zaczynają się od HTTP_
*/
$special = [
'CONTENT_TYPE',
'CONTENT_LENGTH',
'PHP_AUTH_USER',
'PHP_AUTH_PW',
'PHP_AUTH_DIGEST',
'AUTH_TYPE',
];
/**
* dla uproszczenia nagłówki
* będą znajdować się w jednowymiarowej tablicy
*/
foreach ($_SERVER as $key => $value) {
$key = strtoupper($key);
if (in_array($key, $special) || strpos($key, 'HTTP_') === 0) {
if ($key !== 'HTTP_CONTENT_LENGTH') {
$headers[$key] = $value;
}
}
}
/**
* Na razie tyle parametrów powinno wystarczyć.
* W miarę przebudowy aplikacji, będziemy mogli je modyfikować, z poziomu tej funkcji.
*/
$request = [
'uri' => $uri, // eg. /pokacycki
'method' => $method, // eg. POST
'headers' => $headers, // eg. array('HTTP_CACHE_CONTROL' => 'max-age=0')
'query' => $query, // eg. array('foo' => 'bar')
'params' => $params,// eg. array('baz' => 'bar')
'files' => $files, //eg. array(['foo' => ['name' => '', 'tmp_name' => '/tmp/bla', 'error' => 0, ...]])
];
}
return $request;
}
/**
* @param string $body
* @param int $code
* @param array $headers
*/
function response(string $body, int $code = 200, array $headers = [])
{
/**
* Sprawdzamy, czy nagłówki zostały już wysłane
* by nie dostać błędu o już wysłanych nagłówkach
* @see https://stackoverflow.com/q/8028957
*/
if (!headers_sent()) {
/**
* kod odpowiedzi można na pare sposobów
* trudniejszy: dodać do nagłówka odpowiedzi w postaci
* ``HTTP/{wersja protokolu} {kod odpowiedzi} {wiadomość odpowiedzi eg. OK}``
* łatwiejszy: użyć funkcji http_response_code
*/
http_response_code($code);
/**
* dla uproszczenia nagłówki
* składają się z jednowymiarowej tablicy
*/
foreach ($headers as $name => $value) {
header("$name: $value", true);
}
}
/**
* Na końcu wyświetlamy ciało odpowiedzi
*/
echo $body;
}
/**
* mały pomocnik do przekierowywań
*
* @param string $location
* @param int $code
* @param array $headers
*/
function redirect(string $location, int $code = 302, array $headers = [])
{
$headers = array_merge($headers, ['Location' => $location]);
response('', $code, $headers);
}
Do przetestowania kodu możemy wykorzystać taki skrypt:
$request = request();
if ($request['query']['doDomu'] == '1') {
redirect('/');
} else {
response('<pre>');
var_dump($request);
}
Jak wpiszemy w adresie ?doDomu=1
, to przekieruje nas na strone główną.
Jak nie podamy tego parametru, lub już nas przekieruje pokaże nam jak wyglada przeparsowany przez serwer request (zapytanie).