Kako optimizirati PHP Laravel web aplikaciju za visoke performanse?

Laravel je mnogo stvari. Ali brz nije jedan od njih. Naučimo nekoliko trikova trgovine kako biste brže prošli!


Nijedan PHP programer ne dira Laravel ovih dana. Oni su ili mlađi ili srednji programeri koji vole brzi razvoj koji nudi Laravel ili su stariji programeri koji su prisiljeni učiti Laravel zbog tržišnih pritisaka.

Bilo kako bilo, ne poričemo da je Laravel oživio PHP ekosustav (ja bih, sigurno, davno napustio PHP svijet da Laravelova nije bilo).

Isječak (pomalo opravdano) samozahvale iz Laravela

No, budući da se Laravel naginje unatrag kako bi vam olakšao stvari, to znači da ispod toga radi tona i tone posla kako bi se osiguralo ugodan život kao programer. Sve “čarobne” značajke Laravela koje samo izgledaju kao da djeluju imaju slojeve na slojeve koda koje je potrebno uskočiti svaki put kada se neka značajka pokrene. Čak i jednostavan trag Izuzeća koliko je duboka zečja rupa (primjetite gdje počinje greška, sve do glavne jezgre):

Zbog toga što se čini da je pogreška pri sastavljanju u jednom od prikaza, postoji 18 poziva funkcije kojima se može pronaći. Osobno sam naišao na 40, a lako bi moglo biti više ako koristite druge knjižnice i dodatke.

Ako su ovi slojevi na slojevima koda, Laravel je spor.

Koliko je spor Laravel?

Iskreno, na to je pitanje nemoguće odgovoriti iz više razloga.

Prvi, ne postoji prihvaćeni, objektivni, razumni standard za mjerenje brzine web aplikacija. Brži ili sporiji u odnosu na ono? Pod kojim uvjetima?

Drugi, web aplikacija ovisi o toliko mnogo stvari (baza podataka, datotečni sustav, mreža, predmemorija itd.) da je sasvim glupo govoriti o brzini. Vrlo brza web aplikacija s vrlo sporom bazom podataka vrlo je spora web web aplikacija. ��

Ali upravo je ta nesigurnost razlog zašto su referentna mjesta popularna. Iako ne znače ništa (vidi ovaj i ovaj), pružaju neke referentne okvire i pomažu nam da poludimo. Stoga, s nekoliko prstohvata soli spremnih, hajde da dobijemo pogrešnu, grubu predstavu o brzini u okvirima PHP-a.

Prolazeći tim prilično uglednim GitHubom izvor, evo kako se PHP okviri usklađuju u usporedbi:

Ovdje možda nećete primijetiti Laravela (čak i ako jako jako škljocnete) ako svoj slučaj ne bacate na kraj repa. Da, dragi prijatelji, Laravel je zadnji! Sada, dodijeljeno, većina tih “okvira” nije baš praktična ili čak korisna, ali nam govori koliko je lagan Laravel u usporedbi s drugim popularnijim.

Obično se ta “sporost” ne pojavljuje u aplikacijama jer naše svakodnevne web aplikacije rijetko dosežu visoke brojeve. Ali kad to učine (recimo, više od 200-500 paralelno), poslužitelji počinju gušiti i umirati. Došlo je vrijeme kada se čak i bacanje više hardvera na problem ne reši, a računi za infrastrukturu brzo se povećavaju da se vaši visoki ideali računalstva u oblaku ruše..

Ali hej, razveselite se! Ovaj članak nije o tome što se ne može, već o tome što se može učiniti. ��

Dobra vijest je da možete puno učiniti da aplikacija Laravel brže napreduje. Nekoliko puta brzo. Da, ne šalite se. Možete napraviti istu bazu kodova balistički i uštedjeti nekoliko stotina dolara na računima za infrastrukturu / hosting svakog mjeseca. Kako? Ajmo na to.

Četiri vrste optimizacije

Po mom mišljenju, optimizacija se može učiniti na četiri različite razine (kada je u pitanju PHP aplikacija, odnosno):

  1. Jezik razine: To znači da koristite bržu verziju jezika i izbjegavate određene značajke / stilove kodiranja na jeziku koji vaš kôd čini sporim.
  2. Okvir razine: To su stvari o kojima ćemo govoriti u ovom članku.
  3. Infrastruktura na razini: Ugađanje upravitelja PHP procesa, web poslužitelja, baze podataka itd.
  4. Hardver razine: Prelazak na boljeg, bržeg i snažnijeg pružatelja usluga hardverskog hostinga.

