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.

Co tak naprawdę dzieje się pod całą warstwą:

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.

Let's do this

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).

⚠️ **GitHub.com Fallback** ⚠️