gnu-social/DOCUMENTATION/DEVELOPERS/code_walkthrough.md

4.1 KiB

Entrypoint

GNU social's entrypoint is the public/index.php script, which gets called by the webserver for all requests. This is handled by the webserver itself, which translates a GET /foo to GET /index.php?p=foo. This feature is called 'fancy URLs', as it was in V2.

The index script handles all the initialization of the Symfony framework and social itself. It reads configuration from .env or any .env.* file at the project root. The index script creates a Kernel object, which is defined on the src/Kernel.php file. This is the part where the code we control starts; the Kernel constructor creates the needed constants, sets the timezone to UTC and the string encoding to UTF8. The other functions in this class get called by the Symfony framework at the appropriate times. We will come back to this file.

Registering services

Next, the src/Util/GNUsocial.php class is instantiated by the Symfony framework, on the 'onKernelRequest' or 'onCommand' events. The former event, as described in the docs:

This event is dispatched very early in Symfony, before the controller is determined. It's useful to add information to the Request or return a Response early to stop the handling of the request.

The latter, is launched on the bin/console script is used.

In both cases, these events call the register function, which creates static references for the logging, event and translation services. This is done so these services can be used via static function calls, which is much less verbose and more accessible than the way the framework recommends. This function also loads all the Modules and Plugins, which like in V2, are components that aren't directly connected to the core code, being used to implement internal and optional functionality respectively, by handling events launched by various parts of the code.

Database definitions

Going back to the Kernel, the build function gets called by the Symfony framework and allows us to register a 'Compiler Pass'. Specifically, we register the SchemaDefPass from the src/DependencyInjection/Compiler/SchemaDefPass.php file, which adds a new 'metadata driver' to Doctrine. The metadata driver is responsible for loading database definitions. We keep the same method as in V2, which was that each 'Entity' has a schemaDef static function which returns an array describing the database.

This definition is handled by the SchemaDefDriver class from src/Util/SchemaDefDriver.php file, which extends StaticPHPDriver and replaces the methods loadMetadata with schemaDef. The function loadMetadataForClass function is called by the Symfony framework for each file in src/Entity/. It allows us to call the schemaDef function and translate the array definition to Doctrine's internal representation.

Routing

Next, we'll look at the RouteLoader, defined in src/Util/RoutLoader.php, which loads all the files from src/Routes/*.php and calls the static load method, which defines routes with an interface similar to V2's connect, except it requires an extra identifier as the first argument. This identifier is used, for instance, to generate URLs for each route. Each route connects an URL path to a Controller, with the possibility of taking arguments, which are passed to the __invoke method of the respective controller. The controllers are defined in src/Controller/ and are responsible for handling a request and return a Symfony Response object (subject to change, in order to abstract HTML vs JSON output).

End to end

The next steps are handled by the Symfony framework which creates a Request object from the HTTP request, and then a corresponding Response is created by calling the Kernel::handle method, which matches the appropriate route and thus calls it's controller.

Performance

All this happens on each request, which seems like a lot to handle, and would be too slow. Fortunately, Symfony has a 'compiler' which caches and optimizes the code paths. In production mode, this can be done through a command, while in development mode, it's handled on each request if the file changed, which has a performance impact, but obviously makes development easier.