Metoda kontrolera dostępu z innego kontrolera w Laravel 5

162

Mam dwa kontrolery SubmitPerformanceControlleri PrintReportController.

W PrintReportControllermam metodę o nazwie getPrintReport.

Jak uzyskać dostęp do tej metody w SubmitPerformanceController?

Iftakharul Alam
źródło

Odpowiedzi:

364

Możesz uzyskać dostęp do metody kontrolera w następujący sposób:

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

To zadziała, ale jest złe pod względem organizacji kodu (pamiętaj, aby użyć odpowiedniej przestrzeni nazw PrintReportController)

Możesz rozszerzyć, PrintReportControllerwięc SubmitPerformanceControllerbędzie dziedziczyć tę metodę

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

Ale to również odziedziczy wszystkie inne metody z PrintReportController.

Najlepszym podejściem będzie utworzenie trait(np. In app/Traits), zaimplementowanie tam logiki i poinformowanie kontrolerów, aby z niej korzystali:

trait PrintReport {

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

Powiedz swoim kontrolerom, aby użyli tej cechy:

class PrintReportController extends Controller {
     use PrintReport;
}

class SubmitPerformanceController extends Controller {
     use PrintReport;
}

Oba rozwiązania sprawiają, SubmitPerformanceControllerże getPrintReportmetoda ma , więc możesz wywołać ją z $this->getPrintReport();poziomu kontrolera lub bezpośrednio jako trasę (jeśli zmapowałeś ją w routes.php)

Możesz przeczytać więcej o cechach tutaj .

Sh1d0w
źródło
10
gdzie należy zapisać plik zawierający cechę?
Brainmaniac
24
app('App\Http\Controllers\PrintReportController')->getPrintReport();można przekształcić w app(PrintReportController::class')->getPrintReport(). Czyste rozwiązanie dla mnie.
Vincent Decaux
Gdzie jest przechowywany plik cech?
Eric McWinNEr
@EricMcWinNEr Może być przechowywany w dowolnym miejscu, na przykład przypuśćmy, że App \ Traits. Ale upewnij się, że używasz odpowiedniej przestrzeni nazw w tej cesze.
Trybunał
1
Tylko mały przykład wykorzystania cech w Laravel: develodesign.co.uk/news/ ...
Erenor Paz
48

Jeśli potrzebujesz tej metody w innym kontrolerze, oznacza to, że musisz ją wyodrębnić i umożliwić jej ponowne użycie. Przenieś tę implementację do klasy usług (ReportingService lub podobnej) i wstrzyknij ją do kontrolerów.

Przykład:

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
  }
}

Zrób to samo dla innych kontrolerów, w których potrzebujesz tej implementacji. Sięganie po metody kontrolera z innych kontrolerów to zapach kodu.

Falbany
źródło
Gdzie zapisałbyś tę klasę pod względem struktury projektu?
Amitay,
1
Albo Servicesfolder, jeśli projekt nie jest duży, albo folder funkcji, Reportingjeśli jest to większy projekt i używa Folders By Featurestruktury.
Ruffles
Czy masz na myśli usługodawcę (klasę usług), takiego jak tutaj laravel.com/docs/5.7/providers lub kontener usług, jak tutaj laravel.com/docs/5.7/container ?
Baspa
1
@Baspa Nie, normalna klasa PHP.
Ruffles
27

Wywołanie Kontrolera z innego Kontrolera nie jest zalecane, jednak jeśli z jakiegoś powodu musisz to zrobić, możesz to zrobić:

Metoda zgodna z Laravel 5

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

Uwaga: to nie zaktualizuje adresu URL strony.

Lepiej jest zamiast tego wywołać trasę i pozwolić jej wywołać kontroler.

return \Redirect::route('route-name-here');
Mahmoud Zalt
źródło
2
Dlaczego nie jest to zalecane?
brunouno
To powinna być najlepsza odpowiedź.
Justin Vincent
13

Nie powinieneś. To anty-wzór. Jeśli masz metodę w jednym kontrolerze, do której chcesz uzyskać dostęp w innym kontrolerze, to jest to znak, który musisz ponownie uwzględnić.

Rozważ ponowne uwzględnienie metody w klasie usługi, którą można następnie utworzyć na wielu kontrolerach. Jeśli więc chcesz oferować raporty drukowania dla wielu modeli, możesz zrobić coś takiego:

class ExampleController extends Controller
{
    public function printReport()
    {
        $report = new PrintReport($itemToReportOn);
        return $report->render();
    }
}
Martin Bean
źródło
10
\App::call('App\Http\Controllers\MyController@getFoo')
the_hasanov
źródło
11
Pomimo tego, że Twoja odpowiedź może być poprawna, dobrze byłoby ją nieco rozszerzyć i podać więcej wyjaśnień.
scana
9

