Метод контролера доступу від іншого контролера Laravel 5


162

У мене два контролери SubmitPerformanceControllerі PrintReportController.

У PrintReportControllerмене є метод, який називається getPrintReport.

Як отримати доступ до цього методу SubmitPerformanceController?

Відповіді:


364

Ви можете отримати доступ до свого методу контролера так:

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

Це буде працювати, але це погано з точки зору організації коду (не забудьте використовувати правильний простір імен для свого PrintReportController)

Ви можете продовжити, PrintReportControllerтак SubmitPerformanceControllerце успадкує цей метод

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

Але це також успадкує всі інші методи від PrintReportController.

Найкращим підходом буде створити trait(наприклад, в app/Traits), реалізувати логіку там і сказати своїм контролерам використовувати його:

trait PrintReport {

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

Скажіть своїм контролерам використовувати цю ознаку:

class PrintReportController extends Controller {
     use PrintReport;
}

class SubmitPerformanceController extends Controller {
     use PrintReport;
}

Обидва рішення SubmitPerformanceControllerмають getPrintReportметод, щоб ви могли викликати його $this->getPrintReport();зсередини контролера або безпосередньо як маршрут (якщо ви відобразили його у вікні routes.php)

Більше про ознаки ви можете прочитати тут .


10
де слід зберігати файл, включаючи ознаку?
Brainmaniac

24
app('App\Http\Controllers\PrintReportController')->getPrintReport();може трансформуватися в app(PrintReportController::class')->getPrintReport(). Чисте рішення для мене.
Вінсент Деко

Де зберігається файл ознаки?
Eric McWinNEr

@EricMcWinNEr Його можна зберігати в будь-якому місці, як, наприклад, App \ Traits. Але обов’язково використовуйте відповідну область імен у цій рисі.
Трибунал

1
Просто невеликий приклад використання риси в Laravel: develodesign.co.uk/news / ...
Erenor Паз

48

Якщо вам потрібен метод в іншому контролері, це означає, що вам потрібно його абстрагувати та зробити його багаторазовим. Перемістіть цю реалізацію в клас обслуговування (ReportingService або щось подібне) та введіть її у свої контролери.

Приклад:

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

Зробіть те саме для інших контролерів, де вам потрібна ця реалізація. Досягнення методів контролера від інших контролерів - це кодовий запах.


Де ви зберегли б цей клас з точки зору структури проекту?
Амітай

1
Або Servicesпапка, якщо проект не великий, або папка функцій, яка називається, Reportingякщо це більший проект і використовує Folders By Featureструктуру.
Ruffles

Ви посилаєтесь на Постачальника послуг (клас обслуговування), наприклад, тут laravel.com/docs/5.7/providers або контейнер служби, як тут, на laravel.com/docs/5.7/container ?
Баспа

1
@Baspa Ні, нормальний PHP клас.
Складки

27

Викликати контролер з іншого контролера не рекомендується, однак якщо з будь-якої причини вам це потрібно зробити, ви можете зробити це:

Сумісний метод Laravel 5

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

Примітка. Це не буде оновити URL-адресу сторінки.

Краще зателефонувати замість маршруту та дозволити йому викликати контролер.

return \Redirect::route('route-name-here');

2
Чому це не рекомендується?
brunouno

Це має бути головна відповідь.
Джастін Вінсент

13

Ви не повинні. Це анти-шаблон. Якщо у вас в одному контролері є метод, до якого потрібно отримати доступ в іншому контролері, то це знак, що вам потрібно перефактурувати.

Подумайте про перекомпонування методу до класу обслуговування, який ви можете інстанціювати в декількох контролерах. Тож якщо вам потрібно запропонувати звіти про друк для декількох моделей, ви можете зробити щось подібне:

class ExampleController extends Controller
{
    public function printReport()
    {
        $report = new PrintReport($itemToReportOn);
        return $report->render();
    }
}

10
\App::call('App\Http\Controllers\MyController@getFoo')

11
Незважаючи на те, що ваша відповідь може бути правильною, непогано було б трохи подовжити її і дати ще трохи пояснень.
скана

9

Перш за все, запитувати метод контролера від іншого контролера - EVIL. Це спричинить багато прихованих проблем у життєвому циклі Ларавеля.

У будь-якому випадку для цього існує багато рішень. Ви можете вибрати один із цих різних способів.

Випадок 1) Якщо ви хочете зателефонувати на основі класів

Шлях 1) Простий спосіб

