Add a Controller to a Laravel 5 Package - php

Lately, I've been creating only models for my L5 packages, but now I'd like to try and introduce a Controller as well. I've been following this but I always get the error "Class StatController does not exist".
Folder structure
/src
routes.php
/Controllers
StatController.php
StatController.php
<?php namespace Enchance\Sentrysetup\Controllers;
use App\Http\Controllers\Controller;
class StatController extends Controller {
public function index() {
return 'Moot.';
}
}
Service provider
public function register()
{
// Can't get this to work
include __DIR__.'/routes.php';
$this->app->make('Enchance\Sentrysetup\Controllers\StatController');
$this->app['sentrysetup'] = $this->app->share(function($app) {
return new Sentrysetup;
});
}
routes.php
Route::get('slap', 'StatController#index');
Does anyone have an alternate way of assigning a Controller to a L5 package?

You don't need to call $this->app->make() on your controllers. Controllers are resolved by Laravel's IoC automatically (meaning that Laravel creates / instantiates controllers automatically that are tied to routes).
Require your routes in the boot() method of your packages service provider:
public function boot()
{
require __DIR__.'/routes.php';
}
And inside your routes.php file:
Route::group(['namespace' => 'Enchance\Sentrysetup\Controllers'], function()
{
Route::get('slap', ['uses' => 'StatController#index']);
})
Also, just a tip. You should PascalCase your namespaces:
Enchance\SentrySetup\Controllers
Note the capital S in setup
The boot() method should be used to register your routes because when Laravel starts up, it goes through each service provider in your config/app.php file, creates them, calls the register() method (to insert/add any dependencies that the service provider "provides" into Laravel's singleton / IoC container).
Think of Laravel's container as simply a big key => value array. Where the "key" is the name of the dependency (such as config), and the "value" is a closure (function () {}) that is called to create the dependency:
// Exists as a property inside the Container:
$bindings = [
"config" => function () {
// Create the application configuration.
}
];
// Create the app configuration.
$config = $bindings['config']();
// Create the app configuration (Laravel).
$config = app()->make('config');
Once Laravel has registered each provider, it then goes through them again and calls the boot() method. This ensures that any dependencies that have been registered (inside of the register() method of all of your application service providers) is available in the boot() method, ready to be used.
The Service Provider Boot Method - Laravel Docs

Use this in controller function:
use Illuminate\Routing\Controller;
Like:
namespace Enchance\Sentrysetup\Controllers;
use Illuminate\Routing\Controller;
class StatController extends Controller {
public function index() {
return 'Moot.';
}
}
And in controller:
Route::group(['namespace' => 'Enchance\Sentrysetup\Controllers'], function()
{
Route::get('slap', 'StatController#index');
});
In boot function:
public function boot()
{
require __DIR__.'/routes.php';
}

Related

Laravel 5.4: Change DB according to sub domain in laravel

I am creating a SAAS application with a single laravel installation and multi tenant database.
So i have a main DB that is connected to laravel by default. the client can access the application through their sub-domain like: client1.example.com and client2.example.com
I am saving all info fo the client like domain and db name in the main db.
How can change the DB according to the subdomain
I tried with the following Code
in routes/web.php
Route::group(['domain' => '{account}'], function() {
Route::get('/', function($account) {
// This will return {account}, which you can pass along to what you'd like
return $account;
});
});
in app/Providers\RouteServiceProvieder.php
public function boot(Router $router)
{
parent::boot($router);
$router->bind('domain', function ($value) {
//$hst=$request->getHost();
$domain= Customer::where('sub_domain',$value)->first();
if ($domain) {
return $domain;
}
throw new Exception('error message');
});
}
Then i have created a ServiceProvider ConfigServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Request;
class ConfigServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register(Request $request)
{
$customer=$request->route('domain');
//$customer = Request::route('domain'); // there are multiple ways of doing this
//$customer = $request->route()->domain;
config([
'database.mysql.database' => $customer->customer_database, //this is because I have same username and password for all the databases. If you have different username and passwords set them too.
]);
DB::reconnect(); // this will help in reconnecting the database if the connection is already established. and import the DB facade.
}
}
But the code is not working?
The Error i am getting is
FatalThrowableError in ConfigServiceProvider.php line 25:
Type error: Too few arguments to function App\Providers\ConfigServiceProvider::register(), 0 passed in C:\xampp\htdocs\main\vendor\laravel\framework\src\Illuminate\Foundation\Application.php on line 568 and exactly 1 expected
I am using Laravel 5.4. Can any one guide me how to do it correctly?
well there are some issues here:
First the register() method of a service provider should not accept a Request instance as argument. Its better to write it like that:
public function register()
Second, dont use config() in the register() method, from the laravel docs:
within the register method, you should only bind things into the
service container. You should never attempt to register any event
listeners, routes, or any other piece of functionality within the
register method. Otherwise, you may accidentally use a service that is
provided by a service provider which has not loaded yet.
IMHO I would use a middleware to change the database configuration like you tried in a service provider, i.e:
public function handle($request, Closure $next)
{
$customer=$request->route('domain');
if($customer)
{
config(['database.mysql.database' => $customer->customer_database]);
DB::reconnect();
}
return $next($request);
}
If you take care of loading the middleware in the right place (look in app/Http/Kernel.php) you will have database changed before any query.
What I would suggest is to create 2 .env files, one for each subdomain e.g. .env.domain1 and .env.domain2
Then in your app service provider:
public function boot() { //Use boot for this
$fn = ".env.".$this->app->request->route('domain');
$dotenv = new \Dotenv\Dotenv(self::basePath(), $fn);
try {
$dotenv->overload();
} catch (\Dotenv\Exception\InvalidPathException $e) {
//Nothing to overload with
}
}
This makes your sub-domains behave like different environments which does look like what you're trying to do.
Change
use Illuminate\Support\Facades\Request;
to
use Illuminate\Http\Request;
You can not use Facades for dependency/method injection. Hope it helps

