Taming Slim 2.0

Slim is een lichtgewicht raamwerk dat veel kracht put voor zijn kleine voetafdruk. Het heeft een ongelooflijk routing-systeem en biedt een solide basis om mee te werken zonder je in de weg te lopen. Ik zal het je laten zien!

Maar dat wil niet zeggen dat Slim geen enkele problemen heeft; de installatie van één bestand wordt rommelig naarmate uw toepassing groeit. In dit artikel zullen we bekijken hoe een Slim-toepassing gestructureerd kan worden om niet alleen de functionaliteit te onderhouden, maar ook om de functionaliteit te verbeteren en dingen netjes en systematisch te houden.


Vanilla Slim

Laten we beginnen met het bekijken van een aantal Slim-codes om het probleem te identificeren. Nadat u Slim through Composer hebt geïnstalleerd, moet u een exemplaar van de Slank object en definieer je routes:

 get ('/', function () echo "Home Page";); $ app-> get ('/ testPage', functie () gebruik ($ app) $ app-> render ('testpage.php');); $ App-> run ();

Laten we het Slim-object in de "controller" veranderen.

De eerste methode-aanroep stelt een nieuwe route in voor de root-URI (/) en verbindt de gegeven functie met die route. Dit is vrij uitgebreid, maar eenvoudig in te stellen. De tweede methode-oproep definieert een route voor de URI testpagina. Binnen de bijgeleverde methode gebruiken we Slim's render () methode om een ​​weergave te maken.

Hier ligt het eerste probleem: deze functie (een afsluiting) wordt niet genoemd in de huidige context en heeft geen toegang tot de functies van Slim. Dit is de reden waarom we de gebruik sleutelwoord om de verwijzing naar de Slim-app door te geven.

Het tweede probleem komt voort uit de architectuur van Slim; het is de bedoeling dat alles in één bestand wordt gedefinieerd. Natuurlijk kunt u de variabele uitbesteden aan een ander bestand, maar het wordt gewoon rommelig. Idealiter willen we de mogelijkheid om controllers toe te voegen om het framework in individuele componenten te modulariseren. Als een bonus zou het leuk zijn als deze controllers native toegang tot de functies van Slim zouden bieden, zodat verwijzingen naar de sluitingen niet meer nodig zijn.


Een beetje reverse engineering

Het is betwistbaar of het lezen van de broncode van een open-sourceproject wordt beschouwd als reverse-engineering, maar het is de term die ik blijf houden. We begrijpen hoe Slim moet worden gebruikt, maar wat gebeurt er onder de motorkap? Laten we eens kijken naar een meer gecompliceerde route om tot de kern van deze vraag te komen:

 $ app-> get ('/ users /: name', function ($ name) echo "Hello". $ name;);

Deze routedefinitie gebruikt een dubbele punt met het woord, naam. Dit is een tijdelijke aanduiding en de waarde die op die plaats wordt gebruikt, wordt doorgegeven aan de functie. Bijvoorbeeld, / Users / gabriel komt overeen met deze route en 'gabriel' wordt doorgegeven aan de functie. De route, / gebruikers, aan de andere kant, is geen match omdat het de parameter mist.

Als u er logisch over nadenkt, zijn er een aantal stappen die moeten worden voltooid om een ​​route te verwerken.

  • Stap een: controleer of de route overeenkomt met de huidige URI.
  • Stap twee: extraheer alle parameters uit de URI.
  • Stap drie: bel de aangesloten sluiting en geef de geëxtraheerde parameters door.

Om het proces beter te optimaliseren, slaan Slim-gebruikende regex-callbacks en -groepen de tijdelijke aanduidingen op naar wedstrijden. Dit combineert twee stappen in één, waardoor alleen de verbonden functie wordt uitgevoerd wanneer Slim gereed is. Het wordt duidelijk dat het routeobject op zichzelf staat, en eerlijk gezegd, alles wat nodig is.

