bookmark_borderDeveloper Introduction to Maintaina Horde

This introduction is targeted at developers with little or outdated prior Horde knowledge. Having worked with any Backend Development Framework in any language can help. While a lot of these facts can be found in Wikis or previous article, others are fairly new or only relevant to the developments in the version of horde delivered via https://horde-satis.maintain.com – This article will be continuously updated as I have time or relevant questions pop up.

Setup your work environment

A ready-made docker-compose deployment can be downloaded here: https://github.com/maintaina/deployments/
A basic root project for installing horde via composer can be found here: https://github.com/maintaina-com/horde-deployment/

Either way, you should be able to connect into a running sample installation on your local desktop within less than 5 minutes. Visual Studio Code and other editors can directly connect into the container content, so there is no need for fancy mounts etc. Mind each repo’s readme files for instructions.

Horde as viewed by the composer installer

Composer is the installer used for Horde and also does most of the autoloading.

A root project should have at the very least the paths /web/ for all web visible assets, /vendor/ for dependencies and /var/ for both configuration, logs and variable data which should NOT be web-visible.

Besides the default library type, horde comes with a composer plugin to support the types horde-application, horde-theme and horde-library. The /web/ and /vendor/ dirs are under the control of composer, do NOT put any custom content there. It will be deleted with each subsequent update. This is different from the older PEAR installer which would only overwrite files with newer package content, but not remove any custom files not included in the package.

A ready-made docker-compose deployment with some default configuration can be found here:

All horde-application packages are installed to the /web/ dir.
All library, horde-library, horde-theme packages will be installed to the /vendor/ dir.
If a package is a horde-library or a horde-application and has a js/ dir, its contents are symlinked into a structure below web/js/. If a package is a horde-application or a horde-theme, specific links under the /web/themes/ dir are created on install or update. The installer plugin will search for application config files under the /var/config path and link them to /web/$app/config/. The installer will also autogenerate some files under /var/config if they are missing.

Filesystem layout

All Horde applications share the same filesystem layout.

  • /app/controllers/ contains old-style Horde_Controller request handlers.
  • /bin contains commandline scripts or cron jobs, usually prepended by the application name and without the .php suffix – these will automatically linked to the /vendor/bin path unless otherwise declared.
  • /config contains actual defaults files from the package as well as symlinks to user-provided or autogenerated configuration items. The routes.php file goes here.
  • /doc dir contains the license file, changelog.yml file, optionally a CHANGES file and any documentation in RST format. Some documentation can be autogenerated from the horde.org wiki.
  • /js contains ready-to-run Javascript code. Minifying is not necessary as it can be done by horde.
  • /lib dir contains PHP code which is usually unnamespaced and follows the PSR-0 autoloading standard.
  • /locale contains machine-readable PO translation files and the sources from which they are generated.
  • /migration contains code for automated buildup, upgrade and teardown of SQL database schema.
  • /scripts contains upgrade scripts and non-php content like LDIF files or apache config snippets.
  • /src dir contains PHP code following PSR-4 Namespaced Autoloading and PSR-12 Coding Standards.
  • /templates contains PHP and HTML templates mostly for backend-rendered content
  • /test contains unit tests and integration tests built with the phpunit framework.
  • /themes contains css and image files. See separate section on themes.
  • The top dir contains a composer.json manifest, a PEAR package.xml manifest, possibly some README.md and other control files. Traditionally, it housed all the entry points through which the browser would call into PHP code and get server-rendered pages as a result. This is no longer the recommended pattern.

Most of your newly developed code should live somewhere under /src. Libraries follow the same layout.

Web Layout

The general rule is you cannot hardcode any web-visible paths. All assumptions will eventually be wrong.
In a default installation as generated by the installer, the horde base app will be under /horde/ and the login screen will be under /horde/login.php etc. Other applications will be on the same level besides the /horde base app – /turba for the addressbook, /passwd for the password utility etc. Javascript will be visible under /js and themes under /themes – this is, unless you have configured something else. The webroot could be /tools/foo/bar rather than /.
The webmail application could reside under https://imp.foo.org/ rather than https://foo.org/imp. Themes could be presented from a totally different subdomain. The horde backend knows how to autogenerate the appropriate URLs. Your frontend should not make any assumptions.

Translation

Use the horde-translation tool to maintain translations. It generates binary .po files from a human-readable format. PHP can use these files to translate English text to your chosen frontend language. You can expose your frontend language under language-independent keys filled with the appropriate translations. You should NOT maintain translations in your frontend code as translation files can be customized by the administrator.

Themes

Horde can apply themes at runtime. Theme CSS is applied in the following order:

  • Horde global default CSS
  • Horde global custom theme css
  • App-specific default CSS
  • App-specific custom theme css (if present)

This mechanism will break if you use a build process that prepends CSS definitions with build-specific prefixes.
User-provided themes have the composer type horde-theme. The installer will link them to the web/themes folder.

Caching and minifying Javascript

Horde has a runtime javascript bundler and minifier which will bundle all javascript marked for delivery into one file and minify it. This file will be cached into the web-visible static dir. On version update, the file name will change. For this to work, you need to tell the framework which javascript files to include rather than just dish out a static html file with hardcoded paths to the individual JS files.

TODO code example

Caching and minifying themes