Але ви не можете додати жодних параметрів або автентифікації таким чином.

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

Спосіб 2) Розділіть логіку контролера на сервіси.

Ви можете додати будь-які параметри і щось із цим. Найкраще рішення для вашого життя програмування. Ви можете зробити Repositoryзамість цього 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 ...
    }
}

Випадок 2) Якщо ви хочете зателефонувати на основі маршрутів

Спосіб 1) Використовуйте MakesHttpRequestsознаку, яка використовується в тестуванні блоку програми.

Я рекомендую це, якщо у вас є спеціальна причина для створення цього проксі, ви можете використовувати будь-які параметри та власні заголовки . Також це буде внутрішній запит у laravel. (Підроблений запит HTTP) Більш детальну інформацію про callметод ви можете переглянути тут .

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

Однак це також не є «хорошим» рішенням.

Спосіб 2) Використовуйте клієнт guzzlehttp

Це найстрашніше рішення, на мою думку. Ви також можете використовувати будь-які параметри та власні заголовки . Але це буде створення зовнішнього додаткового http запиту. Отже, HTTP-веб-сервер повинен працювати.

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

Нарешті я використовую шлях 1 випадку 2. Мені потрібні параметри та


1
Шлях 2 не повинен записуватися туди, ви ніколи не хочете самостійно http-запитувати, навіть у поганій структурі коду.
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();   
    }
   }

7
Відредагуйте додаткові відомості. Відповіді, що містять лише коди, та "спробувати це" не рекомендують, оскільки вони не містять вмісту, який можна шукати, і не пояснюють, чому хтось повинен "спробувати це".
абарізон

2

Ви можете використовувати статичний метод у PrintReportController, а потім викликати його з SubmitPerformanceController так;

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

    }

}

2

Цей підхід також працює з тією ж ієрархією файлів Controller:

$printReport = new PrintReportController;

$prinReport->getPrintReport();

Мені подобається такий підхід порівняно з App :: make, тому що натяк на тип doc-блоків все ще працює в phpStorm таким чином.
Флоріс

1

Тут ця ознака повністю імітує працюючий контролер ларавел-маршрутизатором (включаючи підтримку середнього рівня та введення залежності). Тестується лише з версією 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();
    }
}

Потім просто додайте його до свого класу та запустіть контролер. Зауважте, що введення залежності буде призначено вашому поточному маршруту.

class CustomController extends Controller {
    use RunsAnotherController;

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

        return $this->runController($controller, 'doSomething');
    }
}

Враховуйте, що робити app()->make(......)дорівнює app(......)тому, що коротше.
matiaslauriti

1

Ви можете отримати доступ до контролера, інстанціюючи його та викликаючи doAction: (поставити use Illuminate\Support\Facades\App;перед декларацією класу контролера)

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

Також зауважте, що виконуючи це, ви не будете виконувати жодну з середніх програм, заявлених на цьому контролері.


-2

Пізня відповідь, але я це шукав вже десь. Це зараз можливо дуже простим способом.

Без параметрів

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

З параметрами

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

Документи: https://laravel.com/docs/5.6/responses#redirecting-controller-action

Ще в 5.0 вона потребувала всього шляху, тепер це набагато простіше.


3
Первісне питання полягав у тому, як отримати доступ до методу контролера від іншого контролера, а не як перенаправити на дії інших конкретних методів, тому ваше рішення не пов'язане з питанням.
matiaslauriti
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.