Greifen Sie von einem anderen Controller in Laravel 5 auf die Controller-Methode zu

160

Ich habe zwei Controller SubmitPerformanceControllerund PrintReportController.

In habe PrintReportControllerich eine Methode aufgerufen getPrintReport.

Wie kann ich in auf diese Methode zugreifen SubmitPerformanceController?

Iftakharul Alam
quelle

Antworten:

363

Sie können wie folgt auf Ihre Controller-Methode zugreifen:

app('App\Http\Controllers\PrintReportController')->getPrintReport();

Dies wird funktionieren, ist jedoch in Bezug auf die Code-Organisation schlecht (denken Sie daran, den richtigen Namespace für Ihre zu verwenden PrintReportController).

Sie können das erweitern PrintReportController, SubmitPerformanceControllerum diese Methode zu erben

class SubmitPerformanceController extends PrintReportController {
     // ....
}

Dies erbt aber auch alle anderen Methoden von PrintReportController.

Der beste Ansatz besteht darin, ein trait(z. B. in app/Traits) zu erstellen , die Logik dort zu implementieren und Ihre Controller anzuweisen, sie zu verwenden:

trait PrintReport {

    public function getPrintReport() {
        // .....
    }
}

Sagen Sie Ihren Controllern, dass sie dieses Merkmal verwenden sollen:

class PrintReportController extends Controller {
     use PrintReport;
}

class SubmitPerformanceController extends Controller {
     use PrintReport;
}

Beide Lösungen SubmitPerformanceControllerverfügen über eine getPrintReportMethode, mit der Sie sie $this->getPrintReport();innerhalb des Controllers oder direkt als Route aufrufen können (wenn Sie sie in der routes.php) zugeordnet haben.

Sie können mehr über Züge lesen hier .

Sh1d0w
quelle
10
Wo soll die Datei mit dem Merkmal gespeichert werden?
Brainmaniac
24
app('App\Http\Controllers\PrintReportController')->getPrintReport();kann umgewandelt werden app(PrintReportController::class')->getPrintReport(). Saubere Lösung für mich.
Vincent Decaux
Wo wird die Trait-Datei gespeichert?
Eric McWinNEr
@EricMcWinNEr Es kann an einem beliebigen Ort gespeichert werden, z. B. App \ Traits. Stellen Sie jedoch sicher, dass Sie den entsprechenden Namespace in diesem Merkmal verwenden.
Tribunal
1
Nur ein kleines Beispiel für die Verwendung von Merkmalen in Laravel: develodesign.co.uk/news/…
Erenor Paz
48

Wenn Sie diese Methode in einem anderen Controller benötigen, müssen Sie sie abstrahieren und wiederverwendbar machen. Verschieben Sie diese Implementierung in eine Serviceklasse (ReportingService oder ähnliches) und fügen Sie sie in Ihre Controller ein.

Beispiel:

class ReportingService
{
  public function getPrintReport()
  {
    // your implementation here.
  }
}
// don't forget to import ReportingService at the top (use Path\To\Class)
class SubmitPerformanceController extends Controller
{
  protected $reportingService;
  public function __construct(ReportingService $reportingService)
  {
     $this->reportingService = $reportingService;
  }

  public function reports() 
  {
    // call the method 
    $this->reportingService->getPrintReport();
    // rest of the code here
  }
}

Machen Sie dasselbe für die anderen Controller, auf denen Sie diese Implementierung benötigen. Das Erreichen von Controller-Methoden von anderen Controllern ist ein Code-Geruch.

Rüschen
quelle
Wo würden Sie diese Klasse in Bezug auf die Projektstruktur speichern?
Amitay
1
Entweder ein ServicesOrdner, wenn das Projekt nicht groß ist, oder ein Feature-Ordner, der aufgerufen wird, Reportingwenn es sich um ein größeres Projekt handelt und Folders By FeatureStruktur verwendet.
Rüschen
Beziehen Sie sich auf einen Service Provider (Serviceklasse) wie hier laravel.com/docs/5.7/providers oder einen Service Container wie hier laravel.com/docs/5.7/container ?
Baspa
1
@ Baspa Nein, eine normale PHP-Klasse.
Rüschen
27

Das Aufrufen eines Controllers von einem anderen Controller aus wird nicht empfohlen. Wenn Sie dies jedoch aus irgendeinem Grund tun müssen, können Sie Folgendes tun:

Laravel 5 kompatible Methode

return \App::call('bla\bla\ControllerName@functionName');

Hinweis: Dadurch wird die URL der Seite nicht aktualisiert.

Es ist besser, stattdessen die Route aufzurufen und den Controller anrufen zu lassen.

return \Redirect::route('route-name-here');
Mahmoud Zalt
quelle
2
Warum wird es nicht empfohlen?
Brunouno
Dies sollte die beste Antwort sein.
Justin Vincent
13

Das solltest du nicht. Es ist ein Anti-Muster. Wenn Sie in einem Controller eine Methode haben, auf die Sie in einem anderen Controller zugreifen müssen, ist dies ein Zeichen, das Sie neu faktorisieren müssen.

Ziehen Sie in Betracht, die Methode in eine Serviceklasse umzuwandeln, die Sie dann in mehreren Controllern instanziieren können. Wenn Sie also Druckberichte für mehrere Modelle anbieten müssen, können Sie Folgendes tun:

class ExampleController extends Controller
{
    public function printReport()
    {
        $report = new PrintReport($itemToReportOn);
        return $report->render();
    }
}
Martin Bean
quelle
10
\App::call('App\Http\Controllers\MyController@getFoo')
the_hasanov
quelle
11
Trotz der Tatsache, dass Ihre Antwort richtig sein könnte, wäre es schön, sie ein wenig zu erweitern und weitere Erklärungen zu geben.
Scana
9

Zuallererst ist es böse, eine Methode eines Controllers von einem anderen Controller anzufordern. Dies wird viele versteckte Probleme in Laravels Lebenszyklus verursachen.

Dafür gibt es viele Lösungen. Sie können eine dieser verschiedenen Möglichkeiten auswählen.

Fall 1) Wenn Sie basierend auf Klassen anrufen möchten