In het vorige voorbeeld hadden we toegang tot de functies van Slim bij het parseren van de routes, maar we moesten een Slim-objectverwijzing doorgeven omdat deze anders niet beschikbaar zou zijn binnen de uitvoeringscontext van de functie. Dat is alles wat u nodig heeft voor de meeste toepassingen, omdat de logica van uw toepassing in de controller moet voorkomen.

Laten we, met dat in gedachten, het "routing" -gedeelte in een klasse uitpakken en het Slim-object in de "controller" veranderen.


Ermee beginnen

Laten we beginnen met het downloaden en installeren van "vanilla Slim" als je dit nog niet hebt gedaan. Ik ga ervan uit dat Composer is geïnstalleerd, maar anders niet, volg dan de stappen .

Maak in een nieuwe map een bestand met de naam composer.json, en voeg het volgende toe:

 "name": "nettuts / slim-mvc", "require": "slim / slim": "*", "slim / extras": "*", "takje / takje": "*"

Blader in een terminalvenster naar de map en typ componist installeren. Ik zal je door deze pakketten leiden, als dit de eerste keer is dat je Slim gebruikt.

  • slank / slim - het daadwerkelijke Slanke kader.
  • slank / extras - een reeks optionele klassen om Slim uit te breiden.
  • takje / takje - de twig-sjablonerende motor.

Voor deze tutorial heb je technisch gezien de Slim-extra's of Twig niet nodig, maar ik vind het leuk om Twig te gebruiken in plaats van standaard PHP-sjablonen. Als u Twig gebruikt, hebt u echter de Slim-extra's nodig omdat deze een interface biedt tussen Twig en Slim.

Laten we nu onze aangepaste bestanden toevoegen, en we beginnen met het toevoegen van een map aan de vendors map. Ik zal de mijne noemen Nettuts, maar voel je vrij om de jouwe te noemen, wat je maar wilt. Als u zich nog steeds in de terminal bevindt, controleert u of uw terminalvenster zich in de projectdirectory bevindt en typt u het volgende:

mkdir vendor / Nettuts

Bewerk het nu composer.json door de verwijzing naar deze nieuwe map toe te voegen:

 "name": "nettuts / slim-mvc", "require": "slim / slim": "*", "slim / extras": "*", "takje / takje": "*", " autoload ": " psr-0 ": " Nettuts ":" vendor / "

We willen dat onze app automatisch klassen laadt van de Nettuts naamruimte, dus dit vertelt Composer alle aanvragen voor te mappen Nettuts naar de PSR-0-standaard vanaf de verkoper map.

Voer nu uit:

componist dump-autoload

Dit hercompileert de autoloader om de nieuwe referentie op te nemen. Maak vervolgens een bestand met de naam Router.php, binnen de Nettuts map en voer het volgende in:

  

We hebben gezien dat elk routevoorwerp een zelfstandige functie heeft die bepaalt of deze overeenkomt met de geleverde URI. We willen dus een hele reeks routes en een functie om ze te ontleden. We hebben ook een andere functie nodig om nieuwe routes toe te voegen en een manier om de URI op te halen uit het huidige HTTP-verzoek.

Laten we beginnen met het toevoegen van enkele lidvariabelen en de constructor:

 Class Router beschermde $ routes; beveiligd $ verzoek; publieke functie __construct () $ env = \ Slim \ Environment :: getInstance (); $ this-> request = new \ Slim \ Http \ Request ($ env); $ this-> routes = array (); 

We hebben de routes variabele om de routes te bevatten, en de verzoek variabele om de Slim op te slaan Verzoek voorwerp. Vervolgens hebben we de mogelijkheid om routes toe te voegen. Om bij de beste werkwijzen te blijven, zal ik dit in twee stappen breken:

openbare functie addRoutes ($ routes) foreach ($ routes als $ route => $ pad) $ method = "any"; if (strpos ($ path, "@")! == false) list ($ path, $ method) = explode ("@", $ path);  $ func = $ this-> processCallback ($ path); $ r = nieuw \ Slim \ Route ($ route, $ func); $ R-> setHttpMethods (strtoupper ($ methode)); array_push ($ this-> routes, $ r); 

Deze openbare functie accepteert een associatief array van routes in de indeling route => pad, waar route is een standaard Slim-route en pad is een string met de volgende conventie:

Optioneel kunt u bepaalde parameters weglaten om een ​​standaardwaarde te gebruiken. De klassenaam wordt bijvoorbeeld vervangen door Hoofd als je het weglaat, inhoudsopgave is de standaard voor weggelaten functienamen en de standaard voor de HTTP-methode is ieder. Natuurlijk, ieder is geen echte HTTP-methode, maar het is een waarde die Slim gebruikt om alle HTTP-methodetypen met elkaar te matchen.

De addRoutes functie begint met a foreach lus die door de routes fietst. Vervolgens stellen we de standaard HTTP-methode in, optioneel overschreven deze met de verstrekte methode als de @ symbool is aanwezig. Vervolgens geven we de rest van het pad door aan een functie om een ​​callback op te halen en deze aan een route te koppelen. Ten slotte voegen we de route toe aan de array.

Laten we nu kijken naar de processCallback () functie:

beschermde functie processCallback ($ pad) $ class = "Main"; if (strpos ($ path, ":")! == false) list ($ class, $ path) = explode (":", $ path);  $ function = ($ path! = "")? $ pad: "index"; $ func = function () gebruik ($ class, $ function) $ class = '\ Controllers \\'. $ Klasse; $ class = new $ class (); $ args = func_get_args (); return call_user_func_array (array ($ klasse, $ functie), $ args); ; return $ func; 

Het tweede probleem komt voort uit de architectuur van Slim; het is de bedoeling dat alles in één bestand wordt gedefinieerd.

We stellen eerst de standaardklasse in op Hoofd, en overschrijven die klasse als het dubbele puntsymbool wordt gevonden. Vervolgens bepalen we of een functie is gedefinieerd en gebruikt de standaardmethode inhoudsopgave indien nodig. Vervolgens geven we de klassen- en functienamen door aan een afsluiting en geven deze terug aan de route.

Binnen de sluiting plaatsen we de klassenaam met de naamruimte. We maken vervolgens een nieuw exemplaar van de opgegeven klasse en halen de lijst met argumenten op die aan deze functie wordt doorgegeven. Als je het vergeet, terwijl Slim controleert of een route overeenkomt, bouwt het langzaam een ​​lijst met parameters op gebaseerd op jokertekens van de route. Deze functie (func_get_args ()) kan worden gebruikt om de doorgegeven parameters in een array te krijgen. Dan, met behulp van de call_user_func_array () methode stelt ons in staat om de klasse en functie te specificeren, terwijl de parameters aan de controller worden doorgegeven.

Het is geen erg ingewikkelde functie als je het eenmaal begrijpt, maar het is een heel goed voorbeeld van wanneer sluitingen van pas komen.

Om samen te vatten, hebben we een functie toegevoegd aan ons router waarmee u een associatieve array kunt doorgeven die routes en paden bevat die zijn toegewezen aan klassen en functies. De laatste stap is om de routes te verwerken en elke match uit te voeren. Laten we het noemen met de Slim-naamgevingsconventie rennen:

public function run () $ display404 = true; $ uri = $ this-> request-> getResourceUri (); $ method = $ this-> request-> getMethod (); foreach ($ this-> routes als $ i => $ route) if ($ route-> matches ($ uri)) if ($ route-> ondersteuntHttpMethod ($ methode) || $ route-> supportsHttpMethod ("ELK ")) call_user_func_array ($ route-> getCallable (), array_values ​​($ route-> getParams ())); $ display404 = false;  if ($ display404) echo "404 - route niet gevonden"; 

