Code structure
For the structure of the PHP code, see the following sections. Different kinds of classes and concepts are explained there:
If you are looking for a general description of our PHP setup, please check PHP!
Modules
A lot of code is sorted into modules in the /src/Modules
directory.
This is a sorting by topic: each module contains files for one topic.
That can be a gateway,
a controller, an (old) view, javascript, css, (old) XHR,
(old) models.
The Rest api controllers do not go into
their respective module directory but into the /src/RestApi
directory. This does not have a good reason but it's the way it is now.
Deprecated module structure
Since legacy code is still widespread through the repository it is important to understand it, too.
The (php) code is roughly structured with Model - View - Controller.
The communication with the database is found in Model classes.
For example we can find sql
-commands to manipulate a foodsaver in /src/Modules/Foodsaver/FoodsaverModel.php
.
Those are executed with the functions inherited from the Db
class (see use Foodsharing\Lib\Db\Db;
, for example $this->q(...)
where q
stands for query
.
Newer module structure
Instead of Model classes, that hold both, data query logic and domain logic, we move towards splitting these up into Gateway classes and Transaction classes.
For a general description what „domain logic“ is, see section Transactions.
Note that all of the following guidelines have a lot of exceptions in the existing code. Nevertheless try to heed the following guidelines in code you write and refactor.
Gateways
Our concept of Gateway classes follows the Table Data Gateway pattern.
One main difference to Models is that a Gateway doesn't contain the actual model of an entity, as the overall domain logic is put into Transactions while the structure lives in Data Transfer Objects.
The purpose of a Gateway is to provide functionality to query instances of a certain entity type from the database. If you are familiar with ORM based architectures, you might compare the Gateway's responsibility to the one of a Repository.
As methods to be found on a Gateway class have the job to perform queries, they should be named in a way that
portrays this. They should not pretend to perform domain-related business logic. A method name suitable for a
Gateway class would be selectResponsibleFoodsavers()
or insertFetcher()
. A method not suitable would be
addFetcher()
, as this implies that the method took care of the whole transaction of adding a fetcher to a store
pickup.
In particular permission checks are not to be found in Gateways.
Another difference to models regarding the implementation of SQL queries is that the functions to communicate with the
database are not directly in the Gateway class by inheritance but encapsulated in the attribute
db
($this->db-><functioncall>
) of class Database
defined in /src/Modules/Core/Database.php
.
Gateways inherit from BaseGateway
(/src/Modules/Core/BaseGateway.php
), which provides them with the $db
attribute.
If possible, use semantic methods like $db->fetch()
or $db->insert()
to build your queries.
Often, requesting information from the database uses sql
calls via the functions at the end of the Database class, like
$db->execute()
- don't use these unless you can't build your query otherwise.
All of those functions are well-documented in /src/Modules/Core/Database.php
.
Individual gateway functionality
Please refer to our list of Gateway classes below if you're looking for specific functionality:
ActivityGateway.php
ApplicationGateway.php
- handles workgroup applications
BasketGateway.php
- adding, editing, requesting food baskets, managing their availability
- querying food basket data, listing new baskets nearby
BellGateway.php
BlogGateway.php
BuddyGateway.php
- add someone as buddy, respond to that, get list of buddies
BusinessCardGateway.php
ContentGateway.php
- basic CMS functionality: display, create, edit, delete pages with fixed contentId
- some content is used in page templates and just a sentence, other content consists of full pages
DashboardGateway.php
- just basic user info
- we have an issue to investigate if `countStoresWithoutDistrict` & `setbezirkids` are still needed
EmailGateway.php
EventGateway.php
- add or edit events, manage invitations (target audience) and their RSVP
- get list of events in a region, query people who are interested
- also has some weird event location storage
FoodsaverGateway.php
- almost 1000 lines of code :)
FoodSharePointGateway.php
GroupGateway.php
- groups handle functionality that is shared between both regions and workgroups
- currently only has a few basic helper, and the complex hull closure computation
GroupFunctionGateway.php
- group functions are currently only used for workgroups, but can attach to both regions and workgroups
- includes adding and removing the special function groups, and querying whether they exist
- there are 3 available functions right now; see `Modules/Core/DBConstants/Region/WorkgroupFunction.php`
LegalGateway.php
LoginGateway.php
LookupGateway.php
MailboxGateway.php
MailsGateway.php
MaintenanceGateway.php
- data needed for cleanup and bookkeeping executed each night (see `MaintenanceControl.php`)
MapGateway.php
MessageGateway.php
MigrateGateway.php
PassportGeneratorGateway.php
ProfileGateway.php
PushNotificationGateway.php
QuizGateway.php
QuizSessionGateway.php
ForumGateway.php
ForumFollowerGateway.php
RegionGateway.php
WorkGroupGateway.php
ReportGateway.php
- currently unused
SearchGateway.php
SettingsGateway.php
StatisticsGateway.php
StatsGateway.php
StoreGateway.php
TeamGateway.php
UploadsGateway.php
VotingGateway.php
WallPostGateway.php
Transactions
All modules have certain business rules/domain logic to follow when their data is modified. After all, there are always certain operations that have to be executed together to ensure that the data keeps being consistent according to the the rules that apply to them in reality. We implement these transactions of operations executed together as methods on Transaction classes.
For example, when someone wants to join a store pickup, it's not enough to just insert this information into the database. We also have to be check if the user has the rights to join without a confirmation, and if not, we have to make sure that the store owner gets notified that they should confirm or deny it.
This is why joining a pickup is implemented in the joinPickup()
method on the corresponding Transaction class. All
controllers should use this transaction if they want to make a user join a pickup, because only if all steps of the
transaction are executed, the pickup joining is complete.
What should not be part of a transaction class:
- knowledge of the underlying database (should still work with a gateway reading from punched cards)
- knowledge of request types (e.g. should be callable from a desktop application or some different internet protocol). Therefore transaction classes do not raise HTTPException or choose HTTP response codes or the json representation of responses
- the session - but at this point we are not strict, so far transaction classes use information of the session
Permissions
Permission classes are used to organize what actions are allowed for which user. They are a special type of transaction class.
Data Transfer Objects
Currently, domain objects are often represented differently: Some methods receive and return them as associative arrays, some receive them as a very long list of parameters. If arrays are used, it's often unclear which format the output has and which format the input is expected to have. Parameter lists on the other hand can get very long, and if parameters are documented, the documentation for one domain object is spread around the code.
For further structuring Data Transfer Objects (DTO) can be used. An example can be found in the Bell module, introduced in the merge request !1457.
DTOs help with clearing up which parameters are expected when and what types they have. DTO classes have public properties and don't encapsulate logic or functionality. Only logic to create DTOs or convert them from other representations shall be implemented on the DTO classes as static methods.
As objects are often represented differently, as only parts of them are needed, most domain objects have multiple DTO
representations. That's why we put them in a DTO
directory inside of the corresponding module directory. Usually,
there is one main or "complete" representation, that includes all aspects of the domain object that can be found in its
database table. This one is just named like the domain object itself (e. g. Bell
). All other partial represantations
can be named according to their purpose or the place they are used (e. g. BellForList
).
Controllers
Controllers handle requests.
They define how to extract relevant information from the
request, check permissions by calling the correct Permission
and calling the suitable transaction.
They define which HTTP response including the response code
is sent back and the conversion of internal data to json
(return $this->handleView(...)
).
Since the business logic („What is part of an event (= transaction)?“) is in the transaction classes, a controller method usually just calls one actual transaction method (apart from permission checks). It can read necessary information from the session to give those as arguments to the transaction class.
We have:
- REST controllers with the name
<submodule>RestController.php
- (legacy) XHR controllers with the name
<module>Xhr.php
- (legacy) render controllers with the name
<module>Control.php
- modern render controllers with the name
<module>Controller.php
Render controllers are called that because they always render a part of the website, as opposed to API controllers (like REST and XHR), which are usually called by the rendered website (client) and return data, not an HTML document.
For a guide to refactoring legacy HTML controllers to modern controllers, see the PHP controller refactoring guide
Services
Currently, in our source code, some code that assists controllers can be found
in classes named "Service": /src/Services
.
Some of these classes are Transaction classes that need to be renamed, and some
of them are utility classes. /src/Services/SanitizerService.php
is the best
example for that.
Some code in the services should rather go into the modules if they belong to a specific module.
Also see the section Services for a broader use of this term.
Helpers
The content of /src/Helpers
is a collection of code that
somehow had no better place. The word Helper
does not say anything.
Rather try to find a suitable place for it.
Routing
For the new REST API, Routing happens completely through Symfony, using @Route
annotations in the respective controllers.
(also, see config/routes/api.yaml
)
Everything else (the website, xhr and xhrapp) uses GET parameters to determine the controller and action to call.
See src/Entrypoint
for the implementations,
and src/Lib/Routing.php
for how the page=
GET parameter corresponds with controller class names.
Last, there are some special routes that consist of:
- a
location
and atry_files
directive in the web server's config For the development environment, you can find them here: https://gitlab.com/foodsharing-dev/images/-/blob/master/web/foodsharing.conf - Symfony routes to make symfony call the correct entrypoint for all possible URI forms in
config/routes/special.yaml
.