1
0
mirror of https://github.com/flarum/core.git synced 2025-10-19 02:36:08 +02:00

Major refactor and improvements

- Reorganised all namespaces and class names for consistency and structure. Following PSR bylaws (Abstract prefix, Interface/Trait suffix).
  - Move models into root of Core, because writing `use Flarum\Core\Discussion` is nice. Namespace the rest by type. (Namespacing by entity was too arbitrary.)
  - Moved some non-domain stuff out of Core: Database, Formatter, Settings.
  - Renamed config table and all references to "settings" for consistency.
  - Remove Core class and add url()/isInstalled()/inDebugMode() as instance methods of Foundation\Application.
  - Cleanup, docblocking, etc.

- Improvements to HTTP architecture
  - API and forum/admin Actions are now actually all the same thing (simple PSR-7 Request handlers), renamed to Controllers.
  - Upgrade to tobscure/json-api 0.2 branch.
  - Where possible, moved generic functionality to tobscure/json-api (e.g. pagination links). I'm quite happy with the backend balance now re: #262

- Improvements to other architecture
  - Use Illuminate's Auth\Access\Gate interface/implementation instead of our old Locked trait. We still use events to actually determine the permissions though. Our Policy classes are actually glorified event subscribers.
  - Extract model validation into Core\Validator classes.
  - Make post visibility permission stuff much more efficient and DRY.

- Renamed Flarum\Event classes for consistency. ref #246
  - `Configure` prefix for events dedicated to configuring an object.
  - `Get` prefix for events whose listeners should return something.
  - `Prepare` prefix when a variable is passed by reference so it can be modified.
  - `Scope` prefix when a query builder is passed.

- Miscellaneous improvements/bug-fixes. I'm easily distracted!
  - Increase default height of post composer.
  - Improve post stream redraw flickering in Safari by keying loading post placeholders with their IDs. ref #451
  - Use a PHP JavaScript minification library for minifying TextFormatter's JavaScript, instead of ClosureCompilerService (can't rely on external service!)
  - Use UrlGenerator properly in various places. closes #123
  - Make Api\Client return Response object. closes #128
  - Allow extensions to specify custom icon images.
  - Allow external API/admin URLs to be optionally specified in config.php. If the value or "url" is an array, we look for the corresponding path inside. Otherwise, we append the path to the base URL, using the corresponding value in "paths" if present. closes #244
This commit is contained in:
Toby Zerner
2015-10-08 14:28:02 +10:30
parent 8c7cdb184f
commit dd67291ce0
434 changed files with 8676 additions and 7997 deletions

View File