Horde has a runtime css minifier and bundler. All CSS will be bundled into a file and minified. This file will be cached in the web-visible static dir. If the user selects another theme, horde will minify and cache a different collection of files. For this to work, you should not hardcode paths to CSS file paths in your HTML templates or frontend code.

TODO code example

The Registry

With this amount of configurability, you need a source of truth about what goes where. This is the Horde Registry.
The registry has an index of all applications, their base web and filesystem location, which RPC and Inter-App APIs they expose, how they are represented in the topbar menu and where their Javascript and Themes resources are available in the local filesystem and as viewed from the web.

Registry Configuration

Querying the Registry

Routes, Middlewares and Handlers

Each application may have a config/routes.php file which contains web-accessible routes relative to this application and.

use Horde\Core\Middleware\AuthHordeSession;
use Horde\Core\Middleware\RedirectToLogin;
use Horde\Passwd\Middleware\RenderReactApp;
use Horde\Core\Middleware\ReturnSessionToken;
use Horde\Core\Middleware\DemandAuthenticatedUser;
use Horde\Core\Middleware\DemandSessionToken;

use Horde\Passwd\Handler\ReactInit;
use Horde\Passwd\Handler\Api\ChangePassword;

$mapper->connect(
    'Api',
    '/api/changepw',
    [
        'controller' => ChangePassword::class,
        'stack' => [
            AuthHordeSession::class,
            DemandAuthenticatedUser::class,
            // DemandSessionToken::class,
        ],
    ]
);

$mapper->connect(
    'ReactInit',
    '/react',
    [
        'controller' => ReactInit::class,
        'stack' => [
            AuthHordeSession::class,
            RedirectToLogin::class,
        ]
    ]
);

This example from a development version of the passwd app shows two routes: /react would hand out a browser page with all the boilerplate to load a Single Page Application UI (SPA). /api/changepw would receive a JSON message and, if the request is valid, change the user’s password. Each route definition contains of a name, a URL pattern and a third parameter defining the controller, the middleware stack or constraints like only processing the route for POST request. A route can also contain placeholders that can expand into variables and defaults for optional parts. Routes are processed in a first-hit-wins strategy so place your specific route definitions before the general cases. All routes are interpreted as relative to the application’s webroot.

The controller can be any string representing a class which is either a traditional Horde_Controller, a PSR-15 Request Handler or a PSR-15 Middleware. The only requirement is that the Injector needs to know how to produce it, either via Autowiring or via some explicit Binder (Factory, Implementation, Annotation …).

The Stack is either an array of strings representing PSR-15 Middlewares or a string recognized by the injector which will produce an iterable list of Middlewares. If there is no stack parameter, a default stack will be applied: Only allow requests for authenticated users identified by session cookie, forward everybody else to the login page. If you do not want any middlewares before your controller, explicitly set stack to an empty array. The middleware stack will be executed top to bottom before the controller is called and the request can be modified in that phase. If any middleware decides to answer your request by itself, no further middlewares will be called and the controller will not be executed. Responses travel back upward through the middleware stack and can be modified during that phase.

Lifecycle of a browser session

A typical user session would go like this:

  • A user navigates to webroot or any application route
  • As his browser provides no valid session cookie, he is redirected to the login page
  • The user enters credentials into the login form and posts a request. This will create a valid session cookie
  • The user is forwarded to his desired route or a default route
  • The backend sends all the HTML, CSS, graphics and javascript required to show the requested page. Javascript and CSS are each minified and bundled into cache files. Also, the framework inserts a javascript variable with dynamic data like the location of the API endpoints, chosen UI language etc.
  • User interactions either trigger ajax requests to API routes or forward him to a location outside the Single Page Application (i.e. another horde app or someplace outside). For ajax requests which change backend content, another credential besides the cookie is needed, for example a custom header with the session key or a special write token.
  • Eventually, the user logs out and his session will be invalidated

Permissions system

Administrator users

Shares

Preferences

Configuration System

Virtual Host specific configuration

Inter-App API and RPC

Special classes

ORM Layer horde/rdo

bookmark_borderCustom iCalendar data in Kronolith & Nag

The iCalendar exchange format is everywhere in Horde’s calendar (kronolith) and tasks (nag) apps. It is offered for manual import/export. It is the centerpiece of the CalDAV synchronisation protocol and various APIs of these apps. The format also plays a role in email invitiations and updates sent via iTip. It is a very powerful and well-accepted format. This is, unfortunately, a little bit of a problem. Fortunately, we also have a solution

The iCalendar format was originally defined by the IETF. They released RFC 2445 back in 1998. While Horde still understands that format, a newer version 2.0 was defined in RFC 5545 in 2009. An extension to the 2.0 standard was released in 2016: RFC 7986 defines additional attributes needed to describe related conferencing software resources or other technical aspects.

Server and client software has always been free to add custom attributes to the different components of the standard. For example, Horde Kronolith stores and reads a special attribute X-HORDE-ATTENDEE to mark event attendees who have a horde user attached. Mozilla uses attributes like X-MOZ-LASTACK and MS Exchange comes with a long list of custom attributes, among them X-CALEND. Understanding these attributes when processing a icalendar file is optional. However, servers are expected not to silently drop attributes they do not understand.