Weg 1) Der einfache Weg

Auf diese Weise können Sie jedoch keine Parameter oder Authentifizierung hinzufügen .

app(\App\Http\Controllers\PrintReportContoller::class)->getPrintReport();

Weg 2) Teilen Sie die Controller-Logik in Dienste.

Sie können damit beliebige Parameter und etwas hinzufügen . Die beste Lösung für Ihr Programmierleben. Sie können Repositorystattdessen machen Service.

class PrintReportService
{
    ...
    public function getPrintReport() {
        return ...
    }
}

class PrintReportController extends Controller
{
    ...
    public function getPrintReport() {
        return (new PrintReportService)->getPrintReport();
    }
}

class SubmitPerformanceController
{
    ...
    public function getSomethingProxy() {
        ...
        $a = (new PrintReportService)->getPrintReport();
        ...
        return ...
    }
}

Fall 2) Wenn Sie basierend auf Routen anrufen möchten

Weg 1) Verwenden Sie das MakesHttpRequestsMerkmal, das beim Testen von Anwendungseinheiten verwendet wurde.

Ich empfehle dies, wenn Sie einen besonderen Grund für die Erstellung dieses Proxys haben. Sie können beliebige Parameter und benutzerdefinierte Header verwenden . Auch dies wird eine interne Anfrage in Laravel sein. (Gefälschte HTTP-Anfrage) Weitere Details zur callMethode finden Sie hier .

class SubmitPerformanceController extends \App\Http\Controllers\Controller
{
    use \Illuminate\Foundation\Testing\Concerns\MakesHttpRequests;

    protected $baseUrl = null;
    protected $app = null;

    function __construct()
    {
        // Require if you want to use MakesHttpRequests
        $this->baseUrl = request()->getSchemeAndHttpHost();
        $this->app     = app();
    }

    public function getSomethingProxy() {
        ...
        $a = $this->call('GET', '/printer/report')->getContent();
        ...
        return ...
    }
}

Dies ist jedoch auch keine „gute“ Lösung.

Weg 2) Verwenden Sie den guzzlehttp-Client

Dies ist die schrecklichste Lösung, die ich denke. Sie können auch beliebige Parameter und benutzerdefinierte Header verwenden . Dies würde jedoch eine externe zusätzliche http-Anfrage stellen. Der HTTP-Webserver muss also ausgeführt werden.

$client = new Client([
    'base_uri' => request()->getSchemeAndhttpHost(),
    'headers' => request()->header()
]);
$a = $client->get('/performance/submit')->getBody()->getContents()

Schließlich verwende ich Weg 1 von Fall 2. Ich benötige Parameter und

Kargnas
quelle
1
Weg 2 sollte dort nicht aufgeschrieben werden, Sie möchten sich selbst niemals http-anfordern, auch nicht in einer schlechten Codestruktur.
Sw0ut
5
namespace App\Http\Controllers;

//call the controller you want to use its methods
use App\Http\Controllers\AdminController;

use Illuminate\Http\Request;

use App\Http\Requests;

class MealController extends Controller
   {
      public function try_call( AdminController $admin){
         return $admin->index();   
    }
   }