We beginnen met het instellen van de display404 variabele, vertegenwoordigend geen gevonden routes, naar waar. Als we een overeenkomende route vinden, zullen we dit instellen vals en omzeil de foutmelding. Vervolgens gebruiken we het verzoekobject van Slim om de huidige URI- en HTTP-methode op te halen.

We zullen deze informatie gebruiken om doorheen te bladeren en overeenkomsten uit onze array te vinden.

Zodra het route-object is wedstrijden() functie uitvoert, kunt u bellen getParams () om de geparseerde parameters op te halen. Met behulp van die functie en de getCallable () methode, zijn we in staat om de sluiting uit te voeren en de nodige parameters door te geven. Ten slotte geven we een 404-bericht weer als er geen route overeenkomt met de huidige URI.

Laten we de controllerklasse maken die de callbacks voor deze routes bevat. Als je meegekeken hebt, heb je je misschien gerealiseerd dat we nooit een protocol of klasse hebben afgedwongen. Als u geen controllerklasse wilt maken, werkt elke klasse goed.

Dus waarom creëren we een controllerklasse? Het korte antwoord is dat we Slim nog steeds niet echt hebben gebruikt. We gebruikten delen van Slim voor het HTTP-verzoek en routes, maar het hele punt hiervan was om gemakkelijk toegang te hebben tot alle eigenschappen van Slim. Onze controllerklasse zal de werkelijke Slim-klasse uitbreiden en krijgt toegang tot alle methoden van Slim.

U kunt dit net zo gemakkelijk overslaan en Slim rechtstreeks van uw controllers subclass.


De controller bouwen

Deze controller stelt je in principe in staat om Slim aan te passen terwijl je het nog steeds vanille houdt. Geef het bestand een naam Controller.php, en schrijf de volgende code:

data = $ instellingen ['model'];  parent :: __ construct ($ settings); 

Wanneer u Slim initialiseert, kunt u verschillende instellingen doorgeven, variërend van de debug-modus van de toepassing tot de sjabloon-engine. In plaats van het hard coderen van waarden in de constructor, laad ik ze vanuit een bestand met de naam settings.php en geef die array door aan de constructor van de ouder.

Omdat we Slim uitbreiden, dacht ik dat het cool zou zijn om een ​​'model'-instelling toe te voegen, zodat mensen hun data-object rechtstreeks in de controller kunnen haken.

Dat is het gedeelte dat u in het midden van de bovenstaande code kunt zien. We controleren of het model- instelling is ingesteld en wijst deze toe aan de controller gegevens eigendom indien nodig.

Maak nu een bestand met de naam settings.php in de hoofdmap van uw project (de map met de composer.json bestand) en voer het volgende in:

 new \ Slim \ Extras \ Views \ Twig (), 'templates.path' => '... / Views', 'model' => (Object) array ("message" => "Hello World")); return $ settings;

Dit zijn standaard Slim-instellingen, met uitzondering van het model. Welke waarde ook wordt toegekend aan de model- eigendom wordt doorgegeven aan de gegevens variable; dit kan een array zijn, een andere klasse, een string, enz ... Ik heb het ingesteld op een object omdat ik het leuk vind om het te gebruiken -> notatie in plaats van de notatie van de haakjes (array).

We kunnen het systeem nu testen. Als je het onthoudt in de router klasse, we voegen de klassenaam toe met de "controleur"naamruimte. Doe open composer.json voeg het volgende direct toe na de psr-0-definitie voor de Nettuts namespace:

"name": "nettuts / slim_advanced", "require": "slim / slim": "2.2.0", "slim / extras": "*", "takje / takje": "*", " autoload ": " psr-0 ": " Nettuts ":" vendor / "," Controller ":" ./ "

Dan, zoals eerder, dump de autoloader:

componist dump-autoload

