Skip to main content

Developer’s guide to upgrading from webtrees 2.2 to 2.3

This document describes the breaking changes and notable API differences between webtrees 2.2.x and 2.3.x that affect authors of third-party modules.

jQuery has been removed

None of our dependencies require jQuery, and our own code has been rewritten to use “vanillaJS”. So jQuery has been removed from our vendor.js bundle.

Routing and request handlers

To respond to HTTP requests, a module needs to registar itself as a handler for the URL, and provide methods to respond to requests.

To register a URL, the syntax has changed slightly.

  • Old: Registry::routeFactory()->routeMap()->get($name, $url, $handler)
  • New: Registry::routeFactory()->routeMap()->add($url, $handler, $optional_middleware)

To handle the request, we now have separate methods for each HTTP verb (GET, POST, PUT, DELETE, etc.)

  • Old: public function handle(ServerRequestInterface $request): ResponseInterface
  • New: public function get(...$params): ResponseInterface

The old ServerRequestInterface method will continue to work. However, you can instead create methods ->get(), ->post(), ->delete(), etc. which accept requests for that HTTP method. These methods can now typehint their parameters, and have them injected directly.

e.g. public function get(Tree $tree, Individual $xref, int $generations, string $layout, Sex $sex)

Time and Timestamps

Our Timestamp class is replaced by CarbonImmutable.
Our TimeFactory class is replaced by PSR-20 ClockInterface.

// Create a timestamp
$timestamp = Registry::timestampFactory()->fromEpoch(time());
$timestamp = Registry::timestampFactory()->now();
$timestamp = Registry::timestampFactory()->fromString($date_string);

// Use CarbonImmutable methods directly
$timestamp->diffForHumans();
$timestamp->format('Y-m-d');
$timestamp->addDays(7);

// Julian day is now available from the timestamp factory, rather than a timestamp
$julian_day = Registry::timestampFactory()->todayJulianDay();

HTTP requests

Guzzle is replaced by PSR-18 ClientInterface, with the symfony/http-client implementation.

$http_client     = Registry::container()->get(ClientInterface::class);
$request_factory = Registry::container()->get(RequestFactoryInterface::class);
$request         = $request_factory->createRequest('GET', 'https://example.com/api');

try {
    $response = $http_client->sendRequest($request);
} catch (ClientExceptionInterface) {
    // Handle connection failure
}

Languages and localisation

Our language modules have been replaced with language classes, in /app/I18N. Logic from the package fisharebest/localization has been imported.

Previously, we used translation to generate dates and relationship names. These are now generated by the language classes, using custom logic for each language.

The main updates to I18N are;

  • I18N::language(): LanguageInterface the current language - replaces ->locale()
  • I18N::languageTag() e.g. en-US
  • I18N::formatDate($date)
  • I18N::formatList($array) - ['red','green','blue'] => 'red, green, blue'
  • I18N::formatListAnd($array) - ['red','green','blue'] => 'red, green and blue'
  • I18N::formatListOr($array) - ['red','green','blue'] => 'red, green or blue'

The translator/translation functions are rewritten. To create an array of translations from a .PO or .MO file, use the following.

$stream       = fopen($po_file, 'rb');
$translations = Translation::fromPoStream($stream)->toArray();
fclose($stream);

Generating thumbnail images

We no longer use intervention/image. Instead, we call GD functions directly. ImageMagick is no longer needed or supported, as GD is faster and requires less memory. ImageFactoryInterface has been simplified. Generating watermarks is (will soon be) a separate concern.

HTML editors

We replaced CKEditor4 with TinyMCE. TinyMCE is simpler and more lightweight.

For server-side filtering, ezyang/htmlpurifier was abandoned, and is replaced by symfony/html-sanitizer.

PSR-11 Container

Our container now supports lazy instantiation.

$container->bind($class_or_interface_name, $concrete_class_name);

New Enums

We now make extensive use of Enums. See /app/Enums/...

The major ones are:

Restriction - replaces the strings 'PRIVACY', 'CONFIDENTIAL', 'LOCKED', etc.
UserRole - replaces the constant strings 'none', 'access', 'edit', 'accept', 'admin'
AccessLevel - replaces the constant integers -1, 0, 1, 2

To check whether a user has permission to do something, change

$user_access_level = Auth::accessLevel($user);
$required_access_level = (int) $tree->getPreference('CAN_DO_SOMETHING');
if ($required_access_level >= $user_access_level) {...}

to

$user_access_level = Auth::accessLevel($user);
$required_access_level = AccessLevel::fromTreePreference($tree, 'CAN_DO_SOMETHING');
if ($required_access_level->allows($user_access_level) {}

Sorting records

To sort records by one of its properties or methods, we have new helpers

  • usort($family_array, FamilyComparator::byMarriageDate());
  • usort($individual_array, IndividualComparator::byBirthDate()); etc.

CLI tools and build scripts

Composer scripts have been updated. Run composer list to see them.
webtrees CLI scripts have been updated. Run php index.php list to see them.

Edit this page