Subclassing Migrator not working for namespaced migration - laravel-4

I have some namespaced migrations, and I can't get past the Class Not Found errors due to namespacing. In an earlier question, Antonio Carlos Ribeiro stated:
Laravel migrator doesn't play nice with namespaced migrations. Your best bet in this case is to subclass and substitute the Migrator class, like Christopher Pitt explains in his blog post: https://medium.com/laravel-4/6e75f99cdb0.
I have tried doing so (followed by composer dump-autoload, of course), but am continuing to receive Class Not Found errors. I've got the project files set up as
inetpub
|--appTruancy
|--database
|--2015_04_24_153942_truancy_create_districts.php
|--MigrationsServiceProvider.php
|--Migrator.php
The migration file itself is as follows:
<?php
namespace Truancy;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class TruancyCreateDistricts extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('districts', function($table) {
$table->string('id')->unique()->primary()->nullable(false);
$table->string('district');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('districts');
}
}
Migrator.php is as follows:
namespace Truancy;
use Illuminate\Database\Migrations\Migrator as Base;
class Migrator extends Base{
/**
* Resolve a migration instance from a file.
*
* #param string $file
* #return object
*/
public function resolve($file)
{
$file = implode("_", array_slice(explode("_", $file), 4));
$class = "Truancy\\" . studly_case($file);
return new $class;
}
}
MigrationServiceProvider.php is as follows:
<?php
namespace Truancy;
use Illuminate\Support\ServiceProvider;
class TruancyServiceProvider extends ServiceProvider{
public function register()
{
$this->app->bindShared(
"migrator",
function () {
return new Migrator(
$this->app->make("migration.repository"),
$this->app->make("db"),
$this->app->make("files")
);
}
);
}
}
The lines generated in autoload_classmap.php are as expected:
'Truancy\\Migrator' => $baseDir . '/appTruancy/database/migrations/Migrator.php',
'Truancy\\TruancyCreateDistricts' => $baseDir . '/appTruancy/database/migrations/2015_04_24_153942_truancy_create_districts.php',
'Truancy\\TruancyServiceProvider' => $baseDir . '/appTruancy/database/migrations/MigrationsServiceProvider.php'
I'm calling php artisan migrate --path="appTruancy/database/migrations" and I receive the error:
PHP Fatal error: Class 'TruancyCreateDistricts' not found in
C:\inetpub\laravel\vendor\laravel\framework\src\Illuminate\Database
\Migrations\Migrator.php on line 297
I know I must be doing something dumb (my instinct is $class = "Truancy\\" . studly_case($file); in Migrator.php is wrong), but I can't unscrew this lightbulb. The migrate command is obviously successfully finding my migrations file, and the correct classname is in the classmap, so it has to be somewhere in the process of resolving the classname itself from the file, which the subclass and substitution is supposed to address. Any suggestions as to where I've gone wrong?

Ok, I've gotten this working. It turns out that the Medium.com article assumes you'd just know where to put the files he talks about, which I didn't. I've made several changes, and now everything is working correctly:
I created a new appTruancy\providers subfolder, and add it to composer.json
I moved both Migrator.php and MigrationServiceProvider.php into the new folder
I changed the namespace in both of those files to Truancy\Providers to match the directory structure
I added 'Truancy\Providers\MigrationsServiceProvider' to the providers array in appTruancy\config\app.php
I added a \ in front of Schema in the migration file to reference the base namespace.
I ran dump-autoload to update the classmap
This is one of those cases where I'm not 100% certain that all of the changes were required, but the layout does make sense so I'm happy with it. So, in a nutshell, if you're trying to namespace your migrations, you need to subclass the Migrator class as described in the Medium.com article listed above, but you then need to register the service provider in config\app, making sure the class names in both files are consistent.

Related

Laravel installation in sub-folder and horizon not working

I have installed the Laravel in sub-folder and is trying to install the horizon. After routing to "test.com/sub-folder/horizon", all the design in broken and also the internal links are pointing to main domain instead of main-domain-without-subfolder.
After the search, it seems to be the known issue which is already reported in github issue
Has there is any work around to make horizon work when Laravel is installed in sub-folder?
I have a solution that only involves PHP.
The issue, as pointed out by #Isaiahiroko, is the basePath defined for Horizon's interface. That code is in Laravel\Horizon\Http\Controllers\HomeController::index(). The idea is this: we are going to pass to Laravel's service container our own implementation of that controller that will override the basePath definition passed to Horizon's interface.
Create a new controller with code like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\Http\Controllers\HomeController;
class HorizonHomeController extends HomeController
{
/**
* Overrides default horizon route to support subdirectory hosting.
*/
public function index ()
{
// We use a plain request to check for the base url.
$request = request();
// Set up our base path.
$base_path = Str::substr($request->getBasePath(), 1);
if (!empty($base_path)) {
$base_path .= '/';
}
// Patch default horizon variables with our own base path.
$variables = Horizon::scriptVariables();
$variables['path'] = $base_path . config('horizon.path');
// Render horizon's home view.
return view('horizon::layout', [
'assetsAreCurrent' => Horizon::assetsAreCurrent(),
'horizonScriptVariables' => $variables,
'cssFile' => Horizon::$useDarkTheme ? 'app-dark.css' : 'app.css',
'isDownForMaintenance' => App::isDownForMaintenance(),
]);
}
}
What's left is telling Laravel's service container that when Horizon's HomeController is requested, it should provide our HorizonHomeController class. In your AppServiceProvider, at the end of the register() method, set this up:
// [...]
class AppServiceProvider extends ServiceProvider
{
// [...]
/**
* Register any application services.
*
* #return void
* #throws InvalidConfiguration
*/
public function register()
{
// [...]
// Horizon's subdirectory hack
$this->app->bind(
Laravel\Horizon\Http\Controllers\HomeController::class,
App\Http\Controllers\HorizonHomeController::class
);
}
// [...]
}
After that, you should be able to browse to http(s)://<your-host>/<your-sub-dir>/horizon normally.
Considerations:
To me this feels cleaner that patching a compiled js, which also has the downside that needs to be re-applied every time Horizon is updated (this can be mitigated with a post-update script in composer, tho). Also, for additional points, this solution is only overriding the method that renders the view, but not the route, which means all of Horizon's authentication mechanisms (middlewares and gates) are working exactly as described in the documentation.
If you desperately need to do this, here is a hack:
In public\vendor\horizon\app.js, search for window.Horizon.basePath
replace window.Horizon.basePath="/"+window.Horizon.path; with window.Horizon.basePath="/[you sub-directoy]/"+window.Horizon.path;
It should work...until you run update one day and it mysteriously stop working.

How to override the resourcePath() function defined in Illuminate/Foundation/Application.php

I am modularizing laravel. I have decided to move all the default routes, controllers, resources, etc.. to /app/Modules/Pub. For the most part this has worked well. However I would like to change the default resources path of the application. Unfortunately this doesn't seem to be (easily) configurable.
So... using grep I was able to track down the resource_path() function to /var/www/sigma/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
I think it's possible to override this function somewhere but this seems like a subpar hack as this function consists simply of:
app()->resourcePath($path)
Again using grep I found out that this function is to be found in /var/www/sigma/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
This seems to be the thing to change since it does not reference any configuration value, rather the value is hard coded:
return $this->basePath.DIRECTORY_SEPARATOR.'resources'.($path ? DIRECTORY_SEPARATOR.$path : $path);
But I think it's safe to assume it's pretty foolish to change anything under the vendor folder manually. Obviously I need to override this function somewhere. I am unclear where and how to do this
Create a new Application class which extends the \Illuminate\Foundation\Application:
<?php
namespace <YOUR NAMESPACE HERE>;
class ApplicationCustom extends \Illuminate\Foundation\Application
{
public function __construct()
{
parent::__construct();
}
/**
* Get the path to the resources directory.
*
* #param string $path
* #return string
*/
public function resourcePath($path = '')
{
// Implement the custom method
}
}
Now, just change your bootstrap/app.php file to use the custom class:
$app = new YOUR_NAMESPACE\ApplicationCustom(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
Hope it helps.
You could create a class somewhere in your project and extend the default \Illuminate\Foundation\Application class. Then override the methods you need and switch the class instantiated in bootstrap/app.php with your custom one.

Why won't this Laravel 5.4 service provider register?

I am trying to do a hello world service provider with the new Laravel 5.4.
I have created the following service provider file:
//File: app/TestProvider/TestServiceProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('Test', function ($app) {
return new Test();
});
}
}
I have created a simple class under the same namespace:
//File: app/TestProvider/Test.php
namespace App\TestProvider;
class Test
{
/**
* Register bindings in the container.
*
* #return void
*/
public function helloWorld()
{
echo "hello world";
}
}
The problem is, this is not registering. The register method is executing as when I put a breaker before the 'bind' method, it executes:
public function register()
{
dd("BREAKER");
$this->app->bind('Test', function ($app) {
return new Test();
});
}
So this outputs "BREAKER" as expected. However if I put the breaker in the closure, nothing happens which suggests for some reason, that 'bind' method isn't being executed??
Any ideas?
EDIT:
Just some further info: I know that the Test class is registered and in the correct namespace as I can do:
dd(new Test());
in the registration method, and it outputs the resource id as expected.
Explanation
The closure provided only runs when the binding is being resolved. That's why it's a closure, it can be saved in the service container and resolved at any time while the program runs.
Solution
To see the resolved binding, create a controller and resolve the class in that controller:
// File: app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
// This isn't the best way, but it works. See the best way below
class TestController extends Controller {
public function index()
{
return \App::make('Test')->helloWorld();
}
}
Of course, don't forget to register the route:
// File: routes/web.php
Route::get('/', 'TestController#index');
The binding will resolve when you hit the homepage.
However, as I said, it's not the best way, so here I prepared a better way. Change the way you register the binding:
// File: app/Providers/TestProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
use App\TestProvider\Test;
// Better way
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
// Note: we bind the exact complete class name!
$this->app->bind(Test::class, function ($app) {
return new Test();
});
}
}
After this change the controller so that it looks like this:
namespace App\Http\Controllers;
use App\TestProvider\Test;
class TestController extends Controller {
/**
* #var Test $test
*/
private $test;
// Let Laravel resolve the dependency on constructing the class
public function __construct(Test $test)
{
$this->test = $test;
}
public function index()
{
return $this->test->helloWorld();
}
}
You will see that the exact same thing happens, but it looks more elegant and avoids conflicts.
Details
Laravel gives only a high level overview of the service container, which doesn't help to learn how it works on the inside. The best way to see that is to go down the call stack.
When you do that, you find that Laravel registers every class in the project in the service container. That means that whether you create a service provider or not, the class will be in the container. How exactly?
When you run php artisan optimize, Laravel creates files that have array with all the classes of the project. When you run the app, after registering everything from the service providers, Laravel registers the rest of the classes from that file.
That means that in your case, if you don't specifically register the Test class, it will still be resolvable. Basically, you only need to register classes that need some specific instructions to be resolved.
So how does Laravel resolve the dependencies?
When you run \App::make(Test::class) or inject dependency via type hinting in the constructor (the "better way" from my solution), Laravel looks for that dependency among the bindings.
When it finds the dependency, it resolves either the closure associated to it or the constructor of the class directly.
When it resolves the constructor directly, it looks for type hints among the constructor parameters and recursively resolves all of them until there's nothing else to resolve.
After that it returns the resolved class.
Of course, bear in mind that for Laravel to analyze the contructor of a class, it needs to be resolved via the service container in the first place. You can't just call $test = new Test(); and expect Laravel to do all the magic :)
Conclusion
This is a rather quick overview of Laravel's service container. The best way for you to learn it is, of course, studying the sources for yourself. It's truly elegant and it uses PHP's functionality to the fullest.
I really hope this shed some light on the service container for you and can help you in the future :)
The closure passed to the bind() method is not executed until you actually attempt to resolve the alias you are binding.
So, if you dd('breaker') inside the closure, this won't actually get executed until Test is resolved (whatever your preferred resolution method is):
Service provider:
// bind the closure to the 'Test' alias
public function register()
{
$this->app->bind('Test', function ($app) {
dd("BREAKER");
return new Test();
});
}
Code that resolve Test alias:
// different ways of resolving the alias out of the container.
// any of these will execute the bound closure.
$test = resolve('Test');
$test = app('Test');
$test = app()->make('Test');
$test = \App::make('Test');
try:
$this->app->bind(Test::class, function ($app) {
return new Test();
});

laravel 4.1 scaffold generated code gives error :Call to undefined method Illuminate\Support\Facades\Event::all()

I am using scaffold command to generate code, but when i do scaffold for "event" everything go fines but when i try to load event controller in browser it shows following error
Call to undefined method Illuminate\Support\Facades\Event::all()
My event controller code is
class EventsController extends BaseController {
protected $event;
public function __construct(Event $event)
{
$this->event = $event;
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$events = $this->event->all();
return View::make('events.index', compact('events'));
}
}
I din't understand why it throw error because its auto generated code, and i do 3 scaffold before this one and they are working well . may be i cant put the name "event" or "static" while generating scaffold. because when i generate scaffold for "static" also it give some kind of error and when i scaffold "staticcontent" its work well.
Note
By scaffold i mean scaffold command
php artisan generate:scaffold event --fields="from:date, body:text"
https://github.com/JeffreyWay/Laravel-4-Generators
The Event class already exists so you'll have to change the name of your model to something else. Ran into this same issue before and was banging my head off the wall for hours.

Managing Own Classes with Composer

I'm new to composer and autoloaders. I think I also lack of file organization strategies.
I'm trying to build up a new project on slimframework.
I have some classes for Slim. But I can not autoload them in my project.
/
composer.json
composer.phar
vendor
config
someapiparams.php
database.php
cache.php
general.php
public
index.php
models
libraries
Foo
Slim
Config.php
Cache.php
/composer.json:
"autoload": {
"psr-0": {
"Foo": "libraries/"
}
}
/libraries/Foo/Slim/Config.php:
<?php
class Config {
/**
* Loads a file based on $key param under ROOT . "/config",
* if not already loaded.
* Then returns an array.
*/
public static function get($key) {}
}
/libraries/Foo/Slim/Cache.php:
<?php
class Cache{
/**
* Initialize a caching engine defined in config file if not already done.
* Then runs corrensponding engine methods for getting and setting.
*/
public static function init() {
$config = Config::get("cache");
// initialize driver.
}
public static function __get($key) {}
public static function __set($key, $value, $params) {}
}
/public/index.php:
require ROOT."/vendor/autoload.php";
$app = new Slim\Slim();
var_dump(Config::get("database")); exit;
//var_dump(Foo\Slim\Config::get("database")); exit;
//var_dump(Slim\Config::get("database")); exit;
Error is Config class not found.
You have forgotten to put:
namespace Foo/Slim;
at the top of /libraries/Foo/Slim/Cache.php (or have possibly have snipped it for the code example).
If adding the namespace doesn't fix it, you should step through the code with a debugger, and see exactly what files the Composer autoloader is searching for, when it tries to load the class and fails.

Resources