Up until now, both Kronolith and Nag did support a wide range of attributes, but far from all. Events are reconstructed from calendar backend contents. Unsupported data simply gets lost. As it is technically impossible to know all attributes anybody out there may use, the opposite definition was needed: An attribute is an “other” attribute if it is not in the list of attributes the app already handles. Both apps got a catalog of attributes they understand and a property for storing everything else. Support for actually storing and retrieving this data is currently limited to the SQL backend. I do not currently invest time in Kolab support, maybe I will never do that.

Also, while I took care to ensure that normal editing via the UI will not break the “other” data, I did not cross check with scenarios where ActiveSync is involved. Please report bugs as you find them.

CalDAV event Storage

For kronolith, an optional storage of unmodified caldav events as received has been added during development. I am not sure if I will keep this or remove it again. It is useful for debugging various support scenarios but under normal operation, it is not required. It may be useful for creating a cache of external caldav calendars though.

Extend to Turba Addressbook

Turba Addressbook uses a similar data format vcard and implements the sister protocol CardDAV. Things are indeed a little more complicated. There are three relevant versions of vcard 2.1, 3.0 and 4.0 and multiple extension RFCs with additional contact attributes exist. Worse, there is a host of X- attributes from popular desktop addressbook vendors, Evolution, Kontact, Thunderbird, Android, MS Exchange/Outlook… There are at least two different ways to represent contact groups and the whole thing is a little messy. But the real problem lies with Turba’s highly configurable nature. At least two backends need to be upgraded: Both SQL storage and LDAP play a key role in typical Turba use cases. The exact layout of each addressbook can be different even inside the same installation. The list of vcard attributes considered as natively supported needs to be calculated for each addressbook and additional attributes maybe need to be stored outside of the specific backend. This will take some time and consideration.

Still, Turba’s tendency to forget what it does not care for needs to be addressed. Otherwise users risk losing attributes on sync.

bookmark_borderMaintaina Horde switches to openSUSE LEAP

Our Horde docker images have switched over from Tumbleweed to openSUSE LEAP once again.

Recently our container build CI job in github.com broke down unexpectedly. An investigation showed that Tumbleweed’s core libraries, especially libc, were too new for the CI’s build system, based on Ubuntu LTS.

This is the second time we abandoned the Tumbleweed basis for Horde docker containers. OpenSUSE Leap 15.3 uses a relatively old, but well-maintained, set of base libraries. Both Leap and Tumbleweed deliver PHP 7.4 as a basis for Horde. In both systems, we skip the packaged composer version for a static pick which we will update from time to time. We may switch over to packaged composer if we feel confident.

For users and administrators of the image, both Tumbleweed and Leap 15.3 should feel more or less the same. For end users of the delivered horde setup, there should not be any downsides. We will switch back to the Tumbleweed image in a while when we have picked a more recent version of Ubuntu.

bookmark_borderCan Horde’s internal API use PSR-7 Messages?

The Horde Inter-App system has been around since Horde 3.

Horde Inter-App messages are addressed by a two part string. The first part, followed by a slash character, is called the API. The second part after the slash is called the method. Registry can delegate complete APIs to an application or a list of complete API/method strings. In the latter format, a certain API/method combination can be assigned to one app even if the API in general is assigned to another app. The API is implemented by a class $application_Api in file Api.php inside each horde application. That application class methods and their signatures are the methods exposed by the Inter-App API. There are some meta arrays controlling further details but let’s ignore them. All but a few APIs only take arrays and primitives (string, number, bool) as parameters and issue them as return types. This is because the RPC layer eventually receives and emits HTTP messages, which are just text. Only those Inter-App API methods which are meant to be strictly internal will consume and emit PHP Objects.

PSR-7 messages and PSR-15 handlers/middlewares are an interop standard. They do not make a lot of assumptions about the underlying implementation. They have been used to implement REST solutions as well as old style server-driven dynamic websites. The request objects contain an URI to a resource and will eventually result in a response object. In between there is usually a broker piece called a router, which analysis URI and other request parameter to assign it to the proper implementation code or chain of code pieces, called middlewares. Anywhere in that chain, an answer is created and sent back to the caller. The request and response bodies are streams of text or binary data, as the request and response are essentially text messages.

At first glance, this is an easy match. The Registry mediates between the message sent by the caller and the code which handles it. We could call it the router. The API class with its methods could be seen as a set of handlers. The PSR-7 ServerRequest object represents a HTTP request, but it also allows arbitrary attributes attached to the actual request data. These attributes may be any PHP value, including objects.

There are some details to keep in mind though.

The inter-app API has little definition on a data contract. It predates PHP method parameter types and return types. In traditional code, users could feed just about anything into the inter-app API and the implementation would need to guard against any value expected or unexpected. Inter-App API just assumes the caller is eligible to call. Authentication is delegated to the existing PHP session or to the RPC setup, authorization control must happen in the called code. That may lead to bloated, repetitive code in the implementation.

As each app has only one API class file, it is not currently possible to implement two different methods on different APIs if the same app handles it. If you have two different APIs clients/get and contracts/get and both are implemented in the same app, they will end up in the same code path. The way around it is with calls like clients/getClients and contracts/getContracts, but this is just ugly.

