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!
A lot of code is sorted into modules in the
This is a sorting by topic: each module contains files for one topic.
That can be a gateway,
The Rest api controllers do not go into
their respective module directory but into the
directory. This does not have a good reason but it's the way it is now.
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
Those are executed with the functions inherited from the
Db class (see
use Foodsharing\Lib\Db\Db;, for example
q stands for
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.
Our concept of Gateway classes follows the Table Data Gateway pattern.
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
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
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
$this->db-><functioncall>) of class
Database defined in
Gateways inherit from
/src/Modules/Core/BaseGateway.php), which provides them with the
If possible, use semantic methods like
$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
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
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
Permission classes are used to organize what actions are allowed for which user. They are a special type of transaction class.
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.
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.
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
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.
- REST controllers with the name
- (legacy) XHR controllers with the name
- (legacy) render controllers with the name
- modern render controllers with the name
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
Currently, in our source code, some code that assists controllers can be found
in classes named "Service":
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.
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.
For the new REST API, Routing happens completely through Symfony, using
@Route annotations in the respective controllers.
Everything else (the website, xhr and xhrapp) uses GET parameters to determine the controller and action to call.
src/Entrypoint for the implementations,
src/Lib/Routing.php for how the
page= GET parameter corresponds with controller class names.
Last, there are some special routes that consist of:
try_filesdirective 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