The server object functions as the global scope, it holds the various core components, manages plugins and routed and handles incomming requsts
Implementes \OC\Hooks\Emitter
registerPlugin(\OC\Plugin $plugin)
registerRoute(\OC\Route $route)
getUserManager(): \OC\User\Manager
getFilesystem(): \OC\Files\Node\Root
getDB(): \OC\DB
getConfig(): \OC\Config
getJobManager(): \OC\BackgroundJob\JobList
getLog(): \OC\Log
getL10N(): OC_L10N
getSession(): \OC\Session\Session
The system is broken down in various components which provides the core api's
- User management
\OC\User\Manager
- Filesystem
\OC\Files\Node\Root
- Database
\OC\DB
- Config
\OC\Config
- Jobs
\OC\BackgroundJob\JobList
- Log
\OC\Log
- Router
- L10N
- Session
When a component is created on of the following events are emitted
getDB(\OC\DB $db)
getFilesystem(\OC\Files\Node\Root $filesystem)
getUserManager(\OC\User\Manager $manager)
getConfig(\OC\Config $config)
getJobManager(\OC\BackgroundJob\JobList $manager)
getLog(\OC\Log $log)
getL10N(\OC_L10N $l10n)
getSession(\OC\Session\Session $session)
These hooks can be used by apps to make changes to the components, such as registering backends or adding additional hooks before a request is handled.
Making the changes component in the hooks instead of making them during the loading of the apps, will make the loading of the apps quicker and only trigger changes for components that are actually used, if the filesystem component isn't used during a request no time will be wasted on initializing storage backends. This removes the need of only loading a subset of the apps which can lead to unexpected behaviour.
Holds the data from the request made by the server, request header, body, cookies, etc. HTTPFoundation from Symphony is used as base.
extends \Symfony\Component\HttpFoundation\Response
getBodyParser(): \OC\Request\BodyParser
(The parser will be selected based on the Content-Type of the request)
getParsed(): mixed
getRaw(): string
Inherits \OC\Request\BodyParser
getParsed(): mixed
Inherits \OC\Request\BodyParser
getParsed(): SimpleXMLElement
Holds the data for the response to be send to the client, headers, body, etc. Various reponse types are represented by subclass (JSON, HTML, etc)
\Symfony\Component\HttpFoundation\Response
is used for managing the responses.
Symfony provides the follwing sub classes for specific response types:
\Symfony\Component\HttpFoundation\JsonResponse
\Symfony\Component\HttpFoundation\StreamedResponse
\Symfony\Component\HttpFoundation\BinaryFileResponse
inherits \Symfony\Component\HttpFoundation\Response
setData(SimpleXMLElement $xml)
Create a response based on a Page object, inherits \Symfony\Component\HttpFoundation\Response
addScript(string $app, string $script)
addStyle(string $app, string $script)
addNavigationEntry(\OC\Page\Navigation\Entry $entry)
setContent(string $html)
Create a response based on a template file, inherits \OC\Resonse\Page
setTemplate(string $app, string $template)
set(string $key, mixed $value)
Create an eventsource response, inherits \Symfony\Component\HttpFoundation\Response
.
open()
write(string $type, mixed $data)
close()
Download a file from the oc virtual filesystem, sets content-length, content-type, etc header and writes the file content inherits \Symfony\Component\HttpFoundation\Response
. Use \Symfony\Component\HttpFoundation\BinaryFileResponse
for files from the local filesystem.
writeFile(\OC\Files\Node $file)
Created when a route throws an unhandled exception inherits \Symfony\Component\HttpFoundation\Response
setException(Exception $exception)
A Page represents an html page that is send to the browser, it manages it's navigation, scripts, styles and content
getNavigation(): \OC\Navigation\Navigation
addScript(string $app, string $script)
getScripts(): string[]
clearScripts()
addStyle(string $app, string $script)
getStyles(): string[]
clearStyles()
setContent(string $html)
Manages the list of navigation entries and the active entry
addEntry(\OC\Page\Navigation\Entry $entry)
setActive(\OC\Page\Navigation\Entry $entry)
getEntries(): \OC\Page\Navigation\Entry[]
__construct(string $app, $page)
getApp(): string
getPage(): string
Plugins manipulate the bahaviour of all requests made (think express middleware), example plugin are auth handelers, file viewers and js/css minifiers
handleRequest(\OC\Request\Request $request, \OC\Server $server)
handleResponse(\OC\Response\Response $response, \OC\Server $server)
Routes are responseible of creating a Response for a Request, apps can register Routes in a server and the server handles calling the correct Route for each request made.
Inherits Symfony\Component\Routing\Route
callCheck()
check for CSRF headers, throws an exception if not setcheckLoggedIn()
throws an exception if not logged incheckAdmin()
throws an exception if not logged inexecute(\OC\Request\Request $request): \OC\Response\Response
- Initialize the Server
- Initialize the apps
- Parse the request from the server into a Request object
- Run
handleRequest
on all registered plugin - Handle the request using the router, generating a Response object for the Request
- Run
handleResponse
on all registered plugin - Send the response to the client
Legacy apps mostly follow the following lifecycle for a request
- Initialize owncloud (load lib/base, which also initializes the apps)
- Get information from the request (
$_GET
,$_POST
) - Create an OC_Template object for the response
- Set template variables, add scripts, navigation, etc using
OC_Util
andOC_App
- Print the response using
OC_Template->printPage()
When a legacy app is run against a new server OC_Template
, OC_Util
and OC_App
function as the main compatibility layers,
the OC_Template
instance will internally keep a Template Response object, adding scripts, navigation, etc using OC_Util
and OC_App
will be redirected to the Response object.
When OC_Template->printPage()
is called, handleResponse
is called on all registered plugin before sending the response
This way legact apps will follow the same request lifecycle as apps build against the new api
Because both the Request and Response are objects and the entire lifecycle is controlled by the Server it becomes much easier to test the code. To test the behaviour of an app you can simple create a mock Request, pass it to the app and check the Response returned.
With the seperation of logic into various Plugins it becomes easy to test that logic, to test Basic Auth you create a mock Request with the right headers set, pass it trough the Basic Auth plugin and check if the login is successfull.
Because the functionality of apps is seperated over the Plugins, Routes and Hooks, the Server has a more fine-grained controll over what parts of the apps get activated, if the user system isn't used during a request, the user backends won't be loaded.
Some pseudocode examples of how apps in interact with the Server in the new architecture
$server->listen('\OC\Server', 'getUserManager', function($manager){
$manager->registerBackend(new MyUserBackend());
}
class MinifierPlugin extends \OC\Plugin {
public function handleResponse($response){
if ($response instanceof \OC\Response\Page) {
$scripts = $response->getScripts();
$minifiedScript = minifyAndConcat($scripts);
$response->clearScripts();
$response->addScript('minifier', $minifiedScript)
}
}
}
@Raydiation
Yes, I mean one instance per server
The server acts as a "global" state, I think using hooks makes it easier to control when things are executed then loading specific files for each app.
Makes sense