Ahmed Mahmoud
quelle
7
Bitte bearbeiten Sie mit weiteren Informationen. Nur-Code- und "Versuch dies" -Antworten werden nicht empfohlen, da sie keinen durchsuchbaren Inhalt enthalten und nicht erklären, warum jemand "dies versuchen" sollte.
Abarisone
2

Sie können eine statische Methode in PrintReportController verwenden und sie dann wie folgt vom SubmitPerformanceController aus aufrufen.

namespace App\Http\Controllers;

class PrintReportController extends Controller
{

    public static function getPrintReport()
    {
      return "Printing report";
    }


}



namespace App\Http\Controllers;

use App\Http\Controllers\PrintReportController;

class SubmitPerformanceController extends Controller
{


    public function index()
    {

     echo PrintReportController::getPrintReport();

    }

}
TheLastCodeBender
quelle
2

Dieser Ansatz funktioniert auch mit derselben Hierarchie von Controller-Dateien:

$printReport = new PrintReportController;

$prinReport->getPrintReport();
Jay Marz
quelle
Ich mag diesen Ansatz im Vergleich zu App :: make one, da die Typhinweise der doc-Blöcke in phpStorm auf diese Weise immer noch funktionieren.
Floris
1

Hier emuliert das Merkmal die laufende Steuerung durch den Laravel-Router vollständig (einschließlich der Unterstützung von Middleware und der Abhängigkeitsinjektion). Nur mit Version 5.4 getestet

<?php

namespace App\Traits;

use Illuminate\Pipeline\Pipeline;
use Illuminate\Routing\ControllerDispatcher;
use Illuminate\Routing\MiddlewareNameResolver;
use Illuminate\Routing\SortedMiddleware;

trait RunsAnotherController
{
    public function runController($controller, $method = 'index')
    {
        $middleware = $this->gatherControllerMiddleware($controller, $method);

        $middleware = $this->sortMiddleware($middleware);

        return $response = (new Pipeline(app()))
            ->send(request())
            ->through($middleware)
            ->then(function ($request) use ($controller, $method) {
                return app('router')->prepareResponse(
                    $request, (new ControllerDispatcher(app()))->dispatch(
                    app('router')->current(), $controller, $method
                )
                );
            });
    }

    protected function gatherControllerMiddleware($controller, $method)
    {
        return collect($this->controllerMidlleware($controller, $method))->map(function ($name) {
            return (array)MiddlewareNameResolver::resolve($name, app('router')->getMiddleware(), app('router')->getMiddlewareGroups());
        })->flatten();
    }

    protected function controllerMidlleware($controller, $method)
    {
        return ControllerDispatcher::getMiddleware(
            $controller, $method
        );
    }

    protected function sortMiddleware($middleware)
    {
        return (new SortedMiddleware(app('router')->middlewarePriority, $middleware))->all();
    }
}

Fügen Sie es dann einfach Ihrer Klasse hinzu und führen Sie den Controller aus. Beachten Sie, dass die Abhängigkeitsinjektion Ihrer aktuellen Route zugewiesen wird.

class CustomController extends Controller {
    use RunsAnotherController;

    public function someAction() 
    {
        $controller = app()->make('App\Http\Controllers\AnotherController');

        return $this->runController($controller, 'doSomething');
    }
}
Anton
quelle
Berücksichtigen Sie, dass das Tun app()->make(......)gleich ist, app(......)also kürzer.
Matiaslauriti
1

Sie können auf den Controller zugreifen, indem Sie ihn instanziieren und doAction aufrufen: ( use Illuminate\Support\Facades\App;vor die Controller-Klassendeklaration setzen)

$controller = App::make('\App\Http\Controllers\YouControllerName');
$data = $controller->callAction('controller_method', $parameters);

Beachten Sie auch, dass Sie auf diese Weise keine der auf diesem Controller deklarierten Middlewares ausführen.

Abhijeet Navgire
quelle
-2

Späte Antwort, aber ich habe seit einiger Zeit danach gesucht. Dies ist jetzt auf sehr einfache Weise möglich.

Ohne Parameter

return redirect()->action('HomeController@index');

Mit Parametern

return redirect()->action('UserController@profile', ['id' => 1]);

Dokumente: https://laravel.com/docs/5.6/responses#redirecting-controller-actions

In 5.0 war der gesamte Pfad erforderlich, jetzt ist es viel einfacher.

Vipul Nandan
quelle
3
Die ursprüngliche Frage lautete, wie Sie von einem anderen Controller auf die Methode eines Controllers zugreifen und nicht auf die Aktion einer anderen bestimmten Methode umleiten, sodass Ihre Lösung nicht mit der Frage zusammenhängt.
Matiaslauriti