How to move my controllers into a separate package folder in Laravel 5.2?

I build a small app using laravel 5.2. I put all of my files into a folder called Surveys located at App/Modules/Surveys. "No worries, I am planning to move the Modules out of the App folder, I just need to get the controller to work"
Currently my controller is located on App/Http/Controllers/SurveysController
I want to move my controller folder so it is located on App/Modules/Surveys/Controllers/Frontend/SurveysController
How would I tell laravel to resolve the controller out of this path App/Modules/Surveys/Controllers/Frontend/ instead of App/Http/Controllers?
I tried to do this into a service provider but it is still not working
<?php
namespace App\Modules\Surveys\Providers;
use Illuminate\Support\ServiceProvider;
class PackageServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
if (! $this->app->routesAreCached()) {
require __DIR__.'/../routes.php';
}
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->make('Modules\Surveys\Controllers\Frontend\SurveyController');
}
}
What should I do to make the routes point to the correct controller?
Just for clarity my Application instructor starts at the App
App/Https/Controllers
App/Modules/Surveys
I think I figured it out.
Inside the App/Providers/RouteServiceProvider file, I changed the value of $namespace variable to an empty string like this
Changed the following line
protected $namespace = 'App\Http\Controllers';
To this
protected $namespace = '';
Then inside my routes.php file I used the namespace like this
Route::group([
'namespace' => 'Modules\Vendname\Surveys\Controllers\Frontend',
], function()
{
Route::get('/survey', ['as' =>'survey.index', 'uses' => 'SurveysController#index']);
});
I am not sure why Taylor have the value hard coded in the service provider and not an option in the configs but this solution seems to work!
I would been better if there is a dynamic way to override that value.
By default controller's namespace is App\Http\Controllers which is defined in RouteServiceProvider and has a property $namespace so all controllers are supposed to be inside the App\Http\Controllers controller.
I think you have more modules and not only Surveys module, you can set the RouteServiceProvider's $namespace property to 'App\Modules' and define your routes as:
Route::get('some/route', ['uses' => 'Surveys\Controllers\Frontend\SurveyController#someMethod']);
If you feel that a huge prefix namespace on each route is ugly you could create a ModuleFooRouteServiceProvider for each module and require a file like 'routesModuleFoo.php' the same way as RouteServiceProvicer does and register those service providers on config/app.php file like:
'providers' => [
...
App\Modules\Surveys\SurveysRouteServiceProvider::class,
App\Modules\ModuleFoo\ModuleFooRouteServiceProvider::class,
...
]
And ModuleFooServiceProvider would look like:
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class ModuleFooServiceProvider extends ServiceProvider
{
protected $namespace = 'App/Modules/ModuleFoo/Controllers';
public function boot(Router $router)
{
parent::boot($router);
}
public function map(Router $router)
{
$router->group(['namespace' => $this->namespace], function ($router) {
require app_path('Http/routesModuleFoo.php');
});
}
}
And your routes for that module will be defined on routesModuleFoo.php

Trouble resolving a singleton from the service container