The rampage/routes/http_server stack can easily discern a GET /clients/ call and a GET /contracts call, but it only works inside a specific app. Setting up a separate API set of routes, we can easily have calls abstracted from the implementing app. The system of handlers and middlewares allows to delegate authentication and authorization checks outside of the actual implementation of an endpoint. This reduces repetitive boilerplate. One big issue remains. As of now, Inter-App can return native PHP objects to the caller. PSR-7 messages allow Attributes on the ServerRequestInterface but there is no equivalent in the ResponseInterface. Inter-App can carry objects (for internal calls) or serialisation-friendly nested arrays until it hits the RPC layer. This layer will turn it into a text structure, say XML or JSON. How would we do that in ResponseInterface implementations? How would that make the implementation reusable for a REST interface, app-internal AJAX or other code?

A vision of convergence

Bringing together new capabilities and existing system participants is tricky. A new RPC and Inter-App system should integrate with the old interface, it should not just stand beside it. Having two different Inter-App layers would be confusing, abandoning the old one right now would be unnecessary stress on developers’ time budgets.

As an inter-app user, I want to use $registry->call(‘method’, [params1, param2]) or $registry->api->method($param1, $param2…) as I did before.

As an RPC user, I want to call \Horde_Rpc::request('api/method', $params, $options) as I did before. I do not care what happens in the background.

As an application developer exposing an API, I do not want to give up Api.php right now, it has to work with the new stack as good or bad as it did before.

As a developer of new apps and features, I want to leverage extended capabilities. I want to be able to implement two distinct APIs using the same method names. I want to be able to return native objects, even serialisation-unfriendly ones with php resources, along with a serialisation-friendly message to use in RPC, Rest or other HTTP use cases. I do not want to be restricted to two levels of API/method. I want to re-use middleware I already built for the frontend AJAY.

As a distributed app developer, I want to define API resources and have them served either internally or by external microservices transparantly over http requests.

For the future, I would like some degree of introspection and possibly some guidance on allowed or required request parameters.

As an integrator, I want to securely communicate with only partially set up horde instances to finish or upgrade setups by firing HTTP requests.

Implementation approach

The horde-deployment project includes a route from webroot/api/ to a global API router managed by the horde base app. This API router is first populated by the Registry and then supplemented by a config/routes.api.php file in each registry app.

Regardless of the calling context, a cascade of middlewares sets attributes for the called API/route, the parameters, the resolved implementation and the outcome of already happened authentication checks. The implementation is either an adapter middleware calling an app’s Api class or an actual implementation middleware/stack. It will write the return values and other state into an attribute digested by the bottom of stack. In case of Inter-App, a token response is returned and the actual data structures are taken from the handler and returned to the caller. Real RPC backends generate appropriate headers and stream body for response. The response can possibly be processed further as it returns back to top of stack, for example gzip compressed or logged or trigger metrics updates.

bookmark_borderAuthentication & Authorization is complex

Could there be any more straight forward topic than authentication & authorization? The user provides user name and password and clicks “login”, the backend checks if credentials are valid. Invalid credentials are not authorized, valid credentials are authorized and identified (authenticated). End of story. Right? Well… in many cases, it’s not that trivial.

As a user, I want to be informed if I have to change my password soon.

As a security officer, I want accounts blocked for some time after a certain amount of failed login attempts. I also want passwords to expire after a certain time. I also want login sessions to expire if client IP address or browser identity changes.

As an integrator, I want to enhance the system to digest certificates, Shibboleth, SAML or OpenID Connect, Bearer Tokens, JWTs or even Kerberos Tickets.

As a support person, I want to silently normalize user login names, lowercase them or append domain names to login names.

As a usability consultant, I want to leverage the user database for UI, make user names searchable and browseable.

As a site administrator, I want to be able to filter out certain or most users from the backend even if they provide valid credentials or block login for all but a few users, i.e. for site maintenance.

As an innovator I want to join user bases from two different authentication sources and possibly migrate them on next login seamlessly, without them noticing.

As a sales person, I want to allow a limited guest user experience prior to login rather than force everbody to the login screen.

As a returning user, I want to transparently log in through a remember me cookie, but maybe make the application aware of that limited trust, asking for real login for sensitive operations.

As a developer, I want to be flexible and allow any combination of criteria. Users may login with a global password or a purpose-limited token, I want them to use a second factor like TOTP if they have set up one but pass if they haven’t, unless I don’t allow it.

For business reasons, I want to rate limit API access per hour, per day and per month with individual thresholds each.

As an auditor, I want each request’s authorization process to be logged for evidence.

There is a lot more to consider, but I will stop here.

Authentication is any means of making sure of a requester’s identity. The most common practice in computers asking for a username (identity) and a password (proof). Another common practice is asking some external authority we trust, an Identity Provider. When we look at a person’s passport to compare his photo or fingerprint with his actual face or finger, this involves the same aspect: The name written on the passport for identity. The photo or fingerprint for proof. And, implicitly, we trust the party who created the passport (Identity Provider) and maybe have some means to check the integrity of the document. But if somebody has no passport but a driver’s license, a club membership card, we might instead use this for verification.

Transparent Authentication is a special case where we can identify the user without explicitly interacting with him. This can be achieved whenever the request carries credible identifying information like a pre-established cookie, a certificate, a passport token whose integrity can be validated or other methods.