Sve ove vrste optimizacije imaju svoje mjesto (na primjer, php-fpm optimizacija je prilično kritična i moćna). No fokus ovog članka bit će optimizacije isključivo tipa 2: one koje se odnose na okvir.

Uzgred, iza numeriranja ne postoji obrazloženje i to nije prihvaćeni standard. Upravo sam ih izmislila. Nemojte me nikada citirati i reći: „Trebamo optimizaciju tipa 3 na našem poslužitelju“, ili će vas vodstvo vašeg tima ubiti, naći i ubiti mene. ��

I sada napokon stižemo u obećanu zemlju.

Budite svjesni n + 1 upita baze podataka

Problem upita n + 1 uobičajen je kada se koriste ORM-ovi. Laravel ima svoj snažan ORM nazvan Eloquent, koji je toliko lijep, tako zgodan da često zaboravljamo pogledati što se događa.

Razmislite o vrlo uobičajenom scenariju: prikazivanje popisa svih narudžbi postavljenih na određenom popisu kupaca. To je prilično uobičajeno u sustavima e-trgovine i svim sučeljima za izvještavanje gdje moramo prikazati sve entitete povezane s nekim entitetima.

U Laravelu bismo mogli zamisliti funkciju kontrolera koja radi takav posao:

klase OrdersController proširuje Controller
{
// …

javna funkcija getAllByCustomers (Zatražite $ zahtjev, niz $ ids) {
$ kupci = kupci :: findMany ($ ids);
$ naloga = prikupiti (); // nova kolekcija

foreach ($ klijenti kao $ kupac) {
$ naloga = $ naloga->spajanje ($ kupac->narudžbe);
}

povratni prikaz (‘admin.reports.orders’, [‘order’ =)> $ naloga]);
}
}

Slatko! I još važnije, elegantno, lijepo. ����

Nažalost, u Laravelu je katastrofalan način pisanja koda.

Evo zašto.

Kad tražimo od ORM-a da potraži kupce, generira se SQL upit poput ovog:

ODABIR * OD kupaca GDJE ID U (22, 45, 34,.);

Što je točno onako kako se i očekivalo. Kao rezultat, svi vraćeni redovi se spremaju u zbirku $ kupaca unutar funkcije kontrolera.

Sada petljamo preko svakog kupca jedan po jedan i dobivamo njihove narudžbe. Ovo izvršava sljedeći upit . . .

ODABIR * IZ NARUDŽBE GDJE customer_id = 22;

. . . koliko puta ima kupaca.

Drugim riječima, ako trebamo dobiti podatke o narudžbi za 1000 kupaca, ukupan broj izvršenih upita baze podataka bit će 1 (za dohvaćanje svih podataka o klijentima) + 1000 (za dohvaćanje podataka o narudžbi za svakog kupca) = 1001. Ovo je odakle dolazi ime n + 1.

Možemo li bolje? Sigurno! Korištenjem onoga što se naziva nestrpljivim učitavanjem, možemo prisiliti ORM da izvede pridruživanje i vratiti sve potrebne podatke u jednom upitu! Kao ovo:

$ naloga = kupac :: findMany ($ ids)->s ( „narudžbe”)->dobiti();

Dobivena struktura podataka sigurno je ugniježđena, ali se podaci o narudžbi mogu lako izvući. Jedan pojedinačni upit, u ovom slučaju, je otprilike ovako:

ODABIR * OD KUPCA UNUTAR PRIDRUŽITE se narudžbe na kupcima.id = narudžbe.customer_id GDJE kupci.id U (22, 45,.);

Jedan je upit, naravno, bolji od tisuću dodatnih upita. Zamislite što bi se dogodilo da je bilo 10.000 kupaca na obradu! Ili ne daj Bože ako smo također željeli prikazati predmete sadržane u svakom narudžbi! Upamtite da se naziv tehnike brzo učitava i gotovo je uvijek dobra ideja.

Keširaj konfiguraciju!

Jedan od razloga Laravelove fleksibilnosti su tone konfiguracijskih datoteka koje su dio okvira. Želite promijeniti način / gdje su slike pohranjene?

