v13.35.0 en gaat dieper in op het registratievoorbeeld met commands() uit Laravel-packages ontwikkelen. Voor de implementatie van het genereren van bestanden zie je GeneratorCommand, en voor deelname aan optimalisatietaken Integratie in optimize.
Expliciet registreren vanuit de provider
Commands in een package registreer je met hun klassenaam in deboot()-methode van de service provider. De automatische detectie van de map app/Console/Commands van de applicatie en de registratie van klassen die een package aanbiedt, zijn twee afzonderlijke ingangen.
runningInConsole() bepaalt of je in een console-omgeving zit, niet of een bepaald command wordt uitgevoerd. Wil je ook ondersteunen dat Artisan::call() vanuit een HTTP-request wordt aangeroepen, houd er dan rekening mee dat het command door deze voorwaarde mogelijk niet wordt geregistreerd, en leg het gebruikscontract vast, eventueel met onvoorwaardelijke registratie. Hoe je commands vanuit code uitvoert, lees je in Artisan.
commands() voert het command niet direct uit
ServiceProvider::commands() registreert bij Illuminate\Console\Application::starting() een closure die de ontvangen klassenamen gebruikt. Op dat moment wordt handle() van het command niet aangeroepen.
In de gebruikelijke Console Kernel worden de geregistreerde opstartcallbacks later uitgevoerd, wanneer de Artisan Application wordt aangemaakt. Dan geeft resolveCommands() elke klasse door aan resolve() en worden de commands geregistreerd.
Het belangrijkste punt is dat het instantiëren van een command niet per se samenvalt met het uitvoeren ervan. Voor gewone klassen die niet de hieronder beschreven speciale route voor uitgestelde registratie volgen, roept resolve() de make() van de container aan, waardoor de constructor en de bijbehorende afhankelijkheden al bij de registratie worden geresolved.
Daardoor kan alleen al het uitvoeren van php artisan list of een ander command de constructor van het package-command laten draaien. commands() in boot() plaatsen garandeert op zichzelf niet dat het instantiëren wordt uitgesteld tot de uitvoering.
Afhankelijkheden voor de uitvoering injecteren in handle()
Als voorbeeld maken we een command dat alleen toont of de packageconfiguratie is in- of uitgeschakeld. Net als in de officiële Artisan-documentatie van Laravel 13 definiëren we naam en beschrijving met de attributenSignature en Description.
handle(). Omdat Command::execute() in het uitvoeringspad van het command de call() van de container gebruikt, worden getypeerde afhankelijkheden geïnjecteerd.
Ook een externe API-client of synchronisatieservice kun je op dezelfde plek injecteren als die alleen tijdens de uitvoering nodig is. Heeft het aanmaken van de service zelf of de boot() van een andere provider echter bijwerkingen, dan worden die niet automatisch voorkomen.
Definieer je een eigen constructor, roep dan ook
parent::__construct() aan. De parent-constructor van Laravel stelt het command namelijk samen op basis van attributen, signature, description enzovoort.
Signature en uitgestelde registratie zijn aparte mechanismen
De onderzochteApplication::resolve() bevat een vertakking die de name van het Symfony-attribuut AsCommand leest en de koppeling tussen naam en klasse in een command map opslaat. In deze vertakking wordt de make() van de container niet direct aangeroepen. De Console Kernel roept later setContainerCommandLoader() aan om een loader in te stellen die deze map gebruikt.
Het Laravel-attribuut Signature uit het voorbeeld hierboven staat daarentegen los van deze controle op AsCommand. Alleen Signature toevoegen betekent niet dat de klasse via deze route voor uitgestelde registratie loopt.
Beschouw dit verschil als aandachtspunt bij het lezen van de interne implementatie, en beperk eerst de aanmaakkosten en bijwerkingen met de officiële manier om commands te definiëren en met dependency injection in handle(). Ook met een uitgestelde loader kan informatie over het command nodig zijn voor bijvoorbeeld het overzicht of de help, dus ontwerp niet vanuit de aanname dat het command “nooit wordt aangemaakt behalve bij het uitvoeren ervan”.
De registratie van commands niet uitstellen
Het uitgesteld laden van servicebindings en Artisan laten weten dat een command bestaat, zijn twee verschillende dingen. Als een providerDeferrableProvider implementeert, wordt zijn boot() mogelijk pas uitgevoerd wanneer de betreffende service nodig is.
Staat de registratie van commands alleen in die boot() en wordt de provider niet geladen voordat Artisan opstart, dan wordt ook de opstartcallback die het bestaan van het command meldt niet geregistreerd. Ook als een service die je naar de dependency injection van handle() hebt verplaatst de uitgestelde provider laadt, lost dat het probleem niet op dat optreedt voordat Artisan het command vindt.
Om de beschikbaarheid van commands te garanderen, kun je de registratie in een direct geladen provider plaatsen en alleen de bindings van zware services afsplitsen naar een aparte uitgestelde provider. De precieze vereisten lees je in Uitgestelde service providers.
Waar je op let bij updates
De naam van het command, de argumenten, opties, uitvoer en exitcode vormen een publieke interface die door shellscripts en CI van gebruikers wordt gebruikt. Wijzig ze niet zo licht als de interne implementatie van een klasse, maar controleer de compatibiliteit en de migratieprocedure.
Voor de
StatusCommand hierboven kun je in een testomgeving die de provider laadt de uitvoer en exitcode als volgt controleren. Voor de testinfrastructuur zie je Laravel-packages testen met Orchestra Testbench, en voor details over assertions Consoletests.
Geraadpleegde primaire bronnen
- Officiële Laravel-documentatie: commands van een package registreren
- Officiële Laravel-documentatie: structuur van commands en dependency injection
- ServiceProvider: commands()
- Console Application: starting(), bootstrap(), resolve()
- Console Kernel: getArtisan() en registratie van de loader
- Command: constructor en execute()
- Officiële tests: resolven van commands en aliasregistratie via AsCommand