Als we alleen het basispad instellen op de hoofdmap, dan is de naamruimte controleur zal toewijzen aan een map met de naam "controleur"in de hoofdmap van onze app. Dus maak die map:

mkdir Controller

Maak binnen deze map een nieuw bestand met de naam main.php. In het bestand moeten we de naamruimte declareren en een klasse maken die onze naam uitbreidt controleur basisklasse:

data-> boodschap;  public function test () echo "Testpagina"; 

Dit is niet ingewikkeld, maar laten we het met mate doen. In deze klasse definiëren we twee functies; hun namen doen er niet toe, want we zullen ze later aan routes toewijzen. Het is belangrijk om op te merken dat ik rechtstreeks toegang heb tot eigenschappen van de controller (dat wil zeggen het model) in de eerste functie en dat je in feite volledige toegang hebt tot alle opdrachten van Slim.

Laten we nu het echte openbare bestand maken. Maak een nieuwe map in de hoofdmap van uw project en noem deze openbaar. Zoals de naam al aangeeft, dit is waar alle openbare dingen zullen verblijven. Maak in deze map een bestand met de naam index.php en voer het volgende in:

 'Main: index @ get', '/ test' => 'Main: test @ get'); $ Router-> addRoutes ($ routes); $ Router-> run ();

We nemen de autoladerbibliotheek van Composer op en maken een nieuw exemplaar van onze router. Vervolgens definiëren we twee routes, voegen ze toe aan het routerobject en voeren het uit.

U moet ook mod_rewrite inschakelen in Apache (of het equivalent dat een andere webserver gebruikt). Om dit in te stellen, maakt u een bestand met de naam .htaccess binnen in de openbaar map en vul het met het volgende:

RewriteEngine On RewriteCond% REQUEST_FILENAME! -F RewriteRule ^ index.php [QSA, L]

Nu worden alle aanvragen voor deze map (die niet overeenkomen met een daadwerkelijk bestand) overgedragen aan index.php.

Navigeer in uw browser naar uw openbaar directory, en u zou een pagina moeten zien met "Hello World". Navigeren naar "/test", en je zou het bericht" Testpagina "moeten zien. Het is niet erg spannend, maar we hebben met succes alle logica-code verplaatst naar individuele controllers.


Ronde twee

Slim is geen CodeIgniter, het is geen Symfony en het is geen Laravel.

Dus we hebben basisfunctionaliteit, maar er zijn een paar ruige kanten. Laten we beginnen met de router.

Vanaf nu wordt een eenvoudig foutbericht weergegeven als een route niet bestaat. In een echte applicatie willen we dezelfde functionaliteit als het laden van een normale pagina. We willen profiteren van de mogelijkheid van Slim om weergaven te laden en de foutcode van het antwoord in te stellen.

Laten we een nieuwe klassenvariabele toevoegen die een optioneel pad bevat (net als de andere routes). Voeg aan het begin van het bestand de volgende regel toe direct na de definitie van het aanvraagobject:

beschermde $ errorHandler;

Laten we vervolgens een functie maken die een pad accepteert en hieraan een callback-functie toewijst. Dit is relatief eenvoudig omdat we deze functionaliteit al hebben geabstraheerd:

public function set404Handler ($ path) $ this-> errorHandler = $ this-> processCallback ($ path); 

Laten we nu de rennen commando om optioneel de callback uit te voeren in plaats van alleen de foutmelding weer te geven:

if ($ display404) if (is_callable ($ this-> errorHandler)) call_user_func ($ this-> errorHandler);  else echo "404 - route niet gevonden"; 

Open de controllerklasse. Hier kunt u de functionaliteit van Slim aanpassen aan uw eigen persoonlijke voorkeuren. Ik zou bijvoorbeeld de optie willen hebben om de bestandsextensie weg te laten bij het laden van views. Dus in plaats van schrijven $ This-> render ( "home.php");, Ik wil alleen schrijven: $ This-> render ( "thuis");. Laten we om dit te doen de weergavemethode overschrijven:

publieke functie renderen ($ naam, $ data = array (), $ status = null) if (strs ($ name, ".php") === false) $ name = $ name. ".Php";  parent :: render ($ name, $ data, $ status); 

We accepteren dezelfde parameters als de bovenliggende functie, maar we controleren of de bestandsextensie is opgegeven en voegen deze indien nodig toe. Na deze wijziging geven we het bestand door aan de hoofdmethode voor verwerking.

Dit is slechts een voorbeeld, maar we moeten hier andere wijzigingen aanbrengen in de render () methode. Als u bijvoorbeeld dezelfde kop- en voettekstpagina's op al uw documenten laadt, kunt u een functie toevoegen renderPage (). Deze functie laadt de doorzichtige weergave tussen de aanroepen om de reguliere kop- en voettekst te laden.

Laten we vervolgens eens kijken naar het laden van enkele weergaven. Maak in de hoofdmap van uw project een map met de naam "Keer bekeken"(de locatie en naam kunnen worden aangepast in de settings.php het dossier). Laten we gewoon twee weergaven met de naam maken test.php en error.php.

Binnen test.php, voeg het volgende toe:

titel

Dit is de name pagina!

En binnen de error.php bestand, voer dit in:

404

De route waarnaar u op zoek was, kon niet worden gevonden

Wijzig ook de Hoofd controller door de inhoudsopgave() functioneren als volgt:

public function index () $ this-> render ("test", array ("title" => $ this-> data-> message, "name" => "Home")); 

Hier geven we de testweergave die we zojuist hebben gemaakt en geven deze door om deze weer te geven. Laten we vervolgens een route met parameters proberen. Verander de test() functioneren als volgt:

openbare functietest ($ title) $ this-> render ("test", array ("title" => $ title, "name" => "Test")); 

Hier gaan we nog een stap verder door de titel van de pagina op te halen uit de URI zelf. Last, but not least, laten we een functie toevoegen voor de 404-pagina:

public function notFound () $ this-> render ('error', array (), 404); 

Wij gebruiken de render () functie's derde optionele parameter, die de HTTP-statuscode van het antwoord instelt.

Onze laatste bewerking is binnen index.php om onze nieuwe routes op te nemen:

$ routes = array ('/' => ", '/ test /: title' => 'Main: test @ get'); $ router-> addRoutes ($ routes); $ router-> set404Handler (" Main: notFound "); $ router-> run ();

U zou nu naar de drie routes kunnen navigeren en hun respectievelijke weergaven kunnen bekijken.


Conclusie

Met alles wat we hebben bereikt, hebt u zeker een paar vragen over waarom Slim deze aanpassingen niet al aanbiedt. Ze lijken logisch, ze wijken niet te ver af van de implementatie van Slim en dat is heel logisch. Josh Lockhart (Slim's maker) deed het het beste:

"Slim is geen CodeIgniter, het is geen Symfony, en het is geen Laravel. Slim is Slim. Het is gebouwd om licht en leuk te zijn, terwijl het toch in staat is ongeveer 80% van de meest voorkomende problemen op te lossen. gevallen, het is erop gericht eenvoudig te zijn en een gemakkelijk te lezen codebase te hebben. "

Soms raken ontwikkelaars zo verstrikt in gekke scenario's dat we vergeten wat echt belangrijk is: de code. Mods, zoals die in deze tutorial, zijn alleen mogelijk vanwege de eenvoud en breedsprakigheid van de code. Dus ja, er kunnen enkele edge-cases zijn die speciale aandacht vereisen, maar je krijgt een actieve community, die naar mijn mening zwaar opweegt tegen de kosten.

Ik hoop dat je dit artikel leuk vond. Als u vragen of opmerkingen heeft, laat dan hieronder een bericht achter. Je kunt ook contact met me opnemen via IRC-kanaal op Freenode op de #nettuts kanaal.