Pa, samo promijenite datoteku config / filesystems.php (barem pri pisanju). Želite raditi s više upravljačkih programa čekanja? Slobodno ih opišite u config / queue.php. Upravo sam prebrojao i otkrio da postoji 13 konfiguracijskih datoteka za različite aspekte okvira, osiguravajući da nećete biti razočarani bez obzira što želite promijeniti.

S obzirom na prirodu PHP-a, svaki put kada dođe novi web zahtjev, Laravel se probudi, pokrene sve i analizira sve te konfiguracijske datoteke kako bi smislio kako drugačije postupiti. Samo što je glupo ako se ništa nije promijenilo u posljednjih nekoliko dana! Obnova konfigura na svaki zahtjev je gubitak koji se može (zapravo, mora) izbjeći, a izlaz je jednostavna naredba koju Laravel nudi:

php artisan config: predmemorija

Sve ovo kombinira sve dostupne konfiguracijske datoteke u jednu, a predmemorija je negdje za brzo pretraživanje. Sljedeći put kad bude web zahtjeva, Laravel će jednostavno pročitati ovu jednu datoteku i krenuti dalje.

Rečeno je da je predmemoriranje konfiguracije izuzetno osjetljiva operacija koja vam se može raznijeti na licu. Najveći problem je da nakon što izdate ovu naredbu, env () funkcija poziva s bilo kojeg mjesta osim konfiguracijskih datoteka vratit će se na nulu!

To ima smisla kad razmišljate o tome. Ako koristite predmemoriranje konfiguracije, govorite okviru, “Znate što, mislim da sam lijepo postavio stvari i uvjeren sam da ne želim da se mijenjaju.” Drugim riječima, očekujete da okruženje ostane statično, za što su .env datoteke.

S tim rečima, evo nekoliko ukrašenih željeza, svetih, nepromjenljivih pravila konfiguracije predmemoriranja:

  1. Učinite to samo na proizvodnom sustavu.
  2. Učinite to samo ako ste stvarno, stvarno sigurni da želite zamrznuti konfiguraciju.
  3. U slučaju da nešto pođe po zlu, poništite postavku s php artisan cache-om: clear
  4. Molite se da šteta nanesena poslu nije bila značajna!

Smanjite automatski učitane usluge

Da bi vam pomogao, Laravel učita tonu usluga kad se probudi. Oni su dostupni u datoteci config / app.php kao dio polja ključa “davatelja usluga”. Pogledajmo što imam u svom slučaju:

/ *
|————————————————————————–
| Automatski učitani davatelji usluga
|————————————————————————–
|
| Ovdje navedeni davatelji usluga automatski će se učitati na
| zahtjev za prijavu. Slobodno dodajte svoje usluge
| ovaj niz za pružanje proširene funkcionalnosti vašim aplikacijama.
|
* /

‘davatelji’ => [

/ *
* Davatelji usluga Laravel Framework…
* /
Illuminate \ autorizacija \ AuthServiceProvider :: klasa,
Illuminate \ emitiranje \ BroadcastServiceProvider :: klasa,
Illuminate \ Autobus \ BusServiceProvider :: klasa,
Illuminate \ Cache \ CacheServiceProvider :: klasa,
Illuminate \ Zaklada \ Providers \ ConsoleSupportServiceProvider :: klasa,
Illuminate \ kolačić \ CookieServiceProvider :: klasa,
Osvijetlite \ Baza podataka \ DatabaseServiceProvider :: klasa,
Illuminate \ šifriranje \ EncryptionServiceProvider :: klasa,
Illuminate \ datotečnog sustava \ FilesystemServiceProvider :: klasa,
Illuminate \ Zaklada \ Providers \ FoundationServiceProvider :: klasa,
Osvijetlite \ raspršenja \ HashServiceProvider :: klasa,
Illuminate \ Mail \ MailServiceProvider :: klasa,
Illuminate \ Obavijesti \ NotificationServiceProvider :: klasa,
Illuminate \ Numeriranje \ PaginationServiceProvider :: klasa,
Illuminate \ cjevovoda \ PipelineServiceProvider :: klasa,
Illuminate \ Red \ QueueServiceProvider :: klasa,
Illuminate \ Redis \ RedisServiceProvider :: klasa,
Illuminate \ autorizacija \ Lozinke \ PasswordResetServiceProvider :: klasa,
Illuminate \ Session \ SessionServiceProvider :: klasa,
Illuminate \ Prevođenje \ TranslationServiceProvider :: klasa,
Illuminate \ Provjera \ ValidationServiceProvider :: klasa,
Illuminate \ Pogledaj \ ViewServiceProvider :: klasa,

/ *
* Pružatelji usluga paketa…
* /

/ *
* Davatelji usluga aplikacije…
* /
App \ Providers \ AppServiceProvider :: klasa,
App \ Providers \ AuthServiceProvider :: klasa,
// Aplikacija \ Pružatelji usluga \ BroadcastServiceProvider :: klasa,
App \ Providers \ EventServiceProvider :: klasa,
App \ Providers \ RouteServiceProvider :: klasa,

],