Po pierwsze, żądanie metody kontrolera od innego kontrolera jest ZŁE. Spowoduje to wiele ukrytych problemów w cyklu życia Laravel.

Zresztą istnieje wiele rozwiązań, jak to zrobić. Możesz wybrać jeden z tych różnych sposobów.

Przypadek 1) Jeśli chcesz zadzwonić na podstawie klas

Sposób 1) Prosty sposób

Ale w ten sposób nie możesz dodać żadnych parametrów ani uwierzytelnienia .

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

Sposób 2) Podziel logikę kontrolera na usługi.

Możesz dodać dowolne parametry i coś z tym. Najlepsze rozwiązanie dla Twojego życia programistycznego. Możesz Repositoryzamiast tego zrobić 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 ...
    }
}

Przypadek 2) Jeśli chcesz dzwonić na podstawie tras

Sposób 1) Użyj MakesHttpRequestscechy używanej w testowaniu jednostkowym aplikacji.

Polecam to, jeśli masz szczególny powód do tworzenia tego serwera proxy, możesz użyć dowolnych parametrów i niestandardowych nagłówków . Także to będzie wewnętrzna prośba w laravel. (Fałszywe żądanie HTTP) Więcej informacji na temat callmetody można znaleźć tutaj .

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 ...
    }
}

Jednak nie jest to również „dobre” rozwiązanie.

Sposób 2) Użyj klienta guzzlehttp

Myślę, że to najstraszniejsze rozwiązanie. Możesz także użyć dowolnych parametrów i niestandardowych nagłówków . Ale oznaczałoby to wykonanie dodatkowego zewnętrznego żądania http. Dlatego serwer WWW HTTP musi być uruchomiony.

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

Wreszcie używam sposobu 1 przypadku 2. Potrzebuję parametrów i

kargnas
źródło
1
Sposób 2 nie powinien być zapisywany w tym miejscu, nigdy nie chcesz samodzielnie wysyłać żądań http, nawet w przypadku złej struktury kodu.
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
źródło
7
Edytuj, podając więcej informacji. Odpowiedzi zawierające tylko kod i „wypróbuj to” są odradzane, ponieważ nie zawierają treści, które można przeszukiwać, i nie wyjaśniają, dlaczego ktoś miałby to zrobić.
abarisone
2

Możesz użyć metody statycznej w PrintReportController, a następnie wywołać ją z SubmitPerformanceController w ten sposób;

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
źródło
2

To podejście działa również z tą samą hierarchią plików kontrolera:

$printReport = new PrintReportController;

$prinReport->getPrintReport();
Jay Marz
źródło
Podoba mi się to podejście w porównaniu do App :: make one, ponieważ podpowiedzi typu bloków dokumentu nadal działają w phpStorm w ten sposób.
Floris
1

Tutaj cecha w pełni emuluje działający kontroler przez router laravel (w tym wsparcie dla oprogramowania pośredniego i wstrzykiwania zależności). Testowany tylko z wersją 5.4

<?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();
    }
}

Następnie po prostu dodaj go do swojej klasy i uruchom kontroler. Należy pamiętać, że iniekcja zależności zostanie przypisana do bieżącej trasy.

class CustomController extends Controller {
    use RunsAnotherController;

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

        return $this->runController($controller, 'doSomething');
    }
}
Anton
źródło
Weź pod uwagę, że robienie tego app()->make(......)jest równe, app(......)więc jest krótsze.
matiaslauriti
1

Możesz uzyskać dostęp do kontrolera, tworząc jego instancję i wywołując doAction: (wstaw use Illuminate\Support\Facades\App;przed deklaracją klasy kontrolera)

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

Zwróć również uwagę, że robiąc to, nie wykonasz żadnego oprogramowania pośredniego zadeklarowanego na tym kontrolerze.

Abhijeet Navgire
źródło
-2

Późna odpowiedź, ale szukałem tego od jakiegoś czasu. Jest to teraz możliwe w bardzo prosty sposób.

Bez parametrów

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

Z parametrami

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

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

W wersji 5.0 wymagało to całej ścieżki, teraz jest znacznie prostsze.

Vipul Nandan
źródło
3
Pierwotne pytanie dotyczyło tego, jak uzyskać dostęp do metody kontrolera z innego kontrolera, a nie jak przekierować do akcji innej konkretnej metody, więc twoje rozwiązanie nie jest związane z pytaniem.
matiaslauriti