How to run console command in yii2 from web - php

I have created a console command in console/controllers with SuggestionController .
If i run command like php yii suggestions, its working.
I want to know how to execute console command from web without any extensions of yii2.

It can be done much simpler
$oldApp = \Yii::$app;
new \yii\console\Application([
'id' => 'Command runner',
'basePath' => '#app',
'components' => [
'db' => $oldApp->db,
],
);
\Yii::$app->runAction('migrate/up', ['migrationPath' => '#yii/rbac/migrations/', 'interactive' => false]);
\Yii:$app = $oldApp;
Github LINK

As of Yii2 - 2.0.11.2 advanced app -- this works
First let's make sure controller and namespace correct. In this case frontend app accessing console application import method()
In console\controllers\FhirController
Set the alias to be available in the console\config\main.php [OPTIONAL]
'aliases' => [
'#common' => dirname(__DIR__),
'#frontend' => dirname(dirname(__DIR__)) . '/frontend',
'#backend' => dirname(dirname(__DIR__)) . '/backend',
'#console' => dirname(dirname(__DIR__)) . '/console',
],
Finally from the frontend view, make the call like this:
In this case, calling the controller route fhir then method import()
$consoleController = new console\controllers\FhirController('fhir', Yii::$app);
$consoleController->runAction('import');

On a site I'm overhauling, I have a need for a background task, that can be toggled via an action, which requires that I can also find its pid using ps. After much googling and almost as much swearing, I pieced together the solution.
# First change to (already-calculated) correct dir:
chdir($strPath);
# Now execute with nohup, directed to dev/null, and crucially with & at end, to run async:
$output = shell_exec("nohup php yii <console controller>/<action> > /dev/null &");
Yes, I understand that shell_exec should be used with extreme caution, thanks.

This is way I found and used some time ago to run yii console controller/action (I used this for run migrations from web).
In your web controller action:
// default console commands outputs to STDOUT
// so this needs to be declared for wep app
if (! defined('STDOUT')) {
define('STDOUT', fopen('/tmp/stdout', 'w'));
}
$consoleController = new \yii\console\controllers\SuggestionController;
$consoleController->runAction('your action eg. index');
/**
* open the STDOUT output file for reading
*
* #var $message collects the resulting messages of the migrate command to be displayed in a view
*/
$handle = fopen('/tmp/stdout', 'r');
$message = '';
while (($buffer = fgets($handle, 4096)) !== false) {
$message .= $buffer . "\n";
}
fclose($handle);
return $message;

You can either exec() your command ´´´php yii suggestions´´´ but this may result in permission issues with the webserver user.
The better way is to use a ConsoleRunner extension, e.g. yii2-console-runner or yii2-console-runner-extension which do the job control job a little bit more sophisticated and more secure with popen().
Always be aware of code injections when executing exec() and the like!

I think this is the simplest solution:
$controller = new SuggestionController(Yii::$app->controller->id, Yii::$app);
$controller->actionSuggestions();

yii2 call console command from web or api .
my controller name :
UnitController
in folder
console/controllers
my action name :
actionUnitDesc
if(!defined('STDIN')) define('STDIN', fopen('php://stdin', 'rb'));
if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'wb'));
if(!defined('STDERR')) define('STDERR', fopen('php://stderr', 'wb'));
$consoleController = new \console\controllers\UnitController('unit', Yii::$app);
$consoleController->runAction('unit-des');

Related

how to print some in like print in django on laravel [duplicate]