Još jednom, prebrojao sam, a na popisu je 27 usluga! Sada će vam ih možda trebati, ali malo je vjerojatno.

Na primjer, u ovom trenutku ja gradim REST API, što znači da mi ne treba pružatelj usluga sesije, prikaz davatelja usluga itd. A budući da radim neke stvari na svoj način i ne slijedim zadane postavke okvira , Također mogu onemogućiti davatelja Auth usluga, davatelja usluga paginacije, davatelja usluga prevođenja i tako dalje. Sve u svemu, gotovo polovica njih je nepotrebna za moj slučaj upotrebe.

Pogledajte dugo i naporno vašu prijavu. Trebaju li svi ti davatelji usluga? Ali za ime Boga, molim vas, nemojte slijepo komentirati ove usluge i gurati se na produkciju! Pokrenite sve testove, ručno provjerite stvari na uređajima za razvoj i postavljanje i budite vrlo paranoični prije nego što povučete okidač. ��

Budite mudri sa hrpama internog softvera

Kada vam je potrebna neka prilagođena obrada dolaznog internetskog zahtjeva, stvaranje novog srednjeg softvera odgovor je. Sad je primamljivo otvoriti app / Http / Kernel.php i staviti srednji softver u web ili api stack; na taj način, postaje dostupna putem aplikacije i ako ne radi nešto nametljivo (kao što je prijava ili prijava, na primjer).

Međutim, kako aplikacija raste, ova zbirka globalnog srednjeg softvera može postati tihi teret aplikacije ako su svi (ili većina) prisutni u svakom zahtjevu, čak i ako za to nema poslovnog razloga..

Drugim riječima, budite oprezni gdje dodajete / primjenjujete novi srednji softver. Može biti povoljnije dodati nešto na globalnoj razini, ali kazna za performanse je dugoročno vrlo visoka. Znam da bi bol trebao trpjeti da biste selektivno primijenili srednji softver svaki put kada se dogodi nova promjena, ali to bi bio bol koji bih rado prihvatio i preporučio!

Izbjegavajte ORM (povremeno)

Iako Eloquent čini mnogo aspekata interakcije s DB-om ugodnim, to dolazi po cijenu brzine. Budući da je preslikač, ORM ne samo da mora dohvaćati zapise iz baze podataka, već ih inicirati i modelirati te ih hidratizirati (ispuniti ih) podacima stupca.

Dakle, ako radite s jednostavnim $ users = User :: all (), a postoji, recimo, 10 000 korisnika, okvir će dohvatiti 10 000 redaka iz baze podataka, a interno napraviti 10 000 novih User () i napuniti njihova svojstva odgovarajućim podacima , Ovo je velika količina posla koji se obavlja iza scene, a ako baza podataka tamo gdje budete vaša aplikacija postaje usko grlo, zaobilaženje ORM-a ponekad je dobra ideja..

To se posebno odnosi na složene SQL upite, gdje trebate uskočiti puno obruča i napisati zapise nakon zatvaranja, a svejedno završiti s učinkovitim upitom. U takvim je slučajevima poželjno raditi DB: raw () i pisati upit ručno.

Prolazi ovaj studija izvedbe, čak i za jednostavne umetke Elokvent je mnogo sporiji kako se povećava broj zapisa:

Koristite keširanje što je više moguće

Jedna od najbolje čuvanih tajni optimizacije web aplikacija je predmemoriranje.

Za neupućene, spremanje u predmemoriju znači unaprijed izračunavanje i pohranjivanje skupih rezultata (skupih u smislu upotrebe CPU-a i memorije) i jednostavno vraćanje istih prilikom ponavljanja istog upita.