@@ -0,0 +1,197 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Foundation;
use Exception;
use Flarum\Core;
use Illuminate\Config\Repository as ConfigRepository;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
abstract class AbstractServer
{
/**
* @var string
*/
protected $path;
/**
* @var array
*/
protected $config;
/**
* @param string $path
*/
public function __construct($path)
{
$this->path = $path;
if (file_exists($file = $this->path.'/config.php')) {
$this->config = include $file;
}
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* @param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* @param array $config
*/
public function setConfig(array $config)
{
$this->config = $config;
}
/**
* @return Application
*/
protected function getApp()
{
// franzliedke/studio currently doesn't autoload files (see issue
// below), so we will need to load them manually if we're using studio.
// https://github.com/franzliedke/studio/issues/29
if (file_exists($corePath = $this->path.'/core')) {
require $corePath.'/src/helpers.php';
require $corePath.'/vendor/swiftmailer/swiftmailer/lib/swift_required.php';
}
date_default_timezone_set('UTC');
$app = new Application($this->path);
$app->instance('env', 'production');
$app->instance('flarum.config', $this->config);
$app->instance('config', $config = $this->getIlluminateConfig($app));
$this->registerLogger($app);
$app->register('Flarum\Database\DatabaseServiceProvider');
$app->register('Flarum\Settings\SettingsServiceProvider');
$app->register('Flarum\Locale\LocaleServiceProvider');
$app->register('Illuminate\Bus\BusServiceProvider');
$app->register('Illuminate\Cache\CacheServiceProvider');
$app->register('Illuminate\Filesystem\FilesystemServiceProvider');
$app->register('Illuminate\Hashing\HashServiceProvider');
$app->register('Illuminate\Mail\MailServiceProvider');
$app->register('Illuminate\View\ViewServiceProvider');
$app->register('Illuminate\Validation\ValidationServiceProvider');
if ($app->isInstalled()) {
$settings = $app->make('Flarum\Settings\SettingsRepository');
try {
$version = $settings->get('version');
} finally {
if (! isset($version) || $version !== $app->version()) {
die('run upgrade script');
// $command = $app->make('Flarum\Console\Command\UpgradeCommand');
// $command->upgrade();
}
}
$config->set('mail.driver', $settings->get('mail_driver'));
$config->set('mail.host', $settings->get('mail_host'));
$config->set('mail.port', $settings->get('mail_port'));
$config->set('mail.from.address', $settings->get('mail_from'));
$config->set('mail.from.name', $settings->get('forum_title'));
$config->set('mail.encryption', $settings->get('mail_encryption'));
$config->set('mail.username', $settings->get('mail_username'));
$config->set('mail.password', $settings->get('mail_password'));
$app->register('Flarum\Core\CoreServiceProvider');
$app->register('Flarum\Api\ApiServiceProvider');
$app->register('Flarum\Forum\ForumServiceProvider');
$app->register('Flarum\Admin\AdminServiceProvider');
$app->register('Flarum\Extension\ExtensionServiceProvider');
}
$app->boot();
return $app;
}
/**
* @param Application $app
* @return ConfigRepository
*/
protected function getIlluminateConfig(Application $app)
{
return new ConfigRepository([
'view' => [
'paths' => [],
'compiled' => $app->storagePath().'/views',
],
'mail' => [
'driver' => 'mail',
],
'cache' => [
'default' => 'file',
'stores' => [
'file' => [
'driver' => 'file',
'path' => $app->storagePath().'/cache',
],
],
'prefix' => 'flarum',
],
'filesystems' => [
'default' => 'local',
'cloud' => 's3',
'disks' => [
'flarum-avatars' => [
'driver' => 'local',
'root' => $app->publicPath().'/assets/avatars'
]
]
]
]);
}
/**
* @param Application $app
*/
protected function registerLogger(Application $app)
{
$logger = new Logger($app->environment());
$logPath = $app->storagePath() . '/logs/flarum.log';
$handler = new StreamHandler($logPath, Logger::DEBUG);
$handler->setFormatter(new LineFormatter(null, null, true, true));
$logger->pushHandler($handler);
$app->instance('log', $logger);
$app->alias('log', 'Psr\Log\LoggerInterface');
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Foundation;
use Illuminate\Support\ServiceProvider;
abstract class AbstractServiceProvider extends ServiceProvider
{
/**
* @var Application
*/
protected $app;
/**
* @param Application $app
*/
public function __construct(Application $app)
{
parent::__construct($app);
}
/**
* {@inheritdoc}
*/
public function register()
{
}
}

View File

@@ -0,0 +1,688 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Foundation;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;
use Illuminate\Events\EventServiceProvider;
class Application extends Container implements ApplicationContract
{
/**
* The Flarum version.
*
* @var string
*/
const VERSION = '0.1.0-beta.2';
/**
* The base path for the Flarum installation.
*
* @var string
*/
protected $basePath;
/**
* Indicates if the application has "booted".
*
* @var bool
*/
protected $booted = false;
/**
* The array of booting callbacks.
*
* @var array
*/
protected $bootingCallbacks = [];
/**
* The array of booted callbacks.
*
* @var array
*/
protected $bootedCallbacks = [];
/**
* All of the registered service providers.
*
* @var array
*/
protected $serviceProviders = [];
/**
* The names of the loaded service providers.
*
* @var array
*/
protected $loadedProviders = [];
/**
* The deferred services and their providers.
*
* @var array
*/
protected $deferredServices = [];
/**
* The custom storage path defined by the developer.
*
* @var string
*/
protected $storagePath;
/**
* Create a new Flarum application instance.
*
* @param string|null $basePath
*/
public function __construct($basePath = null)
{
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
if ($basePath) {
$this->setBasePath($basePath);
}
}
/**
* Determine if Flarum has been installed.
*
* @return bool
*/
public function isInstalled()
{
return $this->bound('flarum.config');
}
/**
* Check if Flarum is in debug mode.
*
* @return bool
*/
public function inDebugMode()
{
return ! $this->isInstalled() || $this->make('flarum.config')['debug'];
}
/**
* Get the URL to the Flarum installation.
*
* @param string $path
* @return string
*/
public function url($path = null)
{
$config = $this->make('flarum.config');
$url = array_get($config, 'url', $_SERVER['REQUEST_URI']);
if (is_array($url)) {
if (isset($url[$path])) {
return $url[$path];
}
$url = $url['base'];
}
if ($path) {
$url .= '/' . array_get($config, "paths.$path", $path);
}
return $url;
}
/**
* Get the version number of the application.
*
* @return string
*/
public function version()
{
return static::VERSION;
}
/**
* Register the basic bindings into the container.
*/
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance('Illuminate\Container\Container', $this);
}
/**
* Register all of the base service providers.
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
}
/**
* Set the base path for the application.
*
* @param string $basePath
* @return $this
*/
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
/**
* Bind all of the application paths in the container.
*
* @return void
*/
protected function bindPathsInContainer()
{
foreach (['base', 'public', 'storage'] as $path) {
$this->instance('path.'.$path, $this->{$path.'Path'}());
}
}
/**
* Get the base path of the Laravel installation.
*
* @return string
*/
public function basePath()
{
return $this->basePath;
}
/**
* Get the path to the public / web directory.
*
* @return string
*/
public function publicPath()
{
return $this->basePath;
}
/**
* Get the path to the storage directory.
*
* @return string
*/
public function storagePath()
{
return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage';
}
/**
* Set the storage directory.
*
* @param string $path
* @return $this
*/
public function useStoragePath($path)
{
$this->storagePath = $path;
$this->instance('path.storage', $path);
return $this;
}
/**
* Get or check the current application environment.
*
* @param mixed
* @return string
*/
public function environment()
{
if (func_num_args() > 0) {
$patterns = is_array(func_get_arg(0)) ? func_get_arg(0) : func_get_args();
foreach ($patterns as $pattern) {
if (Str::is($pattern, $this['env'])) {
return true;
}
}
return false;
}
return $this['env'];
}
/**
* Determine if we are running in the console.
*
* @return bool
*/
public function runningInConsole()
{
return php_sapi_name() == 'cli';
}
/**
* Determine if we are running unit tests.
*
* @return bool
*/
public function runningUnitTests()
{
return $this['env'] == 'testing';
}
/**
* Register all of the configured providers.
*
* @return void
*/
public function registerConfiguredProviders()
{
}
/**
* Register a service provider with the application.
*
* @param ServiceProvider|string $provider
* @param array $options
* @param bool $force
* @return ServiceProvider
*/
public function register($provider, $options = [], $force = false)
{
if ($registered = $this->getProvider($provider) && ! $force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProviderClass($provider);
}
$provider->register();
// Once we have registered the service we will iterate through the options
// and set each of them on the application so they will be available on
// the actual loading of the service objects and for developer usage.
foreach ($options as $key => $value) {
$this[$key] = $value;
}
$this->markAsRegistered($provider);
// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by the developer's application logics.
if ($this->booted) {
$this->bootProvider($provider);
}
return $provider;
}
/**
* Get the registered service provider instance if it exists.
*
* @param ServiceProvider|string $provider
* @return ServiceProvider|null
*/
public function getProvider($provider)
{
$name = is_string($provider) ? $provider : get_class($provider);
return Arr::first($this->serviceProviders, function ($key, $value) use ($name) {
return $value instanceof $name;
});
}
/**
* Resolve a service provider instance from the class name.
*
* @param string $provider
* @return ServiceProvider
*/
public function resolveProviderClass($provider)
{
return new $provider($this);
}
/**
* Mark the given provider as registered.
*
* @param ServiceProvider $provider
* @return void
*/
protected function markAsRegistered($provider)
{
$this['events']->fire($class = get_class($provider), [$provider]);
$this->serviceProviders[] = $provider;
$this->loadedProviders[$class] = true;
}
/**
* Load and boot all of the remaining deferred providers.
*/
public function loadDeferredProviders()
{
// We will simply spin through each of the deferred providers and register each
// one and boot them if the application has booted. This should make each of
// the remaining services available to this application for immediate use.
foreach ($this->deferredServices as $service => $provider) {
$this->loadDeferredProvider($service);
}
$this->deferredServices = [];
}
/**
* Load the provider for a deferred service.
*
* @param string $service
*/
public function loadDeferredProvider($service)
{
if (! isset($this->deferredServices[$service])) {
return;
}
$provider = $this->deferredServices[$service];
// If the service provider has not already been loaded and registered we can
// register it with the application and remove the service from this list
// of deferred services, since it will already be loaded on subsequent.
if (! isset($this->loadedProviders[$provider])) {
$this->registerDeferredProvider($provider, $service);
}
}
/**
* Register a deferred provider and service.
*
* @param string $provider
* @param string $service
*/
public function registerDeferredProvider($provider, $service = null)
{
// Once the provider that provides the deferred service has been registered we
// will remove it from our local list of the deferred services with related
// providers so that this container does not try to resolve it out again.
if ($service) {
unset($this->deferredServices[$service]);
}
$this->register($instance = new $provider($this));
if (! $this->booted) {
$this->booting(function () use ($instance) {
$this->bootProvider($instance);
});
}
}
/**
* Resolve the given type from the container.
*
* (Overriding Container::make)
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($abstract);
if (isset($this->deferredServices[$abstract])) {
$this->loadDeferredProvider($abstract);
}
return parent::make($abstract, $parameters);
}
/**
* Determine if the given abstract type has been bound.
*
* (Overriding Container::bound)
*
* @param string $abstract
* @return bool
*/
public function bound($abstract)
{
return isset($this->deferredServices[$abstract]) || parent::bound($abstract);
}
/**
* Determine if the application has booted.
*
* @return bool
*/
public function isBooted()
{
return $this->booted;
}
/**
* Boot the application's service providers.
*
* @return void
*/
public function boot()
{
if ($this->booted) {
return;
}
// Once the application has booted we will also fire some "booted" callbacks
// for any listeners that need to do work after this initial booting gets
// finished. This is useful when ordering the boot-up processes we run.
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}
/**
* Boot the given service provider.
*
* @param ServiceProvider $provider
* @return mixed
*/
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
/**
* Register a new boot listener.
*
* @param mixed $callback
* @return void
*/
public function booting($callback)
{
$this->bootingCallbacks[] = $callback;
}
/**
* Register a new "booted" listener.
*
* @param mixed $callback
* @return void
*/
public function booted($callback)
{
$this->bootedCallbacks[] = $callback;
if ($this->isBooted()) {
$this->fireAppCallbacks([$callback]);
}
}
/**
* Call the booting callbacks for the application.
*
* @param array $callbacks
* @return void
*/
protected function fireAppCallbacks(array $callbacks)
{
foreach ($callbacks as $callback) {
call_user_func($callback, $this);
}
}
/**
* Get the path to the cached "compiled.php" file.
*
* @return string
*/
public function getCachedCompilePath()
{
return $this->basePath().'/bootstrap/cache/compiled.php';
}
/**
* Get the path to the cached services.json file.
*
* @return string
*/
public function getCachedServicesPath()
{
return $this->basePath().'/bootstrap/cache/services.json';
}
/**
* Determine if the application is currently down for maintenance.
*
* @return bool
*/
public function isDownForMaintenance()
{
return file_exists($this->storagePath().'/framework/down');
}
/**
* Get the service providers that have been loaded.
*
* @return array
*/
public function getLoadedProviders()
{
return $this->loadedProviders;
}
/**
* Get the application's deferred services.
*
* @return array
*/
public function getDeferredServices()
{
return $this->deferredServices;
}
/**
* Set the application's deferred services.
*
* @param array $services
* @return void
*/
public function setDeferredServices(array $services)
{
$this->deferredServices = $services;
}
/**
* Add an array of services to the application's deferred services.
*
* @param array $services
* @return void
*/
public function addDeferredServices(array $services)
{
$this->deferredServices = array_merge($this->deferredServices, $services);
}
/**
* Determine if the given service is a deferred service.
*
* @param string $service
* @return bool
*/
public function isDeferredService($service)
{
return isset($this->deferredServices[$service]);
}
/**
* Register the core class aliases in the container.
*/
public function registerCoreContainerAliases()
{
$aliases = [
'app' => ['Flarum\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'],
'blade.compiler' => 'Illuminate\View\Compilers\BladeCompiler',
'cache' => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'],
'cache.store' => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'],
'config' => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'],
'db' => 'Illuminate\Database\DatabaseManager',
'events' => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'],
'files' => 'Illuminate\Filesystem\Filesystem',
'filesystem' => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'],
'filesystem.disk' => 'Illuminate\Contracts\Filesystem\Filesystem',
'filesystem.cloud' => 'Illuminate\Contracts\Filesystem\Cloud',
'hash' => 'Illuminate\Contracts\Hashing\Hasher',
'mailer' => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'],
'validator' => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'],
'view' => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'],
];
foreach ($aliases as $key => $aliases) {
foreach ((array) $aliases as $alias) {
$this->alias($key, $alias);
}
}
}
/**
* Flush the container of all bindings and resolved instances.
*/
public function flush()
{
parent::flush();
$this->loadedProviders = [];
}
}