Authorization is any decision making if a requester has access to a resource. Simply being authenticated might not be enough. Guests without authentication may be eligible for certain areas of your application, but maybe not if they are from a certain IP range or country. A person may be too young or too old to use a certain facility. A person may be old enough to buy alcohol but cannot currently present a sufficient document to proof this. On the other hand, the bearer of a ticket may be eligible to visit some concert, with no interest in his actual identity. A software user may need to both be authenticated and part of a certain privilege group “administrators” to access a configuration screen.

Both requirements can be linked to each other, as well as all those aspects mentioned above.

In another article I will look at how Horde does it and discuss if this approach is still right for modern use cases.

bookmark_borderCardDAV: What is Turba’s true data model?

Turba Addressbook imports, exports and syncs to and from many formats. I discussed all the formats in a previous article.

You can even customize the addressbooks, make them contain extra fields or omit common fields. Saving and loading data from the addressbook backend is in some way one more such conversion. Turba can actually read and write to SQL and LDAP backends, to the Preferences System and to an outdated version of the Kolab Groupware Server and IMSP Servers. It can derive Addressbooks from Horde Groups, Favourite Recipients (usually from IMP) and stored search queries (virtual addressbooks).

This diversity of backends and configuration options comes at a cost. It is sometimes not clear what is the data model and who owns the data. This complicates synchronisation scenarios like CardDAV.

Let’s look at this in detail. LDAP backends might be considered the owner of data and Turba would be just a view to that data or a client with edit permissions. LDAP would be able to change data, add new contacts, delete contacts or change contact details without turba even knowing. This is less of a problem with the SQL driver as the SQL schema of Turba should never be consumed directly, rather through turba’s API or exchange formats. Groups are a primary culprit of not telling Turba any changes.

On the other hand, sync protocols may contain data the current turba configuration or backend does not recognize. CardDAV and its data format vCard may contain a wealth of properties and allow custom extensions fields. CardDAV servers are supposed not to simply drop/forget fields they do not recognize because that could make the client forget fields in the data it actually supports. On the other hand, a client may send an update of a contact missing a property which is stored in a previous version of the contact. We need to understand if the client simply cannot handle that property or if it is supposed to remove that property. Well-behaved clients should not strip properties they do not understand and well-behaved servers should not do that either.

Turba may support contact information which cannot be mapped to CardDAV/vCard but I am currently unaware of any. As vCard allows custom fields, at the moment I consider vCard the lingua franca.

Another tough scenario are references to other contacts or resources. CardDAV/vCard support multiple ways to indicate these and the backend may support a completely different model of referencing. How should we react on references to contacts which we do not (yet) have? Many of these problems also affect CalDAV sync in the kronolith calendar and nag tasks app.

I am ignoring ActiveSync and SyncML because I do no longer possess any syncml capable device and I have limited understanding of the ActiveSync code. It seems to have a static mapping between turba internal field names and ActiveSync field names.

To resolve all these issues, Turba needs to have its own data model and it needs to store synced data its backend is not interested in. It must not lose or forget any information when syncing back to the client. This is currently not the case.

bookmark_borderHorde’s HTTP component goes PSR

This weekend, I gave the horde/http component a some major redesign. See how things escalated. Oh my.
My minimum goals were namespacing, PSR-4 (Revised Autoloading Standard) and some minor, schematic adjustments. The final result is quite different. I ended up implementing PSR-7 (HTTP Message Interface), PSR-17 (HTTP Factories) and PSR-18 (HTTP Client). The code largely complies with PSR-12 (Extended Coding Style Guide) and thus, implicitly, PSR-1 (Basic Coding Standard). I am sure you will find some deviations and issues, so I welcome any Pull Requests against my repo. You will find the new code in /src/. The original, incompatible implementation is untouched and resides in /lib/. They can coexist as they are so different (namespaced vs unnamespaced, among others).

This is not a total rewrite. I could leverage most of the existing code base with some tweaks. This work would not exist without the foundation by Chuck Hagenbuch and all the contributions from the different Horde maintainers over the years. You will also notice similarities to other php PSR-7/18 implementations out there. I checked out Guzzle, httplug/php-http and some others. It was a great learning experience and I will not pretend I am not influenced by it.

As with all my modernisation activities, I made use of features allowed by PHP 7.4. This excludes Constructor Property Promotion and, sadly, Union Types as both are PHP 8. Union Types have been relegated to phpdoc annotations or check methods. Please mind most of the PSR’s target compatibility with PHP releases older than PHP 7.4 and thus do not sport return types or scalar type hints. I followed these signatures where applicable.

One major change between the old codebase and the new one is clients and Request/Response classes.
In the old implementation, there would be one client but different Request/Response implementations using different backing technologies like pecl/http, fopen or curl. The new implementation moves transport code to clients implementing PSR-18. Optionally, they can be wrapped by a Horde\Http\HordeClientWrapper which exposes the PSR-18 itself, but otherwise mimics the old Horde_Http_Client class.

Horde/http is used by very different parts of Horde, including the horde/dav adapter to SabreDAV, various service integrations (Gravatar, Twitter, …), the horde/feed library and application code all over the place. I intend to upgrade those use cases to the new implementation. I am looking forward to criticism or acceptance of that approach.