Primjerice, u trgovini e-trgovine moglo bi se naići na proizvode od dva milijuna proizvoda, većinu ljudi ljudi zanimaju svježi proizvodi, u određenom cjenovnom rasponu i za određenu dobnu skupinu. Upiti u bazu podataka za ove informacije su rasipni – budući da se upit ne mijenja često, bolje je pohraniti ove rezultate negdje do kojih brzo možemo pristupiti.

Laravel ima ugrađenu podršku za nekoliko vrsta caching. Uz upotrebu pogonskog predmemoriranja i izradu sustava za predmemoriranje od temelja, možda biste trebali upotrijebiti i neke Laravel pakete koji olakšavaju predmemoriranje modela, keširanje upita, itd.

Ali imajte na umu da, osim određenog slučaja pojednostavljene uporabe, pregrađeni keširanje može stvoriti više problema nego što ih oni rješavaju.

Preferirajte predmemoriranje u memoriji

Kada nešto spremate u Laravel, imate nekoliko opcija gdje pohraniti rezultirajući izračun koji se treba spremiti u predmemoriju. Te su opcije također poznate kao vozači predmemorije. Dakle, iako je datotečni sustav moguće i potpuno razumno koristiti za spremanje rezultata predmemorije, zapravo nije ono što predmemoriranje znači.

U idealnom slučaju želite koristiti memoriju memorije (koja u cijelosti živi u RAM-u) kao što su Redis, Memcached, MongoDB itd., Tako da pri većim opterećenjima keširanje služi vitalnoj upotrebi, a ne da postanete usko grlo.

Sada biste mogli pomisliti da je imati SSD disk gotovo isto što i koristiti RAM stick, ali nije ni blizu. Čak i neformalni mjerila pokazuju da RAM nadmašuje SSD 10-20 puta kad je u pitanju brzina.

Moj omiljeni sustav kada je u pitanju predmemoriranje je Redis. to je smiješno brz (100 000 operacija čitanja u sekundi je uobičajeno), a za vrlo velike keš-sisteme može se razviti u a Klastera lako.

Keširajte rute

Kao i konfiguracija aplikacije, rute se ne mijenjaju puno vremena i idealan su kandidat za predmemoriranje. Ovo je posebno istinito ako ne možete podnijeti velike datoteke poput mene i podijeliti web.php i api.php na nekoliko datoteka. Jedna naredba Laravel spakira sve dostupne rute i drži ih spremnim za budući pristup:

php zanatski put: predmemorija

A kad završite sa dodavanjem ili promjenom ruta, jednostavno učinite:

php obrtnički put: jasan

Optimizacija slike i CDN

Slike su srce i duša većine web aplikacija. Slučajno su ujedno i najveći potrošači propusnosti i jedan od najvećih razloga za sporo korištenje aplikacija / web stranica. Ako prenesene slike jednostavno naivno pohranite na poslužitelj i pošaljete ih natrag u HTTP odgovorima, dopuštate da nestane ogromna mogućnost optimizacije.

Moja prva preporuka nije pohranjivanje slika lokalno – tu je problem s gubitkom podataka s kojim se treba nositi, a ovisno o tome u kojoj se geografskoj regiji nalazi vaš kupac, prijenos podataka može biti bolno spor.

Umjesto toga, potražite rješenje poput Cloudinary koji automatski mijenja veličinu i optimizira slike u pokretu.

Ako to nije moguće, upotrijebite nešto poput Cloudflare-a za predmemoriranje i posluživanje slika dok su one spremljene na vašem poslužitelju.

A čak i ako to nije moguće, uvelike promijenite softver web-poslužitelja radi stiskanja imovine i usmjerite preglednik posjetitelja na predmemoriranje stvari. Evo kako bi izgledao isječak konfiguracije Nginx:

poslužitelj {

# datoteka je skraćena

# gzip postavke kompresije
gzip na;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied bilo koji;
gzip_vary uključen;

# kontrola predmemorije preglednika
mjesto ~ * \. (ico | css | js | gif | jpeg | jpg | png | woff | ttf | otf | svg | woff2 | eot) $ {
istječe 1d;
access_log isključen;
add_header Pragma javno;
add_header Cache-Control "javno, maks. dob = 86400";
}
}

Svjestan sam da optimizacija slike nema nikakve veze sa Laravelom, ali to je tako jednostavan i moćan trik (i toliko je često zanemaren) da ne bi mogao sebi pomoći.