So I have a Laravel controller:
class YeahMyController extends BaseController {
public function getSomething() {
Console::info('mymessage'); // <-- what do I put here?
return 'yeahoutputthistotheresponse';
}
}
Currently, I'm running the application using artisan (which runs PHP's built-in development web server under the hood):
php artisan serve
I would like to log console messages to the STDOUT pipe for the artisan process.
Aha!
This can be done with the following PHP function:
error_log('Some message here.');
Found the answer here: Print something in PHP built-in web server
The question relates to serving via artisan and so Jrop's answer is ideal in that case. I.e, error_log logging to the apache log.
However, if your serving via a standard web server then simply use the Laravel specific logging functions:
\Log::info('This is some useful information.');
\Log::warning('Something could be going wrong.');
\Log::error('Something is really going wrong.');
Or with current version of Laravel, like this:
info('This is some useful information.');
This logs to Laravel's log file located at /laravel/storage/logs/laravel-<date>.log (laravel 5.0). Monitor the log - linux/osx: tail -f /laravel/storage/logs/laravel-<date>.log
Laravel 5.0 http://laravel.com/docs/5.0/errors
Laravel 4.2: http://laravel.com/docs/4.2/errors
I haven't tried this myself, but a quick dig through the library suggests you can do this:
$output = new Symfony\Component\Console\Output\ConsoleOutput();
$output->writeln("<info>my message</info>");
I couldn't find a shortcut for this, so you would probably want to create a facade to avoid duplication.
It's very simple.
You can call it from anywhere in APP.
$out = new \Symfony\Component\Console\Output\ConsoleOutput();
$out->writeln("Hello from Terminal");
In Laravel 6 there is a channel called 'stderr'. See config/logging.php:
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
In your controller:
use Illuminate\Support\Facades\Log;
Log::channel('stderr')->info('Something happened!');
For better explain Dave Morrissey's answer I have made these steps for wrap with Console Output class in a laravel facade.
1) Create a Facade in your prefer folder (in my case app\Facades):
class ConsoleOutput extends Facade {
protected static function getFacadeAccessor() {
return 'consoleOutput';
}
}
2) Register a new Service Provider in app\Providers as follow:
class ConsoleOutputServiceProvider extends ServiceProvider
{
public function register(){
App::bind('consoleOutput', function(){
return new \Symfony\Component\Console\Output\ConsoleOutput();
});
}
}
3) Add all this stuffs in config\app.php file, registering the provider and alias.
'providers' => [
//other providers
App\Providers\ConsoleOutputServiceProvider::class
],
'aliases' => [
//other aliases
'ConsoleOutput' => App\Facades\ConsoleOutput::class,
],
That's it, now in any place of your Laravel application, just call your method in this way:
ConsoleOutput::writeln('hello');
Hope this help you.
If you want the fancy command IO from Laravel (like styling, asking and table) then I created this class below
Instructions
I have not fully verified everywhere that it is THE cleanest solution etc, but it works nice (but I only tested it from within a unit test case, under Laravel 5.5).
So most probably you can use it however you like:
$cmd = new ConsoleCommand;
$cmd->error("Aw snap!");
$cmd->table($headers, $rows);
$answer = $cmd->ask("Tell me, what do you need?");
//even Symfony's progress bar
$cmd->outputStyle->progressStart(5); //set n = 100% (here 100% is 5 steps)
$cmd->outputStyle->progressAdvance(); //you can call it n times
$cmd->outputStyle->progressFinish(); //set to 100%
Or course you can also wrap in your own facade, or some static singleton etc, or anyway you wish.
The class itself
class ConsoleCommand extends \Illuminate\Console\Command
{
protected $name = 'NONEXISTENT';
protected $hidden = true;
public $outputSymfony;
public $outputStyle;
public function __construct($argInput = null)
{
parent::__construct();
$this->input = new \Symfony\Component\Console\Input\StringInput($argInput);
$this->outputSymfony = new \Symfony\Component\Console\Output\ConsoleOutput();
$this->outputStyle = new \Illuminate\Console\OutputStyle($this->input, $this->outputSymfony);
$this->output = $this->outputStyle;
}
}
I wanted my logging information to be sent to stdout because it's easy to tell Amazon's Container service (ECS) to collect stdout and send it to CloudWatch Logs. So to get this working, I added a new stdout entry to my config/logging.php file like so:
'stdout' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stdout',
],
'level' => 'info',
],
Then I simply added 'stdout' as one of the channels in the stack log channel:
'default' => env('LOG_CHANNEL', 'stack'),
'stack' => [
'driver' => 'stack',
'channels' => ['stdout', 'daily'],
],
This way, I still get logs in a file for local development (or even on the instance if you can access it), but more importantly they get sent to the stdout which is saved in CloudWatch Logs.
If you want to log to STDOUT you can use any of the ways Laravel provides; for example (from wired00's answer):
Log::info('This is some useful information.');
The STDOUT magic can be done with the following (you are setting the file where info messages go):
Log::useFiles('php://stdout', 'info');
Word of caution: this is strictly for debugging. Do no use anything in production you don't fully understand.
Bit late to this...I'm surprised that no one mentioned Symfony's VarDumper component that Laravel includes, in part, for its dd() (and lesser-known, dump()) utility functions.
$dumpMe = new App\User([ 'name' => 'Cy Rossignol' ]);
(new Symfony\Component\VarDumper\Dumper\CliDumper())->dump(
(new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($dumpMe)
);
There's a bit more code needed, but, in return, we get nice formatted, readable output in the console—especially useful for debugging complex objects or arrays:
App\User {#17
#attributes: array:1 [
"name" => "Cy Rossignol"
]
#fillable: array:3 [
0 => "name"
1 => "email"
2 => "password"
]
#guarded: array:1 [
0 => "*"
]
#primaryKey: "id"
#casts: []
#dates: []
#relations: []
... etc ...
}
To take this a step further, we can even colorize the output! Add this helper function to the project to save some typing:
function toConsole($var)
{
$dumper = new Symfony\Component\VarDumper\Dumper\CliDumper();
$dumper->setColors(true);
$dumper->dump((new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($var));
}
If we're running the app behind a full webserver (like Apache or Nginx—not artisan serve), we can modify this function slightly to send the dumper's prettified output to the log (typically storage/logs/laravel.log):
function toLog($var)
{
$lines = [ 'Dump:' ];
$dumper = new Symfony\Component\VarDumper\Dumper\CliDumper();
$dumper->setColors(true);
$dumper->setOutput(function ($line) use (&$lines) {
$lines[] = $line;
});
$dumper->dump((new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($var));
Log::debug(implode(PHP_EOL, $lines));
}
...and, of course, watch the log using:
$ tail -f storage/logs/laravel.log
PHP's error_log() works fine for quick, one-off inspection of simple values, but the functions shown above take the hard work out of debugging some of Laravel's more complicated classes.
Here's another way to go about it:
$stdout = fopen('php://stdout', 'w');
fwrite($stdout, 'Hello, World!' . PHP_EOL);
The PHP_EOL adds new line.
In command class
before class
use Symfony\Component\Console\Output\ConsoleOutput;
Inside the class methods
$output = new ConsoleOutput();
$output->writeln('my text that appears in command line ');
You can use echo and prefix "\033", simple:
Artisan::command('mycommand', function () {
echo "\033======== Start ========\n";
});
And change color text:
if (App::environment() === 'production') {
echo "\033[0;33m======== WARNING ========\033[0m\n";
}
From Larave 6.0+
$this->info('This will appear in console');
$this->error('This error will appear in console');
$this->line('This line will appear in console);
Documentation https://laravel.com/docs/6.x/artisan#writing-output

Laravel 5 console (artisan) command unit tests

I am migrating my Laravel 4.2 app to 5.1 (starting with 5.0) and am a lot of trouble with my console command unit tests. I have artisan commands for which I need to test the produced console output, proper question/response handling and interactions with other services (using mocks). For all its merits, the Laravel doc is unfortunately silent with regards to testing console commands.
I finally found a way to create those tests, but it feels like a hack with those setLaravel and setApplication calls.
Is there a better way to do this? I wish I could add my mock instances to the Laravel IoC container and let it create the commands to test with everything properly set. I'm afraid my unit tests will break easily with newer Laravel versions.
Here's my unit test:
Use statements:
use Mockery as m;
use App\Console\Commands\AddClientCommand;
use Symfony\Component\Console\Tester\CommandTester;
Setup
public function setUp() {
parent::setUp();
$this->store = m::mock('App\Services\Store');
$this->command = new AddClientCommand($this->store);
// Taken from laravel/framework artisan command unit tests
// (e.g. tests/Database/DatabaseMigrationRollbackCommandTest.php)
$this->command->setLaravel($this->app->make('Illuminate\Contracts\Foundation\Application'));
// Required to provide input to command questions (provides command->getHelper())
// Taken from ??? when I first built my command tests in Laravel 4.2
$this->command->setApplication($this->app->make('Symfony\Component\Console\Application'));
}
Input provided as command arguments. Checks console output
public function testReadCommandOutput() {
$commandTester = new CommandTester($this->command);
$result = $commandTester->execute([
'--client-name' => 'New Client',
]);
$this->assertSame(0, $result);
$templatePath = $this->testTemplate;
// Check console output
$this->assertEquals(1, preg_match('/^Client \'New Client\' was added./m', $commandTester->getDisplay()));
}
Input provided by simulated keyboard keys
public function testAnswerQuestions() {
$commandTester = new CommandTester($this->command);
// Simulate keyboard input in console for new client
$inputs = $this->command->getHelper('question');
$inputs->setInputStream($this->getInputStream("New Client\n"));
$result = $commandTester->execute([]);
$this->assertSame(0, $result);
$templatePath = $this->testTemplate;
// Check console output
$this->assertEquals(1, preg_match('/^Client \'New Client\' was added./m', $commandTester->getDisplay()));
}
protected function getInputStream($input) {
$stream = fopen('php://memory', 'r+', false);
fputs($stream, $input);
rewind($stream);
return $stream;
}
updates
This doesn't work in Laravel 5.1 #11946
I have done this before as follows - my console command returns a json response:
public function getConsoleResponse()
{
$kernel = $this->app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArrayInput([
'command' => 'test:command', // put your command name here
]),
$output = new Symfony\Component\Console\Output\BufferedOutput
);
return json_decode($output->fetch(), true);
}
So if you want to put this in it's own command tester class, or as a function within TestCase etc... up to you.
use Illuminate\Support\Facades\Artisan;
use Symfony\Component\Console\Output\BufferedOutput;
$output = new BufferedOutput();
Artisan::call('passport:client', [
'--password' => true,
'--name' => 'Temp Client',
'--no-interaction' => true,
], $output);
$stringOutput = $output->fetch();

In Symfony2 , How do you mock a Console Component which is consuming another ConsoleComponent

I'm writing a console component using Symfony2 libraries that consumes another application that's also written with symfony2 console components.
I want to mock the other applications console component, how do I go about achieving this? The application I'm building is simply consuming an existing command from another application:
Basically, how do you write a unit test for the code below:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
'command' => 'demo:greet',
'name' => 'Fabien',
'--yell' => true,
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
// ...
}
Another application or the same application?
$commandMock = $this->getMock('Symfony\Component\Console\Command\Command');
$commandMock->expects($this->once())->method('run')->with(...)->will($this->returnValue(1));
$applicationMock = $this->getMockBuilder('Symfony\Component\Console\Application')
->disableConstructor()->getMock();
$applicationMock->method('find')
->with($this->equalTo('demo:greet'))
->will($this->returnValue($commandMock));
You just mock the command and mock the application to return the mocked command.

How do I write to the console from a Laravel Controller?

So I have a Laravel controller:
class YeahMyController extends BaseController {
public function getSomething() {
Console::info('mymessage'); // <-- what do I put here?
return 'yeahoutputthistotheresponse';
}
}
Currently, I'm running the application using artisan (which runs PHP's built-in development web server under the hood):
php artisan serve
I would like to log console messages to the STDOUT pipe for the artisan process.
Aha!
This can be done with the following PHP function:
error_log('Some message here.');
Found the answer here: Print something in PHP built-in web server
The question relates to serving via artisan and so Jrop's answer is ideal in that case. I.e, error_log logging to the apache log.
However, if your serving via a standard web server then simply use the Laravel specific logging functions:
\Log::info('This is some useful information.');
\Log::warning('Something could be going wrong.');
\Log::error('Something is really going wrong.');
Or with current version of Laravel, like this:
info('This is some useful information.');
This logs to Laravel's log file located at /laravel/storage/logs/laravel-<date>.log (laravel 5.0). Monitor the log - linux/osx: tail -f /laravel/storage/logs/laravel-<date>.log
Laravel 5.0 http://laravel.com/docs/5.0/errors
Laravel 4.2: http://laravel.com/docs/4.2/errors
I haven't tried this myself, but a quick dig through the library suggests you can do this:
$output = new Symfony\Component\Console\Output\ConsoleOutput();
$output->writeln("<info>my message</info>");
I couldn't find a shortcut for this, so you would probably want to create a facade to avoid duplication.
It's very simple.
You can call it from anywhere in APP.
$out = new \Symfony\Component\Console\Output\ConsoleOutput();
$out->writeln("Hello from Terminal");
In Laravel 6 there is a channel called 'stderr'. See config/logging.php:
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
In your controller:
use Illuminate\Support\Facades\Log;
Log::channel('stderr')->info('Something happened!');
For better explain Dave Morrissey's answer I have made these steps for wrap with Console Output class in a laravel facade.
1) Create a Facade in your prefer folder (in my case app\Facades):
class ConsoleOutput extends Facade {
protected static function getFacadeAccessor() {
return 'consoleOutput';
}
}
2) Register a new Service Provider in app\Providers as follow:
class ConsoleOutputServiceProvider extends ServiceProvider
{
public function register(){
App::bind('consoleOutput', function(){
return new \Symfony\Component\Console\Output\ConsoleOutput();
});
}
}
3) Add all this stuffs in config\app.php file, registering the provider and alias.
'providers' => [
//other providers
App\Providers\ConsoleOutputServiceProvider::class
],
'aliases' => [
//other aliases
'ConsoleOutput' => App\Facades\ConsoleOutput::class,
],
That's it, now in any place of your Laravel application, just call your method in this way:
ConsoleOutput::writeln('hello');
Hope this help you.
If you want the fancy command IO from Laravel (like styling, asking and table) then I created this class below
Instructions
I have not fully verified everywhere that it is THE cleanest solution etc, but it works nice (but I only tested it from within a unit test case, under Laravel 5.5).
So most probably you can use it however you like:
$cmd = new ConsoleCommand;
$cmd->error("Aw snap!");
$cmd->table($headers, $rows);
$answer = $cmd->ask("Tell me, what do you need?");
//even Symfony's progress bar
$cmd->outputStyle->progressStart(5); //set n = 100% (here 100% is 5 steps)
$cmd->outputStyle->progressAdvance(); //you can call it n times
$cmd->outputStyle->progressFinish(); //set to 100%
Or course you can also wrap in your own facade, or some static singleton etc, or anyway you wish.
The class itself
class ConsoleCommand extends \Illuminate\Console\Command
{
protected $name = 'NONEXISTENT';
protected $hidden = true;
public $outputSymfony;
public $outputStyle;
public function __construct($argInput = null)
{
parent::__construct();
$this->input = new \Symfony\Component\Console\Input\StringInput($argInput);
$this->outputSymfony = new \Symfony\Component\Console\Output\ConsoleOutput();
$this->outputStyle = new \Illuminate\Console\OutputStyle($this->input, $this->outputSymfony);
$this->output = $this->outputStyle;
}
}
I wanted my logging information to be sent to stdout because it's easy to tell Amazon's Container service (ECS) to collect stdout and send it to CloudWatch Logs. So to get this working, I added a new stdout entry to my config/logging.php file like so:
'stdout' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stdout',
],
'level' => 'info',
],
Then I simply added 'stdout' as one of the channels in the stack log channel:
'default' => env('LOG_CHANNEL', 'stack'),
'stack' => [
'driver' => 'stack',
'channels' => ['stdout', 'daily'],
],
This way, I still get logs in a file for local development (or even on the instance if you can access it), but more importantly they get sent to the stdout which is saved in CloudWatch Logs.
If you want to log to STDOUT you can use any of the ways Laravel provides; for example (from wired00's answer):
Log::info('This is some useful information.');
The STDOUT magic can be done with the following (you are setting the file where info messages go):
Log::useFiles('php://stdout', 'info');
Word of caution: this is strictly for debugging. Do no use anything in production you don't fully understand.
Bit late to this...I'm surprised that no one mentioned Symfony's VarDumper component that Laravel includes, in part, for its dd() (and lesser-known, dump()) utility functions.
$dumpMe = new App\User([ 'name' => 'Cy Rossignol' ]);
(new Symfony\Component\VarDumper\Dumper\CliDumper())->dump(
(new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($dumpMe)
);
There's a bit more code needed, but, in return, we get nice formatted, readable output in the console—especially useful for debugging complex objects or arrays:
App\User {#17
#attributes: array:1 [
"name" => "Cy Rossignol"
]
#fillable: array:3 [
0 => "name"
1 => "email"
2 => "password"
]
#guarded: array:1 [
0 => "*"
]
#primaryKey: "id"
#casts: []
#dates: []
#relations: []
... etc ...
}
To take this a step further, we can even colorize the output! Add this helper function to the project to save some typing:
function toConsole($var)
{
$dumper = new Symfony\Component\VarDumper\Dumper\CliDumper();
$dumper->setColors(true);
$dumper->dump((new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($var));
}
If we're running the app behind a full webserver (like Apache or Nginx—not artisan serve), we can modify this function slightly to send the dumper's prettified output to the log (typically storage/logs/laravel.log):
function toLog($var)
{
$lines = [ 'Dump:' ];
$dumper = new Symfony\Component\VarDumper\Dumper\CliDumper();
$dumper->setColors(true);
$dumper->setOutput(function ($line) use (&$lines) {
$lines[] = $line;
});
$dumper->dump((new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($var));
Log::debug(implode(PHP_EOL, $lines));
}
...and, of course, watch the log using:
$ tail -f storage/logs/laravel.log
PHP's error_log() works fine for quick, one-off inspection of simple values, but the functions shown above take the hard work out of debugging some of Laravel's more complicated classes.
Here's another way to go about it:
$stdout = fopen('php://stdout', 'w');
fwrite($stdout, 'Hello, World!' . PHP_EOL);
The PHP_EOL adds new line.
In command class
before class
use Symfony\Component\Console\Output\ConsoleOutput;
Inside the class methods
$output = new ConsoleOutput();
$output->writeln('my text that appears in command line ');
You can use echo and prefix "\033", simple:
Artisan::command('mycommand', function () {
echo "\033======== Start ========\n";
});
And change color text:
if (App::environment() === 'production') {
echo "\033[0;33m======== WARNING ========\033[0m\n";
}
From Larave 6.0+
$this->info('This will appear in console');
$this->error('This error will appear in console');
$this->line('This line will appear in console);
Documentation https://laravel.com/docs/6.x/artisan#writing-output

What is best practice for setting up a multiple webroot environment on a Lithium application?

I'm interested in having a unified backend environment for multiple users and having multiple frontend environments for users. All should run from a single application instance, which will be the equivalent of the app folder. I've gone back and forth on several configurations but keep running into inconsistencies once I get deeper into the app. Imagine something like the enterprise WordPress app: users need a unique webroot for their account for accessing their templates and digital assets, but one application instance runs the backend environment for all users. This is proving tricky on Lithium.
Right now, I set a basic environment parameter in the /[user]/webroot/index.php file, like so:
<?php
$env = ['webroot' => __DIR__, 'id' => 'generic_account'];
require dirname(dirname(__DIR__)) . '/app/config/bootstrap.php';
use lithium\action\Dispatcher;
use lithium\action\Request;
echo Dispatcher::run(new Request(compact('env')));
?>
Then, in the Dispatcher, I have an extension class map the account:
Dispatcher::applyFilter('run', function($self, $params, $chain) use (&$i) {
Environment::set($params['request']);
//Map $env['id'] value to stored database connection
if (isset($params['request']->id)) {
Accounts::load($params['request']);
}
foreach (array_reverse(Libraries::get()) as $name => $config) {
if ($name === 'lithium') {
continue;
}
$file = $config['path'] . '/config/routes.php';
file_exists($file) ? call_user_func(function() use ($file) { include $file; }) : null;
}
return $chain->next($self, $params, $chain);
});
Finally, in the Accounts::load() method, I pull connection settings from a master database and set those as the default Connection configuration:
<?php
namespace app\extensions\core;
use app\models\Routes;
use lithium\net\http\Router;
class Accounts {
public static function load(&$request) {
if (!is_object($request)) {
return false;
}
$class = [
'accounts' => 'app\models\Accounts',
'plugins' => 'app\extensions\core\Plugins',
'prefs' => 'app\extensions\core\Preferences',
'connections' => 'lithium\data\Connections',
'inflector' => 'lithium\util\Inflector',
'exception' => 'lithium\net\http\RoutingException'
];
$class['accounts']::meta('connection', 'master');
$bind = $class['prefs']::read('bind_account');
$key = $bind == 'domain' || $bind == 'subdomain' ? 'HTTP_HOST' : 'id';
$find = $class['accounts'] . '::' . $class['inflector']::camelize('find_by_' . $bind, false);
$account = call_user_func($find, $request->env($key));
if ($account == null) {
throw new $class['exception']('Account `' . $request->env($key) . '` doesn\'t exist.');
}
$class['connections']::add('default', json_decode($account->database, true));
$request->activeAccount = $account;
$request->params['webroot'] = $request->env('webroot');
$plugins = $class['plugins']::load();
return true;
}
/**
* Allows users to store customized route definitions in `routes` table,
* hence the use of `app\models\Routes`.
*/
public static function routes() {
$routes = Routes::all();
foreach ($routes as $route) {
Router::connect($route->match, [
'controller' => 'pages',
'action' => 'view',
'template' => $route->template,
'layout' => $route->layout
]);
}
}
}
?>
All this seems to work well for routing URLs and allowing for multiple front-end webroots. Here's the trick: when creating a webroot for admin interfaces, it's turning into a convoluted mess for keeping the asset paths straight. I've used Media::assets() to try to overcome this, but I have a feeling there's a more elegant solution out there. I've struggled to find any other examples or documentation that specifically addresses this kind of setup concern.
It's pretty straightforward, you're almost there. All you really need is a unique webroot/ directory per user, in addition to the normal bootstrap include and request-dispatching, you can include any other user-specific configuration, and register the main application, like so:
Libraries::add('yourApp', [
'path' => '/path/to/codebase',
'webroot' => __DIR__
]);
This gives you the centralized codebase, but also allows for a custom webroot per user.
I have two platforms on lithium with a similar setup. I wrote a plugin called li3_saas to facilitate it which I think I still need to put up on github. But it does some similar things with loading from a master database and setting the default database to be user specific.
I would recommend an entirely different app for a global admin interface that can load your main app using Libraries::add(), possibly with the 'bootstrap => false option to skip loading the bootstrap.
I accomplish some things - like reusing css or js - with symlinks on the file system.
I do use Media::assets() to let my admin interface know where uploaded files exist. I create a custom key in there called 'upload' and use that when creating assets paths and urls.
I could elaborate on that. Can you give a more specific use case that you are trying to solve?

Categories