The goal of this project is more far-reaching. While Horde 4 and Horde 5 already had horde/controller, they made very limited use of it. In my non-public projects, I relied heavily on controllers and I made several attempts at improving the way controllers are set up in horde/core. However, I always felt the results were clunky and not really what I wished to achieve. While horde/controller knows prefilters and postfilters, these are not easy to use and there are few examples. While doing research, I made up my mind. I want to replace Controller/Prefilter/Postfilter with their PSR-15 (HTTP Handlers and Middleware) equivalents. Controllers will be Handlers, Pre/Postfilters will be Middlewares. Together they will be stacks. Authentication, Authorization, Logging etc will be relegated to Middlewares. There will be a default stack to mimic the default controller behaviour in Horde 5 (Be authenticated or be relegated to the login page). You will be able to define application-specific default stacks or request-specific stacks. As Middlewares are a public standard, we might be able to leverage middlewares existing out in the wild or attract microframework users to some horde built middlewares. I want to make it easier for coders to build horde apps without relearning everything they needed to learn for laravel, laminas or symfony. I also want to make it easier for everyone to cooperate. Horde is among the oldest framework vendors, predating most of PEAR and Zend. I think we still have some bits to offer.

Missing Bits:

  • While I did some implementation of UploadedFileInterface, it is still quite basic
  • UploadedFileFactoryInterface is missing as I have not yet built the server side use cases
  • Unit Tests need to be adapted to the new code base. Is there some PSR Acid Test out there?
  • I began implementing the ext-http (PECL_HTTP) backend but stopped as I am unsure about it. That extension is in version 4 now and still services version 3, but we have backends for versions 1 and 2. I need to learn more about it and decice if it makes sense to invest into that aspect.

bookmark_borderHorde/Rdo ORM: PSR-4 and BC Breaks

Summary: Horde/Rdo ORM got upgraded for Namespaces. User code conversion is straight-forward. Backward Compatibility is limited.

If you ever wondered, RDO stands for Rampage Data Objects. This has been on my list for quite long, but it took some time to get it right. The horde/rdo library is horde’s Object Relational Mapping (ORM) solution. It allows you to store objects into sql databases or retrieve them without writing SQL. Originally, it has been written by Chuck Hagenbuch way back in the PHP 4 days and I have been a heavy user for years. If you know Laravel’s Eloquent, Doctrine, Hibernate, ActiveRecord, nHydrate or Dapper, this one is Chuck’s “as light as possible” implementation of the concept. Colleagues from B1 Systems have been users and contributors over the years. However, it has long been time to rethink Rdo in the light of newer capabilities of PHP 7.4 or even PHP 8. This time is now.

But you should not fear upgrading. First, the library still keeps the unnamespaced PSR-0 code, at least for the time being. Second, there is a straight-forward upgrade path for existing users.

Horde_Rdo_Base -> Horde\Rdo\Base
Horde_Rdo_Mapper -> Horde\Rdo\BaseMapper
Horde_Rdo_Factory -> Horde\Rdo\Factory
Horde_Rdo_List -> Horde\Rdo\DefaultList
Horde_Rdo_Iterator -> Horde\Rdo\DefaultIterator
Horde_Rdo_Query -> Horde\Rdo\DefaultQuery
Horde_Rdo_Exception -> Horde\Rdo\RdoException
Horde_Rdo:: -> Horde\Rdo\Constants::

It’s about as easy as it looks. Converting an application took me a few minutes. You might have noticed the names do not exactly match. Some names were not practical to simply turn into namespaced classes. In other cases, I turned class names into interface names. I found myself implementing the same enhancements over and over in multiple projects and I found myself wishing there was an easy way to do some others.

Less Boilerplate Mappers and Entities

Rdo is much more fun than some other ORMs as it comes with very little configuration. The library autodetects properties from the table columns. Datetime fields are automatically cast to Horde_Date objects. By default, Rdo tries for a convention over configuration approach for mapping table names, mappers, entities etc. Unfortunately, for most of my projects, that default does not fit too well to the class structure and file layout I choose. But still, implementing a new pair of mappers and entities takes two files and only two or three settings I ever need to think about.

Most often, subclassing the base mapper and the base entity is the right thing to do. But sometimes, you do not really care. If all you ever do to your entity is call ->toArray() and serialize it to json, you would be served very well by a generic entity instead. This is something on my list. I would even go one step further: If all you are changing to a mapper is subclassing and telling it the name of its database table and entity class, why subclass at all? Yes, I would want to turn the optional Factory class into something smarter. It will give you your mapper, be it an instance of the generic mapper with the right table name or something very customized.

Custom List Objects

Rdo queries always return a Horde\Rdo\List. This is a good default implementation and it makes common tasks easy. However, there are situations where you want your list of entities to be specific to an entity type or maybe a subclass of some base list class completely external to Rdo. Maybe you want to manipulate a list or merge results from two different queries.

Custom Entities
Sometimes the default entity implementation does not serve you well. There’s a range of things you would want.
– You want to inherit from a base class to attach behavior to your data. So you attach an interface and a trait to that foreign class to make it Rdo aware.
– You want to implement your own behaviour altogether
– You want Rdo to implement a proper repository with strongly encapsulated, less chatty domain objects. Rdo should provide a mechanism to produce those objects for you rather than having you cast or wrap Rdo\Base objects into your actual models. But it should not force you to think about such concepts before you really need them.

