Self-hosted service
wapmorgan/Morphos avatar
wapmorgan/Morphos

Morphos inflects Russian and English words in pure PHP

A morphological solution for Russian and English language written completely in PHP. Provides classes to inflect personal names, geographical names, decline and pluralize nouns, generate cardinal and ordinal numerals, spell out money amounts and time intervals. / Морфологическая библиотека для английского и русского языков. Склоняет имена собственные, названия географических объектов, склонение и плюрализация имен собственных и другое.

831 stars111 forksPHPMIT

At a glance

What is it?
Morphos is a PHP library for morphology, handling Russian name and place declension, noun pluralisation, cardinal and ordinal numerals, money amounts and time intervals in both Russian and English. It also ships as a container so non PHP stacks can call the same rules over HTTP.
Who is it for?
Morphos suits a PHP product that has to produce correct Russian grammatical forms, where hand written rules would be a maintenance trap, and the same library covers English pluralisation, numerals and time intervals for a multilingual stack. It is the wrong tool if you need morphology for a language other than Russian or English, or if you want a general purpose translation layer, because the scope is grammar, not meaning.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 46 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Declension is the problem this solves

Morphos is described as a morphological solution for Russian and English, written completely in PHP and delivered either as a PHP library or as a Docker image so it can be integrated into a non PHP stack.

The problem it addresses is grammatical case. In Russian, a noun changes form depending on its role in the sentence, and the rules are regular enough to be worth encoding and irregular enough that nobody wants to type them by hand. If a product shows a person's name in a list, then again in a sentence, then inside a document title, each of those positions can require a different form of the surname, the given name and the patronymic.

The feature list names the scope precisely. Inflection of personal names in Russian, which covers the surname, the given name and the patronymic. Inflection of geographical names, which covers countries and cities. Declension and pluralisation of nouns and adjectives in Russian and English. Generation of cardinal and ordinal numerals in both languages. Spelling out amounts of money in Russian. And spelling out time units and intervals in both.

The audience is developers building content, documents, invoices or notifications in Russian, or building something multilingual that happens to include Russian. The licence is MIT and the package is wapmorgan/morphos on Packagist.

The Russian and English APIs are parallel but not identical

The quick start shows both languages side by side, and the difference in shape is the first thing to notice. The Russian examples use named functions and static classes under a Russian namespace, and the English examples use the same pattern under an English one.

The Russian quick start covers name inflection, geographical name inflection through a dedicated class, pluralisation, the two numeral generators, and a time speller:

php
morphos\Russian\inflectName('Иванов Петр', 'родительный') => 'Иванова Петра'

morphos\Russian\GeographicalNamesInflection::getCase('Москва', 'родительный') => 'Москвы'

morphos\Russian\pluralize(10, 'новый дом') => '10 новых домов'

The English side is deliberately smaller, and the difference is a fact about the language rather than about the implementation. English nouns pluralise, but English nouns have no case, and personal name inflection is listed as a Russian feature only:

php
morphos\English\pluralize(10, 'house') => '10 houses'

morphos\English\CardinalNumeralGenerator::generate(567) => 'five hundred sixty-seven'

morphos\English\OrdinalNumeralGenerator::generate(961) => 'nine hundred sixty-first'

The Russian numeral generators are static methods taking a case, while the English ones take only the number, because there is no case to select. The Russian case names are given in the calls themselves, such as the genitive case, which means the caller has to know the vocabulary of Russian grammar, not just call a function.

Time intervals and money are spelled, not formatted

The time speller is a separate piece worth looking at, because it does something a formatter does not.

php
morphos\English\TimeSpeller::spellDifference(time() + 3600, morphos\TimeSpeller::DIRECTION) => 'in 1 hour'

The call takes a timestamp and a direction constant, and returns a human phrase. The Russian equivalent returns a phrase in Russian. This is the difference between a duration library that gives you 3600 seconds and one that gives you the string you would put in a sentence.

Money is listed as a Russian only feature. Spelling out amounts of money, as opposed to formatting a number with a currency symbol, is a task that Russian documents genuinely need and that English ones rarely do, which is the same asymmetry that shows up in the feature list.

The library also carries a humanizer topic tag, and the API marks functions that are exposed as service endpoints, which is how you know at a glance which calls are also available over HTTP when you run the container.

Installing as a library, or as a service on port 8080

The Composer install is the short one:

code
composer require wapmorgan/morphos

There are also framework adapters, each in its own repository, for Blade, for Twig and for Yii2. Those exist because the raw library returns strings, and a template engine wants a filter that can be applied inside a template.

The service route exists for the other case, a stack that is not PHP. The server starts in a container from the Docker image and listens on port 8080 for GET requests. In a compose file it looks like this:

yaml
services:
  morphos:
    image: wapmorgan/morphos:3.2.30
    ports:
      - 8093:8080

and standalone the run command publishes a host port against the container port:

bash
docker run --env NUM_WORKERS=4 --rm --publish 8093:8080 -v wapmorgan/morphos:3.2.30

The tip about NUM_WORKERS matters under load. The default is 4, and the documentation suggests adjusting it if you intend to send a lot of traffic to the service. The image tag is pinned to 3.2.30 in both examples, which is the current release.

The container runs RoadRunner on PHP 8.2 Alpine

The Dockerfile shows how the HTTP service is built, and the choices are worth naming because they explain the operational profile.

The base is php:8.2.8-cli-alpine. The RoadRunner binary comes from the ghcr.io/roadrunner-server/roadrunner image at version 2023.2.1, and the Composer binary from the composer:2.4.4 image. The sockets extension is compiled in, and the dependencies are installed with composer install using the prefer-dist and optimize-autoloader options, followed by a composer require for the HTTP stack: spiral/roadrunner-http, nyholm/psr7 and nikic/fast-route.

The entrypoint runs rr with the work directory set to /app in serve mode, and the container exposes 8080 with the comment that the service starts at that port in the container and that you should make sure you use it. Two more environment variables are set at build time, NUM_REQUESTS_BEFORE_RESET to 0 and DEBUG to false, alongside the NUM_WORKERS default of 4.

RoadRunner is a long lived application server rather than a PHP-FPM pool behind nginx, which is why the worker count is the knob to turn when the service is under load. The trade is that you now depend on a specific server binary in the image, and a different base image will not simply work.

Two README files and a doc set that has to keep pace

The documentation is split by language, with README-ru.md covering Russian morphology and README-en.md covering English morphology, and the main README linking to both. There is a further note in the Russian quick start pointing to README-ru.md for the other functions.

That split is a hint about the size of the API. A single file cannot hold every rule for two languages, so the per language files are where the depth lives, and the main README stays an overview. If you are adopting this for Russian, README-ru.md is the document to read, not the front page.

The repository also carries a phpstan.neon for static analysis, a phpunit.xml.dist for the test suite, a .scrutinizer.yml and a .travis.yml for continuous integration, a Dockerfile at the root, a bin directory, a web directory that holds the service code, and a src directory for the library itself. There is a second README file convention in the .rr.yaml, which belongs to the RoadRunner configuration the container uses.

Release cadence and the state of the project

The repository is not archived and the last push was on 2026-09-07, so work is still landing on the master branch. The release history is slower. Version 3.2.30, tagged with a bug fixes note, came on 2025-06-27. Before that, 3.2.29 from 2023-08-08 brought a Docker image and iterative improvements, and 3.2.28 from 2023-02-05 fixed last name inflection.

So the pattern is a long lived 3.2 line with occasional patch releases, and a gap of about two years between the two most recent tags. There is a CHANGELOG in the repository root and a CONTRIBUTING.md that contributors are pointed to.

The version being 3.2.x rather than 1.x or 2.x says the public API has been through a number of rounds already, which is a sign of maturity in the design and a warning about upgrade cost. Because the README does not document a deprecation or upgrade path, the safe habit is to read the changelog between the version you currently use and the one you are moving to, and to pin the version in composer.json rather than tracking the branch.

The MIT licence is permissive, and a specific compliance question about it belongs with your own counsel.

Editorial conclusion

Morphos suits a PHP product that has to produce correct Russian grammatical forms, where hand written rules would be a maintenance trap, and the same library covers English pluralisation, numerals and time intervals for a multilingual stack. It is the wrong tool if you need morphology for a language other than Russian or English, or if you want a general purpose translation layer, because the scope is grammar, not meaning. Before adopting it, install it with composer require wapmorgan/morphos, run the Russian and English examples in the quick start against your own data, and check the version you are pinning, since the newest release on Packagist is 3.2.30 from 2025-06-27.

Frequently asked questions

How do I install Morphos in a PHP project?

The library is installed with Composer using composer require wapmorgan/morphos. There are also separate adapter packages for Blade, for Twig and for Yii2 if you want to apply the rules from inside a template.

Can Morphos be used from a non PHP application?

Yes. The Docker image starts a service that listens on port 8080 for GET requests, and functions marked as API in the documentation are exposed as service endpoints taking the same arguments as the PHP functions. The container is published as wapmorgan/morphos.

Which languages does Morphos support?

Russian and English. Russian covers personal name inflection, geographical name inflection, declension and pluralisation, cardinal and ordinal numerals and money amounts, while English covers pluralisation, numerals and time units and intervals.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. wapmorgan/Morphos on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wapmorgan-morphos.svg)](https://hysenlabs.com/projects/wapmorgan-morphos)

Was this useful?