Je construis un CMS avec Laravel 4 et j'ai un contrôleur d'administration de base pour les pages d'administration qui ressemble à ceci:
class AdminController extends BaseController {
public function __construct(UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
{
$this->auth = $auth;
$this->user = $this->auth->adminLoggedIn();
$this->message = $message;
$this->module = $module;
}
}
J'utilise le conteneur IOC de Laravel pour injecter les dépendances de classe dans le constructeur. J'ai ensuite diverses classes de contrôleurs qui contrôlent les différents modules composant le CMS et chaque classe étend la classe admin. Par exemple:
class UsersController extends AdminController {
public function home()
{
if (!$this->user)
{
return Redirect::route('admin.login');
}
$messages = $this->message->getMessages();
return View::make('users::home', compact('messages'));
}
}
Maintenant, cela fonctionne parfaitement bien que mon problème, qui est moins un problème et plus d'un problème d'efficacité, se produit lorsque j'ajoute un constructeur à la classe UsersController
. Par exemple:
class UsersController extends AdminController {
public function __construct(UsersManager $user)
{
$this->users = $users;
}
public function home()
{
if (!$this->user)
{
return Redirect::route('admin.login');
}
$messages = $this->message->getMessages();
return View::make('users::home', compact('messages'));
}
}
Puisque la classe enfant a maintenant un constructeur, cela signifie que le constructeur du parent n'est pas appelé et que les éléments dont dépend la classe enfant, tels que this->user
, ne sont plus valides, ce qui provoque des erreurs. Je peux appeler la fonction de construction du contrôleur administrateur via parent::__construct()
, mais comme je dois lui transmettre les dépendances de classe, je dois définir ces dépendances dans le constructeur enfant, ce qui donne un résultat ressemblant à ceci:
class UsersController extends AdminController {
public function __construct(UsersManager $user, UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
{
parent::__construct($auth, $messages, $module);
$this->users = $users;
}
// Same as before
}
Maintenant, cela fonctionne bien en termes de fonctionnalité; Cependant, il ne me semble pas très efficace d'inclure les dépendances du parent dans chaque classe enfant ayant un constructeur. Il semble aussi assez désordonné. Est-ce que Laravel fournit un moyen de contourner ce problème ou PHP prend-il en charge un moyen d'appeler à la fois les constructeurs parent et enfant sans avoir à appeler parent::__construct()
à partir de l'enfant?
Je sais que la question est longue, car ce n’est pas un problème, c’est une question d’efficacité, mais j’apprécie toutes les idées et/ou solutions.
Merci d'avance!
Je sais que c’est une très vieille question, mais je viens de terminer une question similaire sur mon projet actuel et j’ai compris le problème.
La question fondamentale sous-jacente est la suivante:
Si j'étends une classe parent qui a un constructeur. Ce constructeur a injecté des dépendances et toutes ses dépendances sont déjà documentées dans le parent lui-même. Pourquoi dois-je inclure à nouveau les dépendances du parent dans ma classe enfant ?
J'ai rencontré le même problème.
Ma classe parent nécessite 3 dépendances différentes. Ils sont injectés via le constructeur:
<?php namespace CodeShare\Parser;
use CodeShare\Node\NodeRepositoryInterface as Node;
use CodeShare\Template\TemplateRepositoryInterface as Template;
use CodeShare\Placeholder\PlaceholderRepositoryInterface as Placeholder;
abstract class BaseParser {
protected $node;
protected $template;
protected $placeholder;
public function __construct(Node $node, Template $template, Placeholder $placeholder){
$this->node = $node;
$this->template = $template;
$this->placeholder = $placeholder;
}
La classe est une classe abstraite, donc je ne peux jamais instancier elle-même. Lorsque j'étends le cours, j'ai toujours besoin d'inclure toutes ces dépendances et leurs références use
dans le constructeur de l'enfant:
<?php namespace CodeShare\Parser;
// Using these so that I can pass them into the parent constructor
use CodeShare\Node\NodeRepositoryInterface as Node;
use CodeShare\Template\TemplateRepositoryInterface as Template;
use CodeShare\Placeholder\PlaceholderRepositoryInterface as Placeholder;
use CodeShare\Parser\BaseParser;
// child class dependencies
use CodeShare\Parser\PlaceholderExtractionService as Extractor;
use CodeShare\Parser\TemplateFillerService as TemplateFiller;
class ParserService extends BaseParser implements ParserServiceInterface {
protected $extractor;
protected $templateFiller;
public function __construct(Node $node, Template $template, Placeholder $placeholder, Extractor $extractor, TemplateFiller $templateFiller){
$this->extractor = $extractor;
$this->templateFiller = $templateFiller;
parent::__construct($node, $template, $placeholder);
}
L'inclusion des instructions use
pour les 3 dépendances parent dans chaque classe semblait être du code en double puisqu'elles sont déjà définies dans le constructeur parent. Mon idée était de supprimer les instructions parent use
car elles devront toujours être définies dans la classe enfant qui étend le parent.
Ce que j'ai compris, c'est que l'inclusion de la variable use
pour les dépendances dans la classe parent et l'inclusion des noms de classe dans le constructeur du parent ne sont UNIQUEMENT requises pour l'indication de type dans le parent.
Si vous supprimez les instructions use
du parent et le nom de classe d'indices de type du constructeur de parents, vous obtenez:
<?php namespace CodeShare\Parser;
// use statements removed
abstract class BaseParser {
protected $node;
protected $template;
protected $placeholder;
// type hinting removed for the node, template, and placeholder classes
public function __construct($node, $template, $placeholder){
$this->node = $node;
$this->template = $template;
$this->placeholder = $placeholder;
}
Sans les instructions use
et l'indication de type du parent, il ne peut plus garantir le type de classe transmis à son constructeur car il n'a aucun moyen de le savoir. Vous pouvez construire à partir de votre classe enfant avec n'importe quoi et le parent l'acceptera.
Cela ressemble à une double saisie de code, mais dans votre cas, vous ne construisez pas avec les dépendances définies dans le parent, vous vérifiez que l'enfant envoie les types corrects.
Il y a un moyen .._____ lorsque BaseController résout automatiquement ses dépendances.
use Illuminate\Routing\Controller;
use Illuminate\Foundation\Application;
// Dependencies
use Illuminate\Auth\AuthManager;
use Prologue\Alerts\AlertsMessageBag;
class BaseController extends Controller {
protected $authManager;
protected $alerts;
public function __construct(
// Required for resolving
Application $app,
// Dependencies
AuthManager $authManager = null,
AlertsMessageBag $alerts = null
)
{
static $dependencies;
// Get parameters
if ($dependencies === null)
{
$reflector = new \ReflectionClass(__CLASS__);
$constructor = $reflector->getConstructor()
$dependencies = $constructor->getParameters();
}
foreach ($dependencies as $dependency)
{
// Process only omitted optional parameters
if (${$dependency->name} === null)
{
// Assign variable
${$dependency->name} = $app->make($dependency->getClass()->name);
}
}
$this->authManager = $authManager;
$this->alerts = $alerts;
// Test it
dd($authManager);
}
}
Donc, dans le contrôleur enfant, vous ne transmettez que l'instance d'application:
class MyController extends BaseController {
public function __construct(
// Class dependencies resolved in BaseController
//..
// Application
Application $app
)
{
// Logic here
//..
// Invoke parent
parent::__construct($app);
}
}
Bien sûr, nous pourrions utiliser Facade pour l'application
Il n'y a pas de solution parfaite et il est important de comprendre que ce n'est pas un problème avec Laravel lui-même.
Pour gérer cela, vous pouvez faire l'une des trois choses suivantes:
Transmettez les dépendances nécessaires au parent (quel était votre problème)
// Parent
public function __construct(UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
{
$this->auth = $auth;
$this->user = $this->auth->adminLoggedIn();
$this->message = $message;
$this->module = $module;
}
// Child
public function __construct(UsersManager $user, UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
{
$this->users = $users;
parent::__construct($auth, $message, $module);
}
Résoudre automatiquement les dépendances dans la construction parente comme indiqué par @piotr_czdans sa réponse
Créez les occurrences dans la construction parent au lieu de les transmettre en tant que paramètres (pour ne pas utiliser l'injection de dépendance):
// Parent
public function __construct()
{
$this->auth = App::make('UserAuthInterface');
$this->user = $this->auth->adminLoggedIn();
$this->message = App::make('MessagesInterface');
$this->module = App::make('ModuleManagerInterface');
}
// Child
public function __construct(UsersManager $user)
{
$this->users = $users;
parent::__construct();
}
Si vous voulez tester vos cours, la troisième solution sera plus difficile à tester. Je ne sais pas si vous pouvez vous moquer des classes en utilisant la deuxième solution, mais vous vous en moquez en utilisant la première solution.
J'ai rencontré le même problème lors de l'extension de mon contrôleur de base.
J'ai opté pour une approche différente des autres solutions présentées ici. Plutôt que de compter sur l'injection de dépendance, j'utilise app () -> make () dans le constructeur des parents.
class Controller
{
public function __construct()
{
$images = app()->make(Images::class);
}
}
Cette approche plus simple peut comporter des inconvénients - rendre éventuellement le code moins vérifiable.
Vous devez transmettre les dépendances au constructeur parent pour qu'elles soient disponibles dans l'enfant. Il n'y a aucun moyen d'injecter les dépendances sur la construction parent lorsque vous l'instanciez via l'enfant.