Optimizacija automatskog utovarivača

Automatsko učitavanje uredna je, ne baš stara značajka u PHP-u koja je, vjerovatno, spasila jezik od propasti. U skladu s tim, postupak pronalaženja i učitavanja odgovarajuće klase dešifriranjem datog niza prostora imena zahtijeva vremena i može se izbjeći u proizvodnim implementacijama gdje je poželjna visoka učinkovitost. Laravel još jednom ima ovo rješenje s jednom naredbom:

skladateljska instalacija –optimize-autoloader –no-dev

Sprijatelji se s redovima

redovi kako obrađujete stvari kada ih je puno, a svakoj od njih je potrebno nekoliko milisekundi. Dobar primjer je slanje e-poruka – široko rasprostranjeni slučaj u web-aplikacijama je uklanjanje nekoliko e-poruka s obavijestima kada korisnik izvrši neke radnje.

Na primjer, u novoizbavljenom proizvodu možda ćete poželjeti da se vodstvo tvrtke (nekih 6-7 adresa e-pošte) obavijesti kad god netko naruči iznad neke vrijednosti. Pod pretpostavkom da vaš gateway e-pošte može odgovoriti na vaš SMTP zahtjev za 500 ms, govorimo o dobrom čekanju od 3-4 sekunde za korisnika prije nego što započne potvrda narudžbe. Stvarno loš komad UX-a, siguran sam da hoćete složiti.

Lijek je pohranjivanje poslova kad dođu, reći korisniku da je sve prošlo dobro i obraditi ih (nekoliko sekundi) kasnije. Ako postoji pogreška, poslovi iz čekanja mogu se ponovno pokušati nekoliko puta prije nego što se utvrde da nisu uspjeli.

Zasluge: Microsoft.com

Iako sustav čekanja malo otežava postavljanje (i dodaje neke nadzorne troškove), to je neophodno u modernoj web aplikaciji.

Optimizacija imovine (Laravel Mix)

Za bilo koji prednji materijal u aplikaciji Laravel provjerite postoji li cjevovod koji sastavlja i minimizira sve datoteke imovine. Oni kojima je ugodan sustav paketa kao što su Webpack, Gulp, Parcel itd., Ne trebaju se gnjaviti, ali ako to već ne radite, Laravel Mix je čvrsta preporuka.

Mix je lagana (i ukusna, iskrena!) Omotača oko Webpacka koja se brine za sve vaše datoteke CSS, SASS, JS itd. Za proizvodnju. Tipična .mix.js datoteka može biti ovako mala i još uvijek čini čuda:

const mix = zahtjev (‘laravel-mix’);

mix.js (‘resursi / js / app.js’, ‘javni / js’)
.sass (‘resursi / sass / app.scss’, ‘javni / css’);

To se automatski brine o uvozu, minifikaciji, optimizaciji i cijelom shebang-u kada ste spremni za proizvodnju i pokrenite npm pokrenuti proizvodnju. Mix vodi računa ne samo o tradicionalnim JS i CSS datotekama, već i o Vue i React komponentama koje biste mogli imati u svom tijeku rada aplikacije.

Više informacija ovdje!

Zaključak

Optimizacija performansi više je umjetnost nego znanost – važno je znati kako i koliko treba učiniti nego što učiniti. To je rečeno, nema kraja koliko i što sve možete optimizirati u aplikaciji Laravel.

Ali bez obzira na to što radite, želio bih vam ostaviti nekoliko savjeta o razdvajanju – optimizaciju treba obaviti kada postoji solidan razlog, a ne zato što zvuči dobro ili zato što ste paranoični u pogledu performansi aplikacije za 100.000+ korisnika dok ste u stvarnosti ima ih samo 10.

Ako niste sigurni da li trebate optimizirati aplikaciju ili ne, ne morate razbijati gnijezdo poslovice stršljena. Radna aplikacija koja se osjeća dosadno, ali čini upravo ono što mora, deset puta je poželjnija od aplikacije koja je optimizirana u mutirani hibridni supermašin, ali s vremena na vrijeme padne ravno.

A kako bi newbiew postao Laravel majstor, provjerite ovo online tečaj.

Neka se vaše aplikacije pokreću puno, puno brže! ��

Jeffrey Wilson Administrator
Sorry! The Author has not filled his profile.
follow me
    Like this post? Please share to your friends:
    Adblock
    detector
    map