I've created a ServiceProvider in Laravel5 that registers two singletons to the service container:
ServiceProvider
namespace App\Providers;
use App\Services\Passwords\FileMakerPasswordBroker;
use App\Services\Passwords\FileMakerTokenRepository;
use Illuminate\Support\ServiceProvider;
class FileMakerPasswordResetServiceProvider extends ServiceProvider
{
protected $defer = true;
public function provides(){
return ['fm.password','fm.password.token'];
}
public function register()
{
$this->registerPasswordBroker();
$this->registerTokenRepository();
}
protected function registerPasswordBroker(){
$this->app->singleton('fm.password', function ($app){
return new FileMakerPasswordBroker;
});
}
protected function registerTokenRepository(){
$this->app->singleton('fm.password.token', function ($app){
return new FileMakerTokenRepository;
});
}
}
I've just started so the two concrete classes being created are just empty class declarations:
TokenRepository
namespace App\Services\Passwords;
class FileMakerTokenRepository {
}
PasswordBroker
namespace App\Services\Passwords;
class FileMakerPasswordBroker {
}
And I've registered my service provider in my config/app.php class:
app.php
...
'providers' => [
...
App\Providers\FileMakerPasswordResetServiceProvider::class
...
The problem is, I can't resolve the singletons out of the service container:
A dev controller
...
use Illuminate\Foundation\Application;
class DevController extends Controller
{
protected $app;
public function __construct(Application $app){
$this->app = $app;
}
public function testPasswordReset(){
// This throws an error
return $this->app->make('fm.password.token');
}
...
When I try to make the singleton, I get the error
ReflectionException in Container.php line 736:
Class fm.password.token does not exist
I walked back through the documentation on binding and resolving from the service container and it looks like everything is right, but I'm obviously missing something.
Is there a step I'm missing or something I'm missing re: resolving a singleton from the service container?
Update
Here's a screen shot of the stack if that helps:
Another interesting fact: When I dump the application to the browser from the dev controller:
dd($this->app);
And look at the list of service providers, the FileMakerPasswordResetServiceProvider class is not present:
The provider is eventually going to be deferred, but at the moment I have the deferred property commented out, so (as far as I know) it should be getting loaded. I see other providers that I've registered in the stack. This may be another clue.

Laravel 5 Repository inside service provider

I have the following problem in my laravel 5 project. I have a service provider for form macros named MacroServiceProvider.php. Some macros should receive data from the database, I'm currently using the model and getting the results with eloquent but I want to use repositories instead, so I created my repository but I can't inject this directly to my service provider.
I want something like this:
...
public function register(MyRepoInterface $repo)
{
$registers = $repo->findAll();
Form::macro...
}
...
How can I do this?
Thanks.
I don't think you can do what are you asking, and I think you are misunderstanding the way providers work and what they are intended for.
In providers, you usually say what are the bindings among interfaces and implementations, so that when you do dependency injection in your application code, it works. I'm pretty sure they are not intended for doing real stuff.
For what you say about your code, I imagine something like this:
a repository interface (MyRepoInterface) with a real implementation using Eloquent (say EloquentMyRepo)
a facade, say Macro, so that you can do Macro::myMacro1(), Macro::myMacro2(), etc.
the methods myMacro1(), myMacro2(), etc, use the repository to get some data from the db and then call some methods from the Form facade
If I'm right, then I suggest something like this.
Repository
Define the interface in the file MyRepoInterface.php with
interface MyRepoInterface
{
public function findAll();
// ... your other repo methods
}
and an implementation EloquentMyRepo.php with
class EloquentMyRepo implements MyRepoInterface
{
public function findAll()
{
// ... do what you need
}
}
Facade
Define a facade file MacroFacade.php with this
use Illuminate\Support\Facades\Facade;
class MacroFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'macro';
}
}
Service class
Define your macro service class in a file MacroService.php, where you can use dependency injection and access your repository. In this class you define your myMacro1()... methods.
class MacroService
{
protected $myRepo;
public function __construct(MyRepoInterface $myRepo)
{
$this->myRepo = $myRepo;
}
public function myMacro1()
{
// access the repo
$items = $this->myRepo->findAll();
// ... do something with $items and finally return a string
return Form::macro(...);
}
public function myMacro2($arg1, $arg2)
{
// ... use the parameters to do something else
}
}
Bindings
In your Providers/AppServiceProvider.php file, go to the register() method and add
public function register()
{
// ...
$this->app->bind('App\MyRepoInterface', 'App\EloquentMyRepo');
// ...
}
so that when you use MyRepoInterface in dependency injection, Laravel knows it has to use an instance of EloquentMyRepo.
Now, let's create a service provider for your macro service. Create a file Providers/MacroServiceProvider.php and put in it
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class MacroServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('macro', 'App\MacroService');
}
}
Now, when we need the facade that is registered as macro, an instance of MacroService is used.
Configuration
We finally need some changes to the configuration. Open the config/app.php file, add the new provider
...
'providers' => [
...
'App\Providers\AppServiceProvider',
...
'App\Providers\MacroServiceProvider',
],
(note that the MacroServiceProvider is declared after the AppServiceProvider.)
Add the alias for the facade:
'aliases' => [
...
'Macro' => 'App\MacroFacade',
],
Done!
What happens
Let's suppose you call
...
Macro::myMacro1();
...
in your code. How the right method is called?
Macro is an alias handled by the MacroFacade class
The facade is registered in the IoC with the macro name by the getFacadeAccessor() method of MacroFacade
The MacroServiceProvider registered the MacroService class as an implementation for macro
An instance of MacroService must be created, but it has MyRepoInterface as dependency
The AppServiceProvider said Laravel to use EloquentMyRepo when MyRepoInterfice is required
So an instance of EloquentMyRepo is created and it is used to create an instance of MacroService
Macro has been resolved to an instance of MacroService
Laravel calls the myMacro1() method of that instance
I hope this can clarify a bit what happens.