NoSql backends
Remember the name Rampage Data Objects? Rdo is mostly about mapping data to objects. It’s not about autogenerating the smartest SQL for the most obscure use cases. But once you have your prototype version ready, your first feedback comes in, you think about new features – and suddenly you want to support a new backend for some of your domain objects. Be it a NoSql database, a key/value store, a limited scope within a directory like LDAP or even a REST service. In a traditional horde application, you would wrap Rdo into a Driver called Driver/Rdo or Driver/Sql and implement a different backend. But what if you do not want to flip all your data to the new backend? Only the shopping list should go to the nosql backend but not the customers or the product inventory? You end up implementing individual drivers with individual backends. But you used Rdo’s relations feature … things get messy.

To achieve these capabilities, I want to make the Mapper less dominant. The formerly optional Factory gets promoted to take care of managing the right mappers, entities, backends, list types. This is what these interfaces are for. Mappers should mostly take care of mapping between an object class and a plain data format. Currently the mapper and query do too much, tightly coupled with the single mandatory list type. This will change.

Rampage Data Objects provides out of the box defaults for easy and common use cases. It gets you started really quick. We will add the capabilities needed when your application is maturing and your use cases get more demanding. This will be fun.

Backward Compatibility Breaks
The Horde\Rdo\Base* classes and their return types will be your best bet for backward compatibility. If you don’t try to use entities and mappers for side effects, you will be very safe. Factory’s constructor will change very soon. Factory should best be created from a Dependency Injector. Mappers should be created from Factory.

You should not rely on mappers exposing adapter or factory for creating other objects. Also, trying to manipulate sql session modes or transactions through Rdo’s adapter is not a good idea.

bookmark_borderWhy extending PHPUnit might be wrong

Over the last few months, I spent a lot of cold winter evening hours looking into porting ancient PHPUnit 4.x test suites over to PHPUnit 9.x. The test suites, you guessed it, belong to the Horde framework. Horde actually does not just use phpunit but wraps it into its own testing library horde/test. The full details are explained on the wiki. Horde provides multiple ways to actually run the unit tests, either through its components helper or through AllTests.php or through calling individual tests with phpunit. While horde/test adds value and allows simplifying some test scenarios, it also has its own problems.

I’m not saying it didn’t make sense back when this was built. The original alpha release of horde/test dates back to 2011. PHP 5.4 was not yet released, composer would see its initial release in 2012, PEAR was slowly getting old and PEAR2 was not yet officially a dead cow. It was a totally different ecosystem back then. However, times have changed.

As of 2021, phpunit has evolved into a relatively fast moving target and while its primary author Sebastian Bergmann does not break backward compatibility for the sake of it, he is not shy of doing it either. Some parts of phpunit which are relevant for integration may become unavailable in the next major release. Some of PHPUnit’s core classes are clearly marked as internal.

The Horde Test library adds some mandatory boilerplate to phpunit: Each library and app has its own mandatory bootstrap.php file calling into the test library’s bootstrap class and may have an additional autoload file. Also, a test suite should come with an AllTests.php file calling into the test package’s AllTests class and in turn using Horde_Test_AllTests_TestRunner – which extends PHPUnit\Runner\BaseTestRunner but wait…

https://github.com/sebastianbergmann/phpunit/blob/9.5/src/Runner/BaseTestRunner.php

/**
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
 */
abstract class BaseTestRunner

Trouble ahead, maybe. I am not joking. The master branch which will become PHPUnit 10 does not even have that file. We can find a solution for that, sure. But maybe we should not. As of 2021, the horde/test suite contains valuable helpers and extensions to PHPUnit, but none of these really need to hook into phpunit’s core that deep anymore.

Let’s look into different issues covered by that code.

Autoloading and dependency setup

Back in 2011 it made sense to have some glue which combines autoloading with Horde’s and PEAR’s notions of how things should be organized in a file system. That is no longer a core concern. Everybody and his dog use composer and composer’s autoloader and either do PSR-0 or PSR-4 autoloading schemes. Horde 6 will be delivered via composer, does – optionally, partially – break with some older ideas of organisation, brings some PSR-4 code and a lot of PSR-0 code. In short, there is very limited need for a custom autoloading scheme. The test suite should just rely on some autoloading being setup, by whoever or whatever. It should not address this concern beyond the means already provided by phpunit itself. Code should be accessed through checking if class names are either present or loadable, not by assuming some files are in some location. Of course, this is a little simplified – at its core, all autoloading depends on something being in some wellknown location. We should simply rely on the default solution until it is not possible for a specific case – and then address that. Even if we accept the notion of a vendor dir and an autoload file in vendor/autoload.php, we could provision it via horde/components or horde/git-tools or some horde/test utility for the rare cases where just using composer would not be appropriate.

Setting up complex test cases

Tests are supposed to be simple. They should focus on one unit under test and keep as much of the ecosystem as possible out of the picture. Dependencies should be stubbed or mocked. Databases and I/O, especially network traffic, should be substituted. A majority of test cases should be modeled with just the library’s own code and PHPUnit’s mocking facilities, maybe depending on some interfaces from some dependency.

But sometimes it’s not so easy. Sometimes the unit under test actually IS code interfacing with databases and their subtle differences. Sometimes the code needs to interact with a non-trivial amount of configuration and dependencies. Especially code which interacts with the framework’s core services or which couples the application’s subsystems may not be easy to mock. It makes sense to provide a simplified pretend environment for such test subjects. But that should be opt-in on a case by case basis. It should not be a mandatory tie-in of additional code and boilerplate for even the most trivial test cases.

