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

StackOverflow https://stackoverflow.com/questions/22882829

  •  28-06-2023
  •  | 
  •  

質問

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?

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top