Adding routes to laravel via plugins

I am working on designing a specific web framework that allows our team to add new components as plugins, then allowing customers to add these plugins or modules using a control panel.
With CodeIgniter things were easy , just copy the controller into the controllers folder and the client-side module will find its way via the URL app/index.php/module/function
But Laravel doesn't allow such dynamic routing.
Is there anyway to extend the route configuration without editing the routes.php by hand ?
You can simply add any routes you want in your service provider's 'boot' method:
public function boot()
{
$this->app['router']->get('my-route', 'MyVendor\Mypackage\MyController#action');
}
If you want to have a kind of automatic prefix, that doesn't happen automatically, but it's not too hard to create one:
public function boot()
{
$this->app['router']->group(['prefix' => 'my-module'], function ($router) {
$router->get('my-route', 'MyVendor\MyPackage\MyController#action');
$router->get('my-second-route', 'MyVendor\MyPackage\MyController#otherAction');
});
}
A lot of people will have this prefix as a config variable so that developers can choose the prefix they want (if you do this remember to name your routes so you can refer to them easily):
public function boot()
{
$this->app['router']->group(['prefix' => \Config::get('my-package::prefix')], function ($router) {
$router->get('my-route', 'MyVendor\MyPackage\MyController#action');
$router->get('my-second-route', 'MyVendor\MyPackage\MyController#otherAction');
});
}
I know I'm bit late but in Laravel 5.4 we can achieve something like this:
Step 1 Create your package and create service provider in it.
Step 2 Register your package service provider in laravel config app.
Step 3 Now create a sperate routes service provider which will contain following
namespace MyPackage\Providers;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Route;
class MyPackageRouteServiceProvider extends RouteServiceProvider
{
protected $namespace='MyPackage\Controllers';
public function boot()
{
parent::boot();
}
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
protected function mapApiRoutes()
{
Route::prefix('Mypackage\api')
->middleware('api')
->namespace($this->namespace)
->group(__DIR__ . '\..\Routes\api.php');
}
protected function mapWebRoutes()
{
Route::prefix('Mypackage')
->middleware('web')
->namespace($this->namespace)
->group(__DIR__ . '\..\Routes\web.php');
}
}
Note: I'm considering there is Routes Folder and contain web.php and api.php file. According to your question you want to load it dynamically you can have a constructor function and pass the package name, prefix and namespace as per your ease.
Step 4 Now the final step is registering the service provider you can call something like this in your package service provider:
public function boot()
{
$this->app->register('Mypackage\Providers\MyPackageRouteServiceProvider');
}
Hope this helps. Cheers
Just some theory
That's in fact pretty easy! When you think about it, Laravels routing layer is also just a component that is bound to Laravels container.
That allows us to grab it from there wherever we're accessing the container. Since you're trying to modify routes in a package, a great place to do it would be in your packages Service Provider.
Also, when doing that in a Service Provider you'll automatically have access to the app property (Your service provider is a child class of Laravels ServiceProvider class) and you can grab the router pretty easy!
Hands on code
<?php namespace My\Packages\Namespace;
use Illuminate\Support\ServiceProvider;
class MyPackageProvider extends ServiceProvider {
public function boot()
{
$this->app['router']->get('package-route', function(){
return "I just dynamically registered a route out of my package";
});
}
}
That's the Service Provider of your package. The only thing the user will have to do is to add the Service Provider to his providers array in the config/app.php.
Be careful!
When a user has defined a route that is identically named as your dynamically added route, your route will be overwritten. Make sure that you're using some kind of route prefixes if you are dynamically adding routes.
Further Reading
Laravel Docs - IoC Container

Categories