Integration with tools

horde/components and some other tools wrap phpunit and other utilities. But as the ecosystem changes, the benefits are shrinking. Github Actions and Gitlab CI have become popular platforms with many ready-made CI tools available to use. It is no longer necessary to run your own Hudson or Jenkins and build your own automation frameworks. While it is still nice to have a short way to run a test suite including dead code and copy/paste detection, coding style fixers etc without the need to check anything into the SCM or even create a commit, there’s little incentive for maintaining a deep integration into your own runtime code. In the end, all testing and quality assurance automation aims to make you deliver safe, stable code as fast as possible. Spending ours writing automated tests makes sense and may safe you from hours of debugging and frustration. Spending hours writing or fixing a deep integration into some test tool which simply does not want to be integrated? Less so. It should be kept to a minimum. There’s a reason why the preferred delivery of some of these tools is not composer but phar.

What can you do?

So what would a new test toolchain look like? Unit tests and even integration tests should rely on a default, external autoloading solution. In the absense of configuration, this should be vendor/autoload.php, however it is delivered.
Unit tests should by default just run off PHPUnit and the library’s code. Maybe it makes sense to provide the most widespread interfaces without actually having to install their backing code. Why not, as long as it does not create manual effort. Any framework-specific recurring need should be addressed by opt-in code provided as traits or helper classes, available through default development-time autoloading sources. Fringe tests relying on specific infrastructure should skip without failing. Configurable tests should run out of the box in a useful default configuration if it makes sense. These changes can be created in an incremental, opt-in fashion with very limited BC breaks. This is good as nobody has time to waste on large scale transformation. Remember, it’s all about your code, the tests are just a useful tool.

bookmark_borderPEAR down – Taking Horde to Composer

Since Horde 4, the Horde ecosystem heavily relied on the PEAR infrastructure. Sadly, this infrastructure is in bad health. It’s time to add alternatives.

Everybody has noticed the recent PEAR break-in.

A security breach has been found on the http://pear.php.net webserver, with a tainted go-pear.phar discovered. The PEAR website itself has been disabled until a known clean site can be rebuilt. A more detailed announcement will be on the PEAR Blog once it’s back online. If you have downloaded this go-pear.phar in the past six months, you should get a new copy of the same release version from GitHub (pear/pearweb_phars) and compare file hashes. If different, you may have the infected file.

While I am writing these lines, pear.php.net is down. Retrieval links for individual pear packages are down. Installation of pear packages is still possible from private mirrors or linux software distribution packages (openSUSE, Debian, Ubuntu). Separate pear servers like pear.horde.org are not directly affected. However, a lot of pear software relies on one or many libraries from pear.php.net – it’s a tough situation. A lot of software projects have moved on to composer, an alternative solution to dependency distribution. However, some composer projects have dependency on PEAR channels.

I am currently submitting some changes to Horde upstream to make Horde libs (both released and from git) more usable from composer projects.
Short-term goal is making use of some highlight libraries easier in other contexts. For example, Horde_ActiveSync and Horde_Mail, Horde_Smtp, Horde_Imap_Client are really shiny. I use Horde_Date so much I even introduced it in some non-horde software – even though most functionality is also somewhere in php native classes.

The ultimate goal however is to enable horde groupware installations out of composer. This requires more work to be done. There are several issues.

  • The db migration tool checks for some pear path settings during runtime https://github.com/horde/Core/pull/2 Most likely there are other code paths which need to be addressed.
  • Horde Libraries should not be web readable but horde apps should be in a web accessible structure. Traditionally, they are installed below the base application (“horde dir”) but they can also be installed to separate dirs.
  • Some libraries like Horde_Core contain files like javascript packages which need to be moved or linked to a location inside another package. Traditionally, this is handled either by the “git-tools” tool linking the code directory to a separate web directory or by pear placing various parts of the package to different root paths. Composer doesn’t have that out of the box.

Horde already has been generating composer manifest files for quite a while. Unfortunately, they were thin wrappers around the existing pear channel. The original generator even took all package information from the pear manifest file (package.xml) and converted it. Which means, it relied on a working pear installation. I wrote an alternative implementation which directly converts from .horde.yml to composer.json – Calling the packages by their composer-native names. As horde packages have not been released on packagist yet, the composer manifest also includes repository links to the relevant git repository. This should later be disabled for releases and only turned on in master/head scenarios. Releases should be pulled from packagist authority, which is much faster and less reliant on existing repository layouts. https://github.com/horde/components/pull/3

To address the open points, composer needs to be amended. I currently generate the manifests using package types “horde-library” and “horde-application” – I also added a package type “horde-theme” for which no precedent exists yet. Composer doesn’t understand these types unless one adds an installer plugin https://github.com/maintaina-com/installers. Once completed and accepted, this should be upstreamed into composer/installers. The plugin currently handles installing apps to appropriate places rather than /vendor/ – however, I think we should avoid having a super-special case “horde-base” and default to installing apps directly below the project dir. Horde base should also live on the same hierarchy. This needs some additional tools and autoconfiguration to make it convenient. Still much way to go.

That said, I don’t think pear support should be dropped anytime soon. It’s the most sensible way for distribution packaging php software. As long as we can bear the cost involved in keeping it up, we should try.