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.