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/Controller 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) HTML controllers with the name <module>Control.php

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 a try_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.