Full Code of doctrine/inflector for AI

2.1.x 9065df322d3d cached
90 files
145.1 KB
39.4k tokens
283 symbols
1 requests
Download .txt
Repository: doctrine/inflector
Branch: 2.1.x
Commit: 9065df322d3d
Files: 90
Total size: 145.1 KB

Directory structure:
gitextract_4n9ri0yq/

├── .doctrine-project.json
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── coding-standards.yml
│       ├── composer-lint.yml
│       ├── continuous-integration.yml
│       ├── release-on-milestone-closed.yml
│       ├── static-analysis.yml
│       └── website-schema.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── docs/
│   └── en/
│       └── index.rst
├── phpcs.xml.dist
├── phpstan.neon.dist
├── phpunit.xml.dist
├── src/
│   ├── CachedWordInflector.php
│   ├── GenericLanguageInflectorFactory.php
│   ├── Inflector.php
│   ├── InflectorFactory.php
│   ├── Language.php
│   ├── LanguageInflectorFactory.php
│   ├── NoopWordInflector.php
│   ├── Rules/
│   │   ├── English/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Esperanto/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── French/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Italian/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── NorwegianBokmal/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Pattern.php
│   │   ├── Patterns.php
│   │   ├── Portuguese/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Ruleset.php
│   │   ├── Spanish/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Substitution.php
│   │   ├── Substitutions.php
│   │   ├── Transformation.php
│   │   ├── Transformations.php
│   │   ├── Turkish/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   └── Word.php
│   ├── RulesetInflector.php
│   └── WordInflector.php
└── tests/
    ├── CachedWordInflectorTest.php
    ├── InflectorFactoryTest.php
    ├── InflectorFunctionalTest.php
    ├── InflectorTest.php
    ├── NoopWordInflectorTest.php
    ├── Rules/
    │   ├── English/
    │   │   └── EnglishFunctionalTest.php
    │   ├── Esperanto/
    │   │   └── EsperantoFunctionalTest.php
    │   ├── French/
    │   │   └── FrenchFunctionalTest.php
    │   ├── Italian/
    │   │   └── ItalianFunctionalTest.php
    │   ├── LanguageFunctionalTestCase.php
    │   ├── NorwegianBokmal/
    │   │   └── NorwegianBokmalFunctionalTest.php
    │   ├── PatternTest.php
    │   ├── PatternsTest.php
    │   ├── Portuguese/
    │   │   └── PortugueseFunctionalTest.php
    │   ├── RulesetTest.php
    │   ├── Spanish/
    │   │   └── SpanishFunctionalTest.php
    │   ├── SubstitutionTest.php
    │   ├── SubstitutionsTest.php
    │   ├── TransformationTest.php
    │   ├── TransformationsTest.php
    │   ├── Turkish/
    │   │   └── TurkishFunctionalTest.php
    │   └── WordTest.php
    └── RulesetInflectorTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .doctrine-project.json
================================================
{
    "active": true,
    "name": "Inflector",
    "slug": "inflector",
    "docsSlug": "doctrine-inflector",
    "versions": [
        {
            "name": "2.2",
            "branchName": "2.2.x",
            "slug": "2.2",
            "upcoming": true
        },
        {
            "name": "2.1",
            "branchName": "2.1.x",
            "slug": "2.1",
            "current": true
        },
        {
            "name": "2.0",
            "slug": "2.0",
            "maintained": false
        },
        {
            "name": "1.4",
            "slug": "1.4",
            "maintained": false
        },
        {
            "name": "1.3",
            "branchName": "1.3.x",
            "slug": "1.3",
            "maintained": false
        },
        {
            "name": "1.2",
            "branchName": "1.2.x",
            "slug": "1.2",
            "maintained": false
        },
        {
            "name": "1.1",
            "branchName": "1.1.x",
            "slug": "1.1",
            "maintained": false
        },
        {
            "name": "1.0",
            "branchName": "1.0.x",
            "slug": "1.0",
            "maintained": false
        }
    ]
}


================================================
FILE: .gitattributes
================================================
/tests export-ignore
.* export-ignore
phpunit.xml.dist export-ignore
phpcs.xml.dist export-ignore
composer.lock export-ignore
phpstan.neon.dist export-ignore


================================================
FILE: .github/FUNDING.yml
================================================
patreon: phpdoctrine
tidelift: packagist/doctrine%2Finflector
custom: https://www.doctrine-project.org/sponsorship.html


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "CI"


================================================
FILE: .github/workflows/coding-standards.yml
================================================

name: "Coding Standards"

on:
  pull_request:
    branches:
      - "*.x"
  push:
    branches:
      - "*.x"

jobs:
  coding-standards:
    name: "Coding Standards"
    uses: "doctrine/.github/.github/workflows/coding-standards.yml@13.1.0"


================================================
FILE: .github/workflows/composer-lint.yml
================================================
name: "Composer Lint"

on:
  pull_request:
    branches:
      - "*.x"
    paths:
      - "composer.json"
  push:
    branches:
      - "*.x"
    paths:
      - "composer.json"

jobs:
  composer-lint:
    name: "Composer Lint"
    uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.1.0"


================================================
FILE: .github/workflows/continuous-integration.yml
================================================

name: "Continuous Integration"

on:
  pull_request:
    branches:
      - "*.x"
  push:
    branches:
      - "*.x"

jobs:
  roave_bc_check:
    name: "Roave BC Check"
    runs-on: ubuntu-latest
    steps:
      - uses: "actions/checkout@v6"
        with:
          fetch-depth: 0

      - name: Install PHP with extensions.
        uses: shivammathur/setup-php@v2
        with:
          php-version: "8.4"

      - name: Install roave/backward-compatibility-check.
        run: composer require --dev roave/backward-compatibility-check

      - name: Run roave/backward-compatibility-check.
        run: vendor/bin/roave-backward-compatibility-check

  phpunit:
    name: "PHPUnit"
    uses: "doctrine/.github/.github/workflows/continuous-integration.yml@13.1.0"
    with:
      php-versions: '["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]'
    secrets:
      CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"


================================================
FILE: .github/workflows/release-on-milestone-closed.yml
================================================
name: "Automatic Releases"

on:
  milestone:
    types:
      - "closed"

jobs:
  release:
    name: "Git tag, release & create merge-up PR"
    uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.1.0"
    secrets:
      GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
      GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
      ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
      SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}


================================================
FILE: .github/workflows/static-analysis.yml
================================================

name: "Static Analysis"

on:
  pull_request:
    branches:
      - "*.x"
  push:
    branches:
      - "*.x"

jobs:
  static-analysis-phpstan:
    name: "Static Analysis"
    uses: "doctrine/.github/.github/workflows/phpstan.yml@13.1.0"


================================================
FILE: .github/workflows/website-schema.yml
================================================

name: "Website config validation"

on:
  pull_request:
    branches:
      - "*.x"
    paths:
      - ".doctrine-project.json"
      - ".github/workflows/website-schema.yml"
  push:
    branches:
      - "*.x"
    paths:
      - ".doctrine-project.json"
      - ".github/workflows/website-schema.yml"

jobs:
  json-validate:
    name: "Validate JSON schema"
    uses: "doctrine/.github/.github/workflows/website-schema.yml@13.1.0"


================================================
FILE: .gitignore
================================================
/vendor/
/composer.lock
/composer.phar
/phpunit.xml
/.phpunit.result.cache
/phpcs.xml
/.phpcs-cache
/phpstan.neon


================================================
FILE: LICENSE
================================================
Copyright (c) 2006-2015 Doctrine Project

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Doctrine Inflector

Doctrine Inflector is a small library that can perform string manipulations
with regard to uppercase/lowercase and singular/plural forms of words.

[![Build Status](https://github.com/doctrine/inflector/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x)
[![Code Coverage](https://codecov.io/gh/doctrine/inflector/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/inflector/branch/2.0.x)


================================================
FILE: composer.json
================================================
{
    "name": "doctrine/inflector",
    "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
    "license": "MIT",
    "type": "library",
    "keywords": [
        "php",
        "strings",
        "words",
        "manipulation",
        "inflector",
        "inflection",
        "uppercase",
        "lowercase",
        "singular",
        "plural"
    ],
    "authors": [
        {
            "name": "Guilherme Blanco",
            "email": "guilhermeblanco@gmail.com"
        },
        {
            "name": "Roman Borschel",
            "email": "roman@code-factory.org"
        },
        {
            "name": "Benjamin Eberlei",
            "email": "kontakt@beberlei.de"
        },
        {
            "name": "Jonathan Wage",
            "email": "jonwage@gmail.com"
        },
        {
            "name": "Johannes Schmitt",
            "email": "schmittjoh@gmail.com"
        }
    ],
    "homepage": "https://www.doctrine-project.org/projects/inflector.html",
    "require": {
        "php": "^7.2 || ^8.0"
    },
    "require-dev": {
        "doctrine/coding-standard": "^12.0 || ^14.0",
        "phpstan/phpstan": "^1.12 || ^2.0",
        "phpstan/phpstan-phpunit": "^1.4 || ^2.0",
        "phpstan/phpstan-strict-rules": "^1.6 || ^2.0",
        "phpunit/phpunit": "^8.5 || ^12.2"
    },
    "autoload": {
        "psr-4": {
            "Doctrine\\Inflector\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Doctrine\\Tests\\Inflector\\": "tests"
        }
    },
    "config": {
        "allow-plugins": {
            "dealerdirect/phpcodesniffer-composer-installer": true
        },
        "sort-packages": true
    }
}


================================================
FILE: docs/en/index.rst
================================================
Introduction
============

The Doctrine Inflector has methods for inflecting text. The features include pluralization,
singularization, converting between camelCase and under_score and capitalizing
words.

Installation
============

You can install the Inflector with composer:

.. code-block:: console

    $ composer require doctrine/inflector

Usage
=====

Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using
the ``Doctrine\Inflector\InflectorFactory`` class:

.. code-block:: php

    use Doctrine\Inflector\InflectorFactory;

    $inflector = InflectorFactory::create()->build();

By default it will create an English inflector. If you want to use another language, just pass the language
you want to create an inflector for to the ``createForLanguage()`` method:

.. code-block:: php

    use Doctrine\Inflector\InflectorFactory;
    use Doctrine\Inflector\Language;

    $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build();

The supported languages are as follows:

- ``Language::ENGLISH``
- ``Language::ESPERANTO``
- ``Language::FRENCH``
- ``Language::ITALIAN``
- ``Language::NORWEGIAN_BOKMAL``
- ``Language::PORTUGUESE``
- ``Language::SPANISH``
- ``Language::TURKISH``

If you want to manually construct the inflector instead of using a factory, you can do so like this:

.. code-block:: php

    use Doctrine\Inflector\CachedWordInflector;
    use Doctrine\Inflector\RulesetInflector;
    use Doctrine\Inflector\Rules\English;

    $inflector = new Inflector(
        new CachedWordInflector(new RulesetInflector(
            English\Rules::getSingularRuleset()
        )),
        new CachedWordInflector(new RulesetInflector(
            English\Rules::getPluralRuleset()
        ))
    );

Adding Languages
----------------

If you are interested in adding support for your language, take a look at the other languages defined in the
``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy
one of the languages and update the rules for your language.

Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions.

Custom Setup
============

If you want to setup custom singular and plural rules, you can configure these in the factory:

.. code-block:: php

    use Doctrine\Inflector\InflectorFactory;
    use Doctrine\Inflector\Rules\Pattern;
    use Doctrine\Inflector\Rules\Patterns;
    use Doctrine\Inflector\Rules\Ruleset;
    use Doctrine\Inflector\Rules\Substitution;
    use Doctrine\Inflector\Rules\Substitutions;
    use Doctrine\Inflector\Rules\Transformation;
    use Doctrine\Inflector\Rules\Transformations;
    use Doctrine\Inflector\Rules\Word;

    $inflector = InflectorFactory::create()
        ->withSingularRules(
            new Ruleset(
                new Transformations(
                    new Transformation(new Pattern('/^(bil)er$/i'), '\1'),
                    new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta')
                ),
                new Patterns(new Pattern('singulars')),
                new Substitutions(new Substitution(new Word('spins'), new Word('spinor')))
            )
        )
        ->withPluralRules(
            new Ruleset(
                new Transformations(
                    new Transformation(new Pattern('^(bil)er$'), '\1'),
                    new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta')
                ),
                new Patterns(new Pattern('noflect'), new Pattern('abtuse')),
                new Substitutions(
                    new Substitution(new Word('amaze'), new Word('amazable')),
                    new Substitution(new Word('phone'), new Word('phonezes'))
                )
            )
        )
        ->build();

No operation inflector
----------------------

The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for
pluralization and/or singularization. If will simply return the input as output.

This is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_.

.. code-block:: php

    use Doctrine\Inflector\Inflector;
    use Doctrine\Inflector\NoopWordInflector;

    $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector());

Tableize
========

Converts ``ModelName`` to ``model_name``:

.. code-block:: php

    echo $inflector->tableize('ModelName'); // model_name

Classify
========

Converts ``model_name`` to ``ModelName``:

.. code-block:: php

    echo $inflector->classify('model_name'); // ModelName

Camelize
========

This method uses `Classify`_ and then converts the first character to lowercase:

.. code-block:: php

    echo $inflector->camelize('model_name'); // modelName

Capitalize
==========

Takes a string and capitalizes all of the words, like PHP's built-in
``ucwords`` function. This extends that behavior, however, by allowing the
word delimiters to be configured, rather than only separating on
whitespace.

Here is an example:

.. code-block:: php

    $string = 'top-o-the-morning to all_of_you!';

    echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you!

    echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You!

Pluralize
=========

Returns a word in plural form.

.. code-block:: php

    echo $inflector->pluralize('browser'); // browsers

Singularize
===========

Returns a word in singular form.

.. code-block:: php

    echo $inflector->singularize('browsers'); // browser

Urlize
======

Generate a URL friendly string from a string of text:

.. code-block:: php

    echo $inflector->urlize('My first blog post'); // my-first-blog-post

Unaccent
========

You can unaccent a string of text using the ``unaccent()`` method:

.. code-block:: php

    echo $inflector->unaccent('año'); // ano

Legacy API
==========

The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0.
Support for languages other than English is available in the 2.0 API only.

Acknowledgements
================

The language rules in this library have been adapted from several different sources, including but not limited to:

- `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_
- `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_
- `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_


================================================
FILE: phpcs.xml.dist
================================================
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         name="PHP_CodeSniffer"
         xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
    <arg name="basepath" value="."/>
    <arg name="extensions" value="php"/>
    <arg name="parallel" value="80"/>
    <arg name="cache" value=".phpcs-cache"/>
    <arg name="colors"/>

    <!-- Ignore warnings, show progress of the run and show sniff names -->
    <arg value="nps"/>

    <config name="php_version" value="70200"/>

    <file>src</file>
    <file>tests</file>

    <rule ref="Doctrine" />

    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint">
        <properties>
            <property name="enableNativeTypeHint" value="false" />
            <property name="traversableTypeHints" type="array">
                <element value="Traversable"/>
                <element value="Iterator"/>
                <element value="IteratorAggregate"/>
                <element value="Doctrine\Common\Collections\Collection"/>
            </property>
        </properties>
    </rule>
</ruleset>


================================================
FILE: phpstan.neon.dist
================================================
includes:
    - vendor/phpstan/phpstan-phpunit/extension.neon
    - vendor/phpstan/phpstan-phpunit/rules.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon

parameters:
    level: 10
    paths:
      - src
      - tests


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         colors="true"
         beStrictAboutOutputDuringTests="true"
         failOnRisky="true"
         failOnWarning="true"
>
    <testsuites>
        <testsuite name="Doctrine Inflector Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <source>
        <include>
            <directory>src</directory>
        </include>
    </source>

    <groups>
        <exclude>
            <group>performance</group>
        </exclude>
    </groups>
</phpunit>


================================================
FILE: src/CachedWordInflector.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

class CachedWordInflector implements WordInflector
{
    /** @var WordInflector */
    private $wordInflector;

    /** @var string[] */
    private $cache = [];

    public function __construct(WordInflector $wordInflector)
    {
        $this->wordInflector = $wordInflector;
    }

    public function inflect(string $word): string
    {
        return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word);
    }
}


================================================
FILE: src/GenericLanguageInflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

use Doctrine\Inflector\Rules\Ruleset;

use function array_unshift;

abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory
{
    /** @var Ruleset[] */
    private $singularRulesets = [];

    /** @var Ruleset[] */
    private $pluralRulesets = [];

    final public function __construct()
    {
        $this->singularRulesets[] = $this->getSingularRuleset();
        $this->pluralRulesets[]   = $this->getPluralRuleset();
    }

    final public function build(): Inflector
    {
        return new Inflector(
            new CachedWordInflector(new RulesetInflector(
                ...$this->singularRulesets
            )),
            new CachedWordInflector(new RulesetInflector(
                ...$this->pluralRulesets
            ))
        );
    }

    final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory
    {
        if ($reset) {
            $this->singularRulesets = [];
        }

        if ($singularRules instanceof Ruleset) {
            array_unshift($this->singularRulesets, $singularRules);
        }

        return $this;
    }

    final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory
    {
        if ($reset) {
            $this->pluralRulesets = [];
        }

        if ($pluralRules instanceof Ruleset) {
            array_unshift($this->pluralRulesets, $pluralRules);
        }

        return $this;
    }

    abstract protected function getSingularRuleset(): Ruleset;

    abstract protected function getPluralRuleset(): Ruleset;
}


================================================
FILE: src/Inflector.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

use RuntimeException;

use function chr;
use function function_exists;
use function lcfirst;
use function mb_strtolower;
use function ord;
use function preg_match;
use function preg_replace;
use function sprintf;
use function str_replace;
use function strlen;
use function strtolower;
use function strtr;
use function trim;
use function ucwords;

class Inflector
{
    private const ACCENTED_CHARACTERS = [
        'À' => 'A',
        'Á' => 'A',
        'Â' => 'A',
        'Ã' => 'A',
        'Ä' => 'Ae',
        'Æ' => 'Ae',
        'Å' => 'Aa',
        'æ' => 'a',
        'Ç' => 'C',
        'È' => 'E',
        'É' => 'E',
        'Ê' => 'E',
        'Ë' => 'E',
        'Ì' => 'I',
        'Í' => 'I',
        'Î' => 'I',
        'Ï' => 'I',
        'Ñ' => 'N',
        'Ò' => 'O',
        'Ó' => 'O',
        'Ô' => 'O',
        'Õ' => 'O',
        'Ö' => 'Oe',
        'Ù' => 'U',
        'Ú' => 'U',
        'Û' => 'U',
        'Ü' => 'Ue',
        'Ý' => 'Y',
        'ß' => 'ss',
        'à' => 'a',
        'á' => 'a',
        'â' => 'a',
        'ã' => 'a',
        'ä' => 'ae',
        'å' => 'aa',
        'ç' => 'c',
        'è' => 'e',
        'é' => 'e',
        'ê' => 'e',
        'ë' => 'e',
        'ì' => 'i',
        'í' => 'i',
        'î' => 'i',
        'ï' => 'i',
        'ñ' => 'n',
        'ò' => 'o',
        'ó' => 'o',
        'ô' => 'o',
        'õ' => 'o',
        'ö' => 'oe',
        'ù' => 'u',
        'ú' => 'u',
        'û' => 'u',
        'ü' => 'ue',
        'ý' => 'y',
        'ÿ' => 'y',
        'Ā' => 'A',
        'ā' => 'a',
        'Ă' => 'A',
        'ă' => 'a',
        'Ą' => 'A',
        'ą' => 'a',
        'Ć' => 'C',
        'ć' => 'c',
        'Ĉ' => 'C',
        'ĉ' => 'c',
        'Ċ' => 'C',
        'ċ' => 'c',
        'Č' => 'C',
        'č' => 'c',
        'Ď' => 'D',
        'ď' => 'd',
        'Đ' => 'D',
        'đ' => 'd',
        'Ē' => 'E',
        'ē' => 'e',
        'Ĕ' => 'E',
        'ĕ' => 'e',
        'Ė' => 'E',
        'ė' => 'e',
        'Ę' => 'E',
        'ę' => 'e',
        'Ě' => 'E',
        'ě' => 'e',
        'Ĝ' => 'G',
        'ĝ' => 'g',
        'Ğ' => 'G',
        'ğ' => 'g',
        'Ġ' => 'G',
        'ġ' => 'g',
        'Ģ' => 'G',
        'ģ' => 'g',
        'Ĥ' => 'H',
        'ĥ' => 'h',
        'Ħ' => 'H',
        'ħ' => 'h',
        'Ĩ' => 'I',
        'ĩ' => 'i',
        'Ī' => 'I',
        'ī' => 'i',
        'Ĭ' => 'I',
        'ĭ' => 'i',
        'Į' => 'I',
        'į' => 'i',
        'İ' => 'I',
        'ı' => 'i',
        'IJ' => 'IJ',
        'ij' => 'ij',
        'Ĵ' => 'J',
        'ĵ' => 'j',
        'Ķ' => 'K',
        'ķ' => 'k',
        'ĸ' => 'k',
        'Ĺ' => 'L',
        'ĺ' => 'l',
        'Ļ' => 'L',
        'ļ' => 'l',
        'Ľ' => 'L',
        'ľ' => 'l',
        'Ŀ' => 'L',
        'ŀ' => 'l',
        'Ł' => 'L',
        'ł' => 'l',
        'Ń' => 'N',
        'ń' => 'n',
        'Ņ' => 'N',
        'ņ' => 'n',
        'Ň' => 'N',
        'ň' => 'n',
        'ʼn' => 'N',
        'Ŋ' => 'n',
        'ŋ' => 'N',
        'Ō' => 'O',
        'ō' => 'o',
        'Ŏ' => 'O',
        'ŏ' => 'o',
        'Ő' => 'O',
        'ő' => 'o',
        'Œ' => 'OE',
        'œ' => 'oe',
        'Ø' => 'O',
        'ø' => 'o',
        'Ŕ' => 'R',
        'ŕ' => 'r',
        'Ŗ' => 'R',
        'ŗ' => 'r',
        'Ř' => 'R',
        'ř' => 'r',
        'Ś' => 'S',
        'ś' => 's',
        'Ŝ' => 'S',
        'ŝ' => 's',
        'Ş' => 'S',
        'ş' => 's',
        'Š' => 'S',
        'š' => 's',
        'Ţ' => 'T',
        'ţ' => 't',
        'Ť' => 'T',
        'ť' => 't',
        'Ŧ' => 'T',
        'ŧ' => 't',
        'Ũ' => 'U',
        'ũ' => 'u',
        'Ū' => 'U',
        'ū' => 'u',
        'Ŭ' => 'U',
        'ŭ' => 'u',
        'Ů' => 'U',
        'ů' => 'u',
        'Ű' => 'U',
        'ű' => 'u',
        'Ų' => 'U',
        'ų' => 'u',
        'Ŵ' => 'W',
        'ŵ' => 'w',
        'Ŷ' => 'Y',
        'ŷ' => 'y',
        'Ÿ' => 'Y',
        'Ź' => 'Z',
        'ź' => 'z',
        'Ż' => 'Z',
        'ż' => 'z',
        'Ž' => 'Z',
        'ž' => 'z',
        'ſ' => 's',
        '€' => 'E',
        '£' => '',
    ];

    /** @var WordInflector */
    private $singularizer;

    /** @var WordInflector */
    private $pluralizer;

    public function __construct(WordInflector $singularizer, WordInflector $pluralizer)
    {
        $this->singularizer = $singularizer;
        $this->pluralizer   = $pluralizer;
    }

    /**
     * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
     */
    public function tableize(string $word): string
    {
        $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word);

        if ($tableized === null) {
            throw new RuntimeException(sprintf(
                'preg_replace returned null for value "%s"',
                $word
            ));
        }

        return mb_strtolower($tableized);
    }

    /**
     * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
     */
    public function classify(string $word): string
    {
        return str_replace([' ', '_', '-'], '', ucwords($word, ' _-'));
    }

    /**
     * Camelizes a word. This uses the classify() method and turns the first character to lowercase.
     */
    public function camelize(string $word): string
    {
        return lcfirst($this->classify($word));
    }

    /**
     * Uppercases words with configurable delimiters between words.
     *
     * Takes a string and capitalizes all of the words, like PHP's built-in
     * ucwords function. This extends that behavior, however, by allowing the
     * word delimiters to be configured, rather than only separating on
     * whitespace.
     *
     * Here is an example:
     * <code>
     * <?php
     * $string = 'top-o-the-morning to all_of_you!';
     * echo $inflector->capitalize($string);
     * // Top-O-The-Morning To All_of_you!
     *
     * echo $inflector->capitalize($string, '-_ ');
     * // Top-O-The-Morning To All_Of_You!
     * ?>
     * </code>
     *
     * @param string $string     The string to operate on.
     * @param string $delimiters A list of word separators.
     *
     * @return string The string with all delimiter-separated words capitalized.
     */
    public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string
    {
        return ucwords($string, $delimiters);
    }

    /**
     * Checks if the given string seems like it has utf8 characters in it.
     *
     * @param string $string The string to check for utf8 characters in.
     */
    public function seemsUtf8(string $string): bool
    {
        for ($i = 0; $i < strlen($string); $i++) {
            if (ord($string[$i]) < 0x80) {
                continue; // 0bbbbbbb
            }

            if ((ord($string[$i]) & 0xE0) === 0xC0) {
                $n = 1; // 110bbbbb
            } elseif ((ord($string[$i]) & 0xF0) === 0xE0) {
                $n = 2; // 1110bbbb
            } elseif ((ord($string[$i]) & 0xF8) === 0xF0) {
                $n = 3; // 11110bbb
            } elseif ((ord($string[$i]) & 0xFC) === 0xF8) {
                $n = 4; // 111110bb
            } elseif ((ord($string[$i]) & 0xFE) === 0xFC) {
                $n = 5; // 1111110b
            } else {
                return false; // Does not match any model
            }

            for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
                if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Remove any illegal characters, accents, etc.
     *
     * @param  string $string String to unaccent
     *
     * @return string Unaccented string
     */
    public function unaccent(string $string): string
    {
        if (preg_match('/[\x80-\xff]/', $string) === false) {
            return $string;
        }

        if ($this->seemsUtf8($string)) {
            $string = strtr($string, self::ACCENTED_CHARACTERS);
        } else {
            $characters = [];

            // Assume ISO-8859-1 if not UTF-8
            $characters['in'] =
                  chr(128)
                . chr(131)
                . chr(138)
                . chr(142)
                . chr(154)
                . chr(158)
                . chr(159)
                . chr(162)
                . chr(165)
                . chr(181)
                . chr(192)
                . chr(193)
                . chr(194)
                . chr(195)
                . chr(196)
                . chr(197)
                . chr(199)
                . chr(200)
                . chr(201)
                . chr(202)
                . chr(203)
                . chr(204)
                . chr(205)
                . chr(206)
                . chr(207)
                . chr(209)
                . chr(210)
                . chr(211)
                . chr(212)
                . chr(213)
                . chr(214)
                . chr(216)
                . chr(217)
                . chr(218)
                . chr(219)
                . chr(220)
                . chr(221)
                . chr(224)
                . chr(225)
                . chr(226)
                . chr(227)
                . chr(228)
                . chr(229)
                . chr(231)
                . chr(232)
                . chr(233)
                . chr(234)
                . chr(235)
                . chr(236)
                . chr(237)
                . chr(238)
                . chr(239)
                . chr(241)
                . chr(242)
                . chr(243)
                . chr(244)
                . chr(245)
                . chr(246)
                . chr(248)
                . chr(249)
                . chr(250)
                . chr(251)
                . chr(252)
                . chr(253)
                . chr(255);

            $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy';

            $string = strtr($string, $characters['in'], $characters['out']);

            $doubleChars = [];

            $doubleChars['in'] = [
                chr(140),
                chr(156),
                chr(198),
                chr(208),
                chr(222),
                chr(223),
                chr(230),
                chr(240),
                chr(254),
            ];

            $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'];

            $string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
        }

        return $string;
    }

    /**
     * Convert any passed string to a url friendly string.
     * Converts 'My first blog post' to 'my-first-blog-post'
     *
     * @param  string $string String to urlize.
     *
     * @return string Urlized string.
     */
    public function urlize(string $string): string
    {
        // Remove all non url friendly characters with the unaccent function
        $unaccented = $this->unaccent($string);

        if (function_exists('mb_strtolower')) {
            $lowered = mb_strtolower($unaccented);
        } else {
            $lowered = strtolower($unaccented);
        }

        $replacements = [
            '/\W/' => ' ',
            '/([A-Z]+)([A-Z][a-z])/' => '\1_\2',
            '/([a-z\d])([A-Z])/' => '\1_\2',
            '/[^A-Z^a-z^0-9^\/]+/' => '-',
        ];

        $urlized = $lowered;

        foreach ($replacements as $pattern => $replacement) {
            $replaced = preg_replace($pattern, $replacement, $urlized);

            if ($replaced === null) {
                throw new RuntimeException(sprintf(
                    'preg_replace returned null for value "%s"',
                    $urlized
                ));
            }

            $urlized = $replaced;
        }

        return trim($urlized, '-');
    }

    /**
     * Returns a word in singular form.
     *
     * @param string $word The word in plural form.
     *
     * @return string The word in singular form.
     */
    public function singularize(string $word): string
    {
        return $this->singularizer->inflect($word);
    }

    /**
     * Returns a word in plural form.
     *
     * @param string $word The word in singular form.
     *
     * @return string The word in plural form.
     */
    public function pluralize(string $word): string
    {
        return $this->pluralizer->inflect($word);
    }
}


================================================
FILE: src/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

use Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Esperanto;
use Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Italian;
use Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Turkish;
use InvalidArgumentException;

use function sprintf;

final class InflectorFactory
{
    public static function create(): LanguageInflectorFactory
    {
        return self::createForLanguage(Language::ENGLISH);
    }

    public static function createForLanguage(string $language): LanguageInflectorFactory
    {
        switch ($language) {
            case Language::ENGLISH:
                return new English\InflectorFactory();

            case Language::ESPERANTO:
                return new Esperanto\InflectorFactory();

            case Language::FRENCH:
                return new French\InflectorFactory();

            case Language::ITALIAN:
                return new Italian\InflectorFactory();

            case Language::NORWEGIAN_BOKMAL:
                return new NorwegianBokmal\InflectorFactory();

            case Language::PORTUGUESE:
                return new Portuguese\InflectorFactory();

            case Language::SPANISH:
                return new Spanish\InflectorFactory();

            case Language::TURKISH:
                return new Turkish\InflectorFactory();

            default:
                throw new InvalidArgumentException(sprintf(
                    'Language "%s" is not supported.',
                    $language
                ));
        }
    }
}


================================================
FILE: src/Language.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

final class Language
{
    public const ENGLISH          = 'english';
    public const ESPERANTO        = 'esperanto';
    public const FRENCH           = 'french';
    public const ITALIAN          = 'italian';
    public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';
    public const PORTUGUESE       = 'portuguese';
    public const SPANISH          = 'spanish';
    public const TURKISH          = 'turkish';

    private function __construct()
    {
    }
}


================================================
FILE: src/LanguageInflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

use Doctrine\Inflector\Rules\Ruleset;

interface LanguageInflectorFactory
{
    /**
     * Applies custom rules for singularisation
     *
     * @param bool $reset If true, will unset default inflections for all new rules
     *
     * @return $this
     */
    public function withSingularRules(?Ruleset $singularRules, bool $reset = false): self;

    /**
     * Applies custom rules for pluralisation
     *
     * @param bool $reset If true, will unset default inflections for all new rules
     *
     * @return $this
     */
    public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): self;

    /**
     * Builds the inflector instance with all applicable rules
     */
    public function build(): Inflector;
}


================================================
FILE: src/NoopWordInflector.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

class NoopWordInflector implements WordInflector
{
    public function inflect(string $word): string
    {
        return $word;
    }
}


================================================
FILE: src/Rules/English/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\English;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('(s)tatuses$'), '\1\2tatus');
        yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatus');
        yield new Transformation(new Pattern('(c)ampus$'), '\1\2ampus');
        yield new Transformation(new Pattern('^(.*)(menu)s$'), '\1\2');
        yield new Transformation(new Pattern('(quiz)zes$'), '\\1');
        yield new Transformation(new Pattern('(matr)ices$'), '\1ix');
        yield new Transformation(new Pattern('(vert|ind)ices$'), '\1ex');
        yield new Transformation(new Pattern('^(ox)en'), '\1');
        yield new Transformation(new Pattern('(alias)(es)*$'), '\1');
        yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\1o');
        yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\1us');
        yield new Transformation(new Pattern('([ftw]ax)es'), '\1');
        yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\1is');
        yield new Transformation(new Pattern('(shoe|slave)s$'), '\1');
        yield new Transformation(new Pattern('(o)es$'), '\1');
        yield new Transformation(new Pattern('ouses$'), 'ouse');
        yield new Transformation(new Pattern('([^a])uses$'), '\1us');
        yield new Transformation(new Pattern('([m|l])ice$'), '\1ouse');
        yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\1');
        yield new Transformation(new Pattern('(m)ovies$'), '\1\2ovie');
        yield new Transformation(new Pattern('(s)eries$'), '\1\2eries');
        yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\1y');
        yield new Transformation(new Pattern('([lr])ves$'), '\1f');
        yield new Transformation(new Pattern('(tive)s$'), '\1');
        yield new Transformation(new Pattern('(hive)s$'), '\1');
        yield new Transformation(new Pattern('(drive)s$'), '\1');
        yield new Transformation(new Pattern('(dive)s$'), '\1');
        yield new Transformation(new Pattern('(olive)s$'), '\1');
        yield new Transformation(new Pattern('([^fo])ves$'), '\1fe');
        yield new Transformation(new Pattern('(^analy)ses$'), '\1sis');
        yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\1\2sis');
        yield new Transformation(new Pattern('(tax)a$'), '\1on');
        yield new Transformation(new Pattern('(c)riteria$'), '\1riterion');
        yield new Transformation(new Pattern('([ti])a(?<!regatta)$'), '\1um');
        yield new Transformation(new Pattern('(p)eople$'), '\1\2erson');
        yield new Transformation(new Pattern('(m)en$'), '\1an');
        yield new Transformation(new Pattern('(c)hildren$'), '\1\2hild');
        yield new Transformation(new Pattern('(f)eet$'), '\1oot');
        yield new Transformation(new Pattern('(n)ews$'), '\1\2ews');
        yield new Transformation(new Pattern('eaus$'), 'eau');
        yield new Transformation(new Pattern('^tights$'), 'tights');
        yield new Transformation(new Pattern('^shorts$'), 'shorts');
        yield new Transformation(new Pattern('s$'), '');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatuses');
        yield new Transformation(new Pattern('(quiz)$'), '\1zes');
        yield new Transformation(new Pattern('^(ox)$'), '\1\2en');
        yield new Transformation(new Pattern('([m|l])ouse$'), '\1ice');
        yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\1ices');
        yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\1es');
        yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\1ies');
        yield new Transformation(new Pattern('(hive|gulf)$'), '\1s');
        yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\1\2ves');
        yield new Transformation(new Pattern('sis$'), 'ses');
        yield new Transformation(new Pattern('([ti])um$'), '\1a');
        yield new Transformation(new Pattern('(tax)on$'), '\1a');
        yield new Transformation(new Pattern('(c)riterion$'), '\1riteria');
        yield new Transformation(new Pattern('(p)erson$'), '\1eople');
        yield new Transformation(new Pattern('(m)an$'), '\1en');
        yield new Transformation(new Pattern('(c)hild$'), '\1hildren');
        yield new Transformation(new Pattern('(f)oot$'), '\1eet');
        yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\1\2oes');
        yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\1i');
        yield new Transformation(new Pattern('us$'), 'uses');
        yield new Transformation(new Pattern('(alias)$'), '\1es');
        yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\1es');
        yield new Transformation(new Pattern('s$'), 's');
        yield new Transformation(new Pattern('^$'), '');
        yield new Transformation(new Pattern('$'), 's');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word('abuse'), new Word('abuses'));
        yield new Substitution(new Word('alga'), new Word('algae'));
        yield new Substitution(new Word('atlas'), new Word('atlases'));
        yield new Substitution(new Word('avalanche'), new Word('avalanches'));
        yield new Substitution(new Word('axis'), new Word('axes'));
        yield new Substitution(new Word('axe'), new Word('axes'));
        yield new Substitution(new Word('beef'), new Word('beefs'));
        yield new Substitution(new Word('blouse'), new Word('blouses'));
        yield new Substitution(new Word('brother'), new Word('brothers'));
        yield new Substitution(new Word('brownie'), new Word('brownies'));
        yield new Substitution(new Word('cache'), new Word('caches'));
        yield new Substitution(new Word('cafe'), new Word('cafes'));
        yield new Substitution(new Word('canvas'), new Word('canvases'));
        yield new Substitution(new Word('cave'), new Word('caves'));
        yield new Substitution(new Word('chateau'), new Word('chateaux'));
        yield new Substitution(new Word('child'), new Word('children'));
        yield new Substitution(new Word('cookie'), new Word('cookies'));
        yield new Substitution(new Word('corpus'), new Word('corpuses'));
        yield new Substitution(new Word('cow'), new Word('cows'));
        yield new Substitution(new Word('criterion'), new Word('criteria'));
        yield new Substitution(new Word('curriculum'), new Word('curricula'));
        yield new Substitution(new Word('curve'), new Word('curves'));
        yield new Substitution(new Word('demo'), new Word('demos'));
        yield new Substitution(new Word('die'), new Word('dice'));
        yield new Substitution(new Word('domino'), new Word('dominoes'));
        yield new Substitution(new Word('echo'), new Word('echoes'));
        yield new Substitution(new Word('emphasis'), new Word('emphases'));
        yield new Substitution(new Word('epoch'), new Word('epochs'));
        yield new Substitution(new Word('foe'), new Word('foes'));
        yield new Substitution(new Word('foot'), new Word('feet'));
        yield new Substitution(new Word('fungus'), new Word('fungi'));
        yield new Substitution(new Word('ganglion'), new Word('ganglions'));
        yield new Substitution(new Word('gas'), new Word('gases'));
        yield new Substitution(new Word('genie'), new Word('genies'));
        yield new Substitution(new Word('genus'), new Word('genera'));
        yield new Substitution(new Word('goose'), new Word('geese'));
        yield new Substitution(new Word('graffito'), new Word('graffiti'));
        yield new Substitution(new Word('grave'), new Word('graves'));
        yield new Substitution(new Word('hippopotamus'), new Word('hippopotami'));
        yield new Substitution(new Word('hoax'), new Word('hoaxes'));
        yield new Substitution(new Word('hoof'), new Word('hoofs'));
        yield new Substitution(new Word('human'), new Word('humans'));
        yield new Substitution(new Word('iris'), new Word('irises'));
        yield new Substitution(new Word('larva'), new Word('larvae'));
        yield new Substitution(new Word('leaf'), new Word('leaves'));
        yield new Substitution(new Word('lens'), new Word('lenses'));
        yield new Substitution(new Word('loaf'), new Word('loaves'));
        yield new Substitution(new Word('man'), new Word('men'));
        yield new Substitution(new Word('medium'), new Word('media'));
        yield new Substitution(new Word('memorandum'), new Word('memoranda'));
        yield new Substitution(new Word('money'), new Word('monies'));
        yield new Substitution(new Word('mongoose'), new Word('mongooses'));
        yield new Substitution(new Word('motto'), new Word('mottoes'));
        yield new Substitution(new Word('move'), new Word('moves'));
        yield new Substitution(new Word('mythos'), new Word('mythoi'));
        yield new Substitution(new Word('neurosis'), new Word('neuroses'));
        yield new Substitution(new Word('niche'), new Word('niches'));
        yield new Substitution(new Word('niveau'), new Word('niveaux'));
        yield new Substitution(new Word('nucleus'), new Word('nuclei'));
        yield new Substitution(new Word('numen'), new Word('numina'));
        yield new Substitution(new Word('nursery'), new Word('nurseries'));
        yield new Substitution(new Word('oasis'), new Word('oases'));
        yield new Substitution(new Word('occiput'), new Word('occiputs'));
        yield new Substitution(new Word('octopus'), new Word('octopuses'));
        yield new Substitution(new Word('opus'), new Word('opuses'));
        yield new Substitution(new Word('ox'), new Word('oxen'));
        yield new Substitution(new Word('passerby'), new Word('passersby'));
        yield new Substitution(new Word('penis'), new Word('penises'));
        yield new Substitution(new Word('person'), new Word('people'));
        yield new Substitution(new Word('plateau'), new Word('plateaux'));
        yield new Substitution(new Word('runner-up'), new Word('runners-up'));
        yield new Substitution(new Word('safe'), new Word('safes'));
        yield new Substitution(new Word('save'), new Word('saves'));
        yield new Substitution(new Word('sex'), new Word('sexes'));
        yield new Substitution(new Word('sieve'), new Word('sieves'));
        yield new Substitution(new Word('soliloquy'), new Word('soliloquies'));
        yield new Substitution(new Word('son-in-law'), new Word('sons-in-law'));
        yield new Substitution(new Word('stadium'), new Word('stadiums'));
        yield new Substitution(new Word('syllabus'), new Word('syllabi'));
        yield new Substitution(new Word('testis'), new Word('testes'));
        yield new Substitution(new Word('thief'), new Word('thieves'));
        yield new Substitution(new Word('tooth'), new Word('teeth'));
        yield new Substitution(new Word('tornado'), new Word('tornadoes'));
        yield new Substitution(new Word('trilby'), new Word('trilbys'));
        yield new Substitution(new Word('turf'), new Word('turfs'));
        yield new Substitution(new Word('valve'), new Word('valves'));
        yield new Substitution(new Word('volcano'), new Word('volcanoes'));
        yield new Substitution(new Word('wave'), new Word('waves'));
        yield new Substitution(new Word('zombie'), new Word('zombies'));
    }
}


================================================
FILE: src/Rules/English/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\English;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/English/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\English;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/English/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\English;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();

        yield new Pattern('.*ss');
        yield new Pattern('clothes');
        yield new Pattern('data');
        yield new Pattern('fascia');
        yield new Pattern('fuchsia');
        yield new Pattern('galleria');
        yield new Pattern('mafia');
        yield new Pattern('militia');
        yield new Pattern('pants');
        yield new Pattern('petunia');
        yield new Pattern('sepia');
        yield new Pattern('trivia');
        yield new Pattern('utopia');
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();

        yield new Pattern('people');
        yield new Pattern('trivia');
        yield new Pattern('\w+ware$');
        yield new Pattern('media');
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('\w+media');
        yield new Pattern('advice');
        yield new Pattern('aircraft');
        yield new Pattern('amoyese');
        yield new Pattern('art');
        yield new Pattern('audio');
        yield new Pattern('baggage');
        yield new Pattern('bison');
        yield new Pattern('borghese');
        yield new Pattern('bream');
        yield new Pattern('breeches');
        yield new Pattern('britches');
        yield new Pattern('buffalo');
        yield new Pattern('butter');
        yield new Pattern('cantus');
        yield new Pattern('carp');
        yield new Pattern('cattle');
        yield new Pattern('chassis');
        yield new Pattern('clippers');
        yield new Pattern('clothing');
        yield new Pattern('coal');
        yield new Pattern('cod');
        yield new Pattern('coitus');
        yield new Pattern('compensation');
        yield new Pattern('congoese');
        yield new Pattern('contretemps');
        yield new Pattern('coreopsis');
        yield new Pattern('corps');
        yield new Pattern('cotton');
        yield new Pattern('data');
        yield new Pattern('debris');
        yield new Pattern('deer');
        yield new Pattern('diabetes');
        yield new Pattern('djinn');
        yield new Pattern('education');
        yield new Pattern('eland');
        yield new Pattern('elk');
        yield new Pattern('emoji');
        yield new Pattern('equipment');
        yield new Pattern('evidence');
        yield new Pattern('faroese');
        yield new Pattern('feedback');
        yield new Pattern('fish');
        yield new Pattern('flounder');
        yield new Pattern('flour');
        yield new Pattern('foochowese');
        yield new Pattern('food');
        yield new Pattern('furniture');
        yield new Pattern('gallows');
        yield new Pattern('genevese');
        yield new Pattern('genoese');
        yield new Pattern('gilbertese');
        yield new Pattern('gold');
        yield new Pattern('headquarters');
        yield new Pattern('herpes');
        yield new Pattern('hijinks');
        yield new Pattern('homework');
        yield new Pattern('hottentotese');
        yield new Pattern('impatience');
        yield new Pattern('information');
        yield new Pattern('innings');
        yield new Pattern('jackanapes');
        yield new Pattern('jeans');
        yield new Pattern('jedi');
        yield new Pattern('kin');
        yield new Pattern('kiplingese');
        yield new Pattern('knowledge');
        yield new Pattern('kongoese');
        yield new Pattern('leather');
        yield new Pattern('love');
        yield new Pattern('lucchese');
        yield new Pattern('luggage');
        yield new Pattern('mackerel');
        yield new Pattern('Maltese');
        yield new Pattern('management');
        yield new Pattern('metadata');
        yield new Pattern('mews');
        yield new Pattern('money');
        yield new Pattern('moose');
        yield new Pattern('mumps');
        yield new Pattern('music');
        yield new Pattern('nankingese');
        yield new Pattern('news');
        yield new Pattern('nexus');
        yield new Pattern('niasese');
        yield new Pattern('nutrition');
        yield new Pattern('offspring');
        yield new Pattern('oil');
        yield new Pattern('patience');
        yield new Pattern('pekingese');
        yield new Pattern('piedmontese');
        yield new Pattern('pincers');
        yield new Pattern('pistoiese');
        yield new Pattern('plankton');
        yield new Pattern('pliers');
        yield new Pattern('pokemon');
        yield new Pattern('police');
        yield new Pattern('polish');
        yield new Pattern('portuguese');
        yield new Pattern('proceedings');
        yield new Pattern('progress');
        yield new Pattern('rabies');
        yield new Pattern('rain');
        yield new Pattern('research');
        yield new Pattern('rhinoceros');
        yield new Pattern('rice');
        yield new Pattern('salmon');
        yield new Pattern('sand');
        yield new Pattern('sarawakese');
        yield new Pattern('scissors');
        yield new Pattern('sea[- ]bass');
        yield new Pattern('series');
        yield new Pattern('shavese');
        yield new Pattern('shears');
        yield new Pattern('sheep');
        yield new Pattern('siemens');
        yield new Pattern('silk');
        yield new Pattern('sms');
        yield new Pattern('soap');
        yield new Pattern('social media');
        yield new Pattern('spam');
        yield new Pattern('species');
        yield new Pattern('staff');
        yield new Pattern('sugar');
        yield new Pattern('swine');
        yield new Pattern('talent');
        yield new Pattern('toothpaste');
        yield new Pattern('traffic');
        yield new Pattern('travel');
        yield new Pattern('trousers');
        yield new Pattern('trout');
        yield new Pattern('tuna');
        yield new Pattern('us');
        yield new Pattern('vermontese');
        yield new Pattern('vinegar');
        yield new Pattern('weather');
        yield new Pattern('wenchowese');
        yield new Pattern('wheat');
        yield new Pattern('whiting');
        yield new Pattern('wildebeest');
        yield new Pattern('wood');
        yield new Pattern('wool');
        yield new Pattern('yengeese');
    }
}


================================================
FILE: src/Rules/Esperanto/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Esperanto;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('oj$'), 'o');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('o$'), 'oj');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word(''), new Word(''));
    }
}


================================================
FILE: src/Rules/Esperanto/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Esperanto;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/Esperanto/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Esperanto;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/Esperanto/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Esperanto;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('');
    }
}


================================================
FILE: src/Rules/French/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\French;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail');
        yield new Transformation(new Pattern('/ails$/'), 'ail');
        yield new Transformation(new Pattern('/(journ|chev|loc)aux$/'), '\1al');
        yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1');
        yield new Transformation(new Pattern('/s$/'), '');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('/(s|x|z)$/'), '\1');
        yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\1aux');
        yield new Transformation(new Pattern('/ail$/'), 'ails');
        yield new Transformation(new Pattern('/(chacal|carnaval|festival|récital)$/'), '\1s');
        yield new Transformation(new Pattern('/al$/'), 'aux');
        yield new Transformation(new Pattern('/(bleu|émeu|landau|pneu|sarrau)$/'), '\1s');
        yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|lieu|pou|au|eu|eau)$/'), '\1x');
        yield new Transformation(new Pattern('/$/'), 's');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word('monsieur'), new Word('messieurs'));
        yield new Substitution(new Word('madame'), new Word('mesdames'));
        yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles'));
    }
}


================================================
FILE: src/Rules/French/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\French;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/French/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\French;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/French/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\French;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();

        yield new Pattern('bois');
        yield new Pattern('mas');
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('');
    }
}


================================================
FILE: src/Rules/Italian/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Italian;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return iterable<Transformation> */
    public static function getSingular(): iterable
    {
        // Reverse of -sce → -scia (fasce → fascia)
        yield new Transformation(new Pattern('([aeiou])sce$'), '\\1scia');

        // Reverse of -cie → -cia (farmacia → farmacie)
        yield new Transformation(new Pattern('cie$'), 'cia');

        // Reverse of -gie → -gia (bugia → bugie)
        yield new Transformation(new Pattern('gie$'), 'gia');

        // Reverse of -ce → -cia (arance → arancia)
        yield new Transformation(new Pattern('([^aeiou])ce$'), '\1cia');

        // Reverse of -ge → -gia (valige → valigia)
        yield new Transformation(new Pattern('([^aeiou])ge$'), '\1gia');

        // Reverse of -chi → -co (bachi → baco)
        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])chi$'), '\1co');

        // Reverse of -ghi → -go (laghi → lago)
        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])ghi$'), '\1go');

        // Reverse of -ci → -co (medici → medico)
        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])ci$'), '\1co');

        // Reverse of -gi → -go (psicologi → psicologo)
        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])gi$'), '\1go');

        // Reverse of -i → -io (zii → zio, negozi → negozio)
        // This is more complex due to Italian's stress patterns, but we'll handle the basic case
        yield new Transformation(new Pattern('([^aeiou])i$'), '\1io');

        // Handle words that end with -i but should go to -co/-go (amici → amico, not amice)
        yield new Transformation(new Pattern('([^aeiou])ci$'), '\1co');
        yield new Transformation(new Pattern('([^aeiou])gi$'), '\1go');

        // Reverse of -a → -e
        yield new Transformation(new Pattern('e$'), 'a');

        // Reverse of -e → -i
        yield new Transformation(new Pattern('i$'), 'e');

        // Reverse of -o → -i
        yield new Transformation(new Pattern('i$'), 'o');
    }

    /** @return iterable<Transformation> */
    public static function getPlural(): iterable
    {
        // Words ending in -scia without stress on 'i' become -sce (e.g. fascia → fasce)
        yield new Transformation(new Pattern('([aeiou])scia$'), '\\1sce');

        // Words ending in -cia/gia with stress on 'i' keep the 'i' in plural
        yield new Transformation(new Pattern('cia$'), 'cie'); // e.g. farmacia → farmacie
        yield new Transformation(new Pattern('gia$'), 'gie'); // e.g. bugia → bugie

        // Words ending in -cia/gia without stress on 'i' lose the 'i' in plural
        yield new Transformation(new Pattern('([^aeiou])cia$'), '\\1ce'); // e.g. arancia → arance
        yield new Transformation(new Pattern('([^aeiou])gia$'), '\\1ge'); // e.g. valigia → valige

        // Words ending in -co/-go with stress on 'o' become -chi/-ghi
        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])co$'), '\\1chi'); // e.g. baco → bachi
        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])go$'), '\\1ghi'); // e.g. lago → laghi

        // Words ending in -co/-go with stress on the penultimate syllable become -ci/-gi
        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])co$'), '\\1ci'); // e.g. medico → medici
        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])go$'), '\\1gi'); // e.g. psicologo → psicologi

        // Words ending in -io with stress on 'i' keep the 'i' in plural
        yield new Transformation(new Pattern('([^aeiou])io$'), '\\1i'); // e.g. zio → zii

        // Words ending in -io with stress on 'o' lose the 'i' in plural
        yield new Transformation(new Pattern('([aeiou])io$'), '\\1i'); // e.g. negozio → negozi

        // Standard ending rules
        yield new Transformation(new Pattern('a$'), 'e');  // -a → -e
        yield new Transformation(new Pattern('e$'), 'i');  // -e → -i
        yield new Transformation(new Pattern('o$'), 'i');  // -o → -i
    }

    /** @return iterable<Substitution> */
    public static function getIrregular(): iterable
    {
        // Irregular substitutions (singular => plural)
        $irregulars = [
            'ala' => 'ali',
            'albergo' => 'alberghi',
            'amica' => 'amiche',
            'amico' => 'amici',
            'ampio' => 'ampi',
            'arancia' => 'arance',
            'arma' => 'armi',
            'asparago' => 'asparagi',
            'banca' => 'banche',
            'belga' => 'belgi',
            'braccio' => 'braccia',
            'budello' => 'budella',
            'bue' => 'buoi',
            'caccia' => 'cacce',
            'calcagno' => 'calcagna',
            'camicia' => 'camicie',
            'cane' => 'cani',
            'capitale' => 'capitali',
            'carcere' => 'carceri',
            'casa' => 'case',
            'cavaliere' => 'cavalieri',
            'centinaio' => 'centinaia',
            'cerchio' => 'cerchia',
            'cervello' => 'cervella',
            'chiave' => 'chiavi',
            'chirurgo' => 'chirurgi',
            'ciglio' => 'ciglia',
            'città' => 'città',
            'corno' => 'corna',
            'corpo' => 'corpi',
            'crisi' => 'crisi',
            'dente' => 'denti',
            'dio' => 'dei',
            'dito' => 'dita',
            'dottore' => 'dottori',
            'fiore' => 'fiori',
            'fratello' => 'fratelli',
            'fuoco' => 'fuochi',
            'gamba' => 'gambe',
            'ginocchio' => 'ginocchia',
            'gioco' => 'giochi',
            'giornale' => 'giornali',
            'giraffa' => 'giraffe',
            'labbro' => 'labbra',
            'lenzuolo' => 'lenzuola',
            'libro' => 'libri',
            'madre' => 'madri',
            'maestro' => 'maestri',
            'magico' => 'magici',
            'mago' => 'maghi',
            'maniaco' => 'maniaci',
            'manico' => 'manici',
            'mano' => 'mani',
            'medico' => 'medici',
            'membro' => 'membri',
            'metropoli' => 'metropoli',
            'migliaio' => 'migliaia',
            'miglio' => 'miglia',
            'mille' => 'mila',
            'mio' => 'miei',
            'moglie' => 'mogli',
            'mosaico' => 'mosaici',
            'muro' => 'muri',
            'nemico' => 'nemici',
            'nome' => 'nomi',
            'occhio' => 'occhi',
            'orecchio' => 'orecchi',
            'osso' => 'ossa',
            'paio' => 'paia',
            'pane' => 'pani',
            'papa' => 'papi',
            'pasta' => 'paste',
            'penna' => 'penne',
            'pesce' => 'pesci',
            'piede' => 'piedi',
            'pittore' => 'pittori',
            'poeta' => 'poeti',
            'porco' => 'porci',
            'porto' => 'porti',
            'problema' => 'problemi',
            'ragazzo' => 'ragazzi',
            're' => 're',
            'rene' => 'reni',
            'riso' => 'risa',
            'rosa' => 'rosa',
            'sale' => 'sali',
            'sarto' => 'sarti',
            'scuola' => 'scuole',
            'serie' => 'serie',
            'serramento' => 'serramenta',
            'sorella' => 'sorelle',
            'specie' => 'specie',
            'staio' => 'staia',
            'stazione' => 'stazioni',
            'strido' => 'strida',
            'strillo' => 'strilla',
            'studio' => 'studi',
            'suo' => 'suoi',
            'superficie' => 'superfici',
            'tavolo' => 'tavoli',
            'tempio' => 'templi',
            'treno' => 'treni',
            'tuo' => 'tuoi',
            'uomo' => 'uomini',
            'uovo' => 'uova',
            'urlo' => 'urla',
            'valigia' => 'valigie',
            'vestigio' => 'vestigia',
            'vino' => 'vini',
            'viola' => 'viola',
            'zio' => 'zii',
        ];

        foreach ($irregulars as $singular => $plural) {
            yield new Substitution(new Word($singular), new Word($plural));
        }
    }
}


================================================
FILE: src/Rules/Italian/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Italian;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/Italian/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Italian;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/Italian/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Italian;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return iterable<Pattern> */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();
    }

    /** @return iterable<Pattern> */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return iterable<Pattern> */
    private static function getDefault(): iterable
    {
        // Invariable words (same form in singular and plural)
        $invariables = [
            'alpaca',
            'auto',
            'bar',
            'blu',
            'boia',
            'boomerang',
            'brindisi',
            'campus',
            'computer',
            'crisi',
            'crocevia',
            'dopocena',
            'film',
            'foto',
            'fuchsia',
            'gnu',
            'gorilla',
            'gru',
            'iguana',
            'kamikaze',
            'karaoke',
            'koala',
            'lama',
            'menu',
            'metropoli',
            'moto',
            'opossum',
            'panda',
            'quiz',
            'radio',
            're',
            'scacciapensieri',
            'serie',
            'smartphone',
            'sosia',
            'sottoscala',
            'specie',
            'sport',
            'tablet',
            'taxi',
            'vaglia',
            'virtù',
            'virus',
            'yogurt',
            'foto',
            'fuchsia',
        ];

        foreach ($invariables as $word) {
            yield new Pattern($word);
        }
    }
}


================================================
FILE: src/Rules/NorwegianBokmal/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\NorwegianBokmal;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('/re$/i'), 'r');
        yield new Transformation(new Pattern('/er$/i'), '');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('/e$/i'), 'er');
        yield new Transformation(new Pattern('/r$/i'), 're');
        yield new Transformation(new Pattern('/$/'), 'er');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word('konto'), new Word('konti'));
    }
}


================================================
FILE: src/Rules/NorwegianBokmal/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\NorwegianBokmal;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/NorwegianBokmal/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\NorwegianBokmal;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/NorwegianBokmal/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\NorwegianBokmal;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('barn');
        yield new Pattern('fjell');
        yield new Pattern('hus');
    }
}


================================================
FILE: src/Rules/Pattern.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

use function preg_match;

final class Pattern
{
    /** @var string */
    private $pattern;

    /** @var string */
    private $regex;

    public function __construct(string $pattern)
    {
        $this->pattern = $pattern;

        if (isset($this->pattern[0]) && $this->pattern[0] === '/') {
            $this->regex = $this->pattern;
        } else {
            $this->regex = '/' . $this->pattern . '/i';
        }
    }

    public function getPattern(): string
    {
        return $this->pattern;
    }

    public function getRegex(): string
    {
        return $this->regex;
    }

    public function matches(string $word): bool
    {
        return preg_match($this->getRegex(), $word) === 1;
    }
}


================================================
FILE: src/Rules/Patterns.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

use function array_map;
use function implode;
use function preg_match;

class Patterns
{
    /** @var string */
    private $regex;

    public function __construct(Pattern ...$patterns)
    {
        $patterns = array_map(static function (Pattern $pattern): string {
            return $pattern->getPattern();
        }, $patterns);

        $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i';
    }

    public function matches(string $word): bool
    {
        return preg_match($this->regex, $word, $regs) === 1;
    }
}


================================================
FILE: src/Rules/Portuguese/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Portuguese;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('/^(g|)ases$/i'), '\1ás');
        yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\1ês');
        yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao');
        yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão');
        yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\1');
        yield new Transformation(new Pattern('/sses$/i'), 'sse');
        yield new Transformation(new Pattern('/ns$/i'), 'm');
        yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\1il');
        yield new Transformation(new Pattern('/uis$/i'), 'ul');
        yield new Transformation(new Pattern('/ois$/i'), 'ol');
        yield new Transformation(new Pattern('/eis$/i'), 'ei');
        yield new Transformation(new Pattern('/éis$/i'), 'el');
        yield new Transformation(new Pattern('/([^p])ais$/i'), '\1al');
        yield new Transformation(new Pattern('/(r|z)es$/i'), '\1');
        yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\1s');
        yield new Transformation(new Pattern('/([^ê])s$/i'), '\1');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\1aes');
        yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\1aos');
        yield new Transformation(new Pattern('/ao$/i'), 'oes');
        yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\1ães');
        yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\1ãos');
        yield new Transformation(new Pattern('/ão$/i'), 'ões');
        yield new Transformation(new Pattern('/^(|g)ás$/i'), '\1ases');
        yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\1eses');
        yield new Transformation(new Pattern('/m$/i'), 'ns');
        yield new Transformation(new Pattern('/([^aeou])il$/i'), '\1is');
        yield new Transformation(new Pattern('/ul$/i'), 'uis');
        yield new Transformation(new Pattern('/ol$/i'), 'ois');
        yield new Transformation(new Pattern('/el$/i'), 'eis');
        yield new Transformation(new Pattern('/al$/i'), 'ais');
        yield new Transformation(new Pattern('/(z|r)$/i'), '\1es');
        yield new Transformation(new Pattern('/(s)$/i'), '\1');
        yield new Transformation(new Pattern('/$/'), 's');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word('abdomen'), new Word('abdomens'));
        yield new Substitution(new Word('alemão'), new Word('alemães'));
        yield new Substitution(new Word('artesã'), new Word('artesãos'));
        yield new Substitution(new Word('álcool'), new Word('álcoois'));
        yield new Substitution(new Word('árvore'), new Word('árvores'));
        yield new Substitution(new Word('bencão'), new Word('bencãos'));
        yield new Substitution(new Word('cão'), new Word('cães'));
        yield new Substitution(new Word('campus'), new Word('campi'));
        yield new Substitution(new Word('cadáver'), new Word('cadáveres'));
        yield new Substitution(new Word('capelão'), new Word('capelães'));
        yield new Substitution(new Word('capitão'), new Word('capitães'));
        yield new Substitution(new Word('chão'), new Word('chãos'));
        yield new Substitution(new Word('charlatão'), new Word('charlatães'));
        yield new Substitution(new Word('cidadão'), new Word('cidadãos'));
        yield new Substitution(new Word('cônsul'), new Word('cônsules'));
        yield new Substitution(new Word('cristão'), new Word('cristãos'));
        yield new Substitution(new Word('difícil'), new Word('difíceis'));
        yield new Substitution(new Word('email'), new Word('emails'));
        yield new Substitution(new Word('escrivão'), new Word('escrivães'));
        yield new Substitution(new Word('fóssil'), new Word('fósseis'));
        yield new Substitution(new Word('gás'), new Word('gases'));
        yield new Substitution(new Word('germens'), new Word('germen'));
        yield new Substitution(new Word('grão'), new Word('grãos'));
        yield new Substitution(new Word('hífen'), new Word('hífens'));
        yield new Substitution(new Word('irmão'), new Word('irmãos'));
        yield new Substitution(new Word('liquens'), new Word('liquen'));
        yield new Substitution(new Word('mal'), new Word('males'));
        yield new Substitution(new Word('mão'), new Word('mãos'));
        yield new Substitution(new Word('mês'), new Word('meses'));
        yield new Substitution(new Word('orfão'), new Word('orfãos'));
        yield new Substitution(new Word('país'), new Word('países'));
        yield new Substitution(new Word('pai'), new Word('pais'));
        yield new Substitution(new Word('pão'), new Word('pães'));
        yield new Substitution(new Word('projétil'), new Word('projéteis'));
        yield new Substitution(new Word('réptil'), new Word('répteis'));
        yield new Substitution(new Word('sacristão'), new Word('sacristães'));
        yield new Substitution(new Word('sotão'), new Word('sotãos'));
        yield new Substitution(new Word('tabelião'), new Word('tabeliães'));
        yield new Substitution(new Word('útil'), new Word('úteis'));
    }
}


================================================
FILE: src/Rules/Portuguese/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Portuguese;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/Portuguese/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Portuguese;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/Portuguese/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Portuguese;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('atlas');
        yield new Pattern('bate-papo');
        yield new Pattern('cais');
        yield new Pattern('fênix');
        yield new Pattern('guarda-chuva');
        yield new Pattern('guarda-roupa');
        yield new Pattern('lápis');
        yield new Pattern('oásis');
        yield new Pattern('ônibus');
        yield new Pattern('ônus');
        yield new Pattern('pára-brisa');
        yield new Pattern('pára-choque');
        yield new Pattern('pires');
        yield new Pattern('porta-malas');
        yield new Pattern('porta-voz');
        yield new Pattern('sem-terra');
        yield new Pattern('tênis');
        yield new Pattern('tórax');
        yield new Pattern('vírus');
    }
}


================================================
FILE: src/Rules/Ruleset.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

class Ruleset
{
    /** @var Transformations */
    private $regular;

    /** @var Patterns */
    private $uninflected;

    /** @var Substitutions */
    private $irregular;

    public function __construct(Transformations $regular, Patterns $uninflected, Substitutions $irregular)
    {
        $this->regular     = $regular;
        $this->uninflected = $uninflected;
        $this->irregular   = $irregular;
    }

    public function getRegular(): Transformations
    {
        return $this->regular;
    }

    public function getUninflected(): Patterns
    {
        return $this->uninflected;
    }

    public function getIrregular(): Substitutions
    {
        return $this->irregular;
    }
}


================================================
FILE: src/Rules/Spanish/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Spanish;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('/ereses$/'), 'erés');
        yield new Transformation(new Pattern('/iones$/'), 'ión');
        yield new Transformation(new Pattern('/ces$/'), 'z');
        yield new Transformation(new Pattern('/es$/'), '');
        yield new Transformation(new Pattern('/s$/'), '');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('/ú([sn])$/i'), 'u\1es');
        yield new Transformation(new Pattern('/ó([sn])$/i'), 'o\1es');
        yield new Transformation(new Pattern('/í([sn])$/i'), 'i\1es');
        yield new Transformation(new Pattern('/é([sn])$/i'), 'e\1es');
        yield new Transformation(new Pattern('/á([sn])$/i'), 'a\1es');
        yield new Transformation(new Pattern('/z$/i'), 'ces');
        yield new Transformation(new Pattern('/([aeiou]s)$/i'), '\1');
        yield new Transformation(new Pattern('/([^aeéiou])$/i'), '\1es');
        yield new Transformation(new Pattern('/$/'), 's');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word('el'), new Word('los'));
        yield new Substitution(new Word('papá'), new Word('papás'));
        yield new Substitution(new Word('mamá'), new Word('mamás'));
        yield new Substitution(new Word('sofá'), new Word('sofás'));
        yield new Substitution(new Word('mes'), new Word('meses'));
    }
}


================================================
FILE: src/Rules/Spanish/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Spanish;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/Spanish/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Spanish;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/Spanish/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Spanish;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('lunes');
        yield new Pattern('rompecabezas');
        yield new Pattern('crisis');
    }
}


================================================
FILE: src/Rules/Substitution.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

final class Substitution
{
    /** @var Word */
    private $from;

    /** @var Word */
    private $to;

    public function __construct(Word $from, Word $to)
    {
        $this->from = $from;
        $this->to   = $to;
    }

    public function getFrom(): Word
    {
        return $this->from;
    }

    public function getTo(): Word
    {
        return $this->to;
    }
}


================================================
FILE: src/Rules/Substitutions.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

use Doctrine\Inflector\WordInflector;

use function strtolower;
use function strtoupper;
use function substr;

class Substitutions implements WordInflector
{
    /** @var Substitution[] */
    private $substitutions;

    public function __construct(Substitution ...$substitutions)
    {
        foreach ($substitutions as $substitution) {
            $this->substitutions[$substitution->getFrom()->getWord()] = $substitution;
        }
    }

    public function getFlippedSubstitutions(): Substitutions
    {
        $substitutions = [];

        foreach ($this->substitutions as $substitution) {
            $substitutions[] = new Substitution(
                $substitution->getTo(),
                $substitution->getFrom()
            );
        }

        return new Substitutions(...$substitutions);
    }

    public function inflect(string $word): string
    {
        $lowerWord = strtolower($word);

        if (isset($this->substitutions[$lowerWord])) {
            $firstLetterUppercase = $lowerWord[0] !== $word[0];

            $toWord = $this->substitutions[$lowerWord]->getTo()->getWord();

            if ($firstLetterUppercase) {
                return strtoupper($toWord[0]) . substr($toWord, 1);
            }

            return $toWord;
        }

        return $word;
    }
}


================================================
FILE: src/Rules/Transformation.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

use Doctrine\Inflector\WordInflector;

use function preg_replace;

final class Transformation implements WordInflector
{
    /** @var Pattern */
    private $pattern;

    /** @var string */
    private $replacement;

    public function __construct(Pattern $pattern, string $replacement)
    {
        $this->pattern     = $pattern;
        $this->replacement = $replacement;
    }

    public function getPattern(): Pattern
    {
        return $this->pattern;
    }

    public function getReplacement(): string
    {
        return $this->replacement;
    }

    public function inflect(string $word): string
    {
        return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word);
    }
}


================================================
FILE: src/Rules/Transformations.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

use Doctrine\Inflector\WordInflector;

class Transformations implements WordInflector
{
    /** @var Transformation[] */
    private $transformations;

    public function __construct(Transformation ...$transformations)
    {
        $this->transformations = $transformations;
    }

    public function inflect(string $word): string
    {
        foreach ($this->transformations as $transformation) {
            if ($transformation->getPattern()->matches($word)) {
                return $transformation->inflect($word);
            }
        }

        return $word;
    }
}


================================================
FILE: src/Rules/Turkish/Inflectible.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Turkish;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;

class Inflectible
{
    /** @return Transformation[] */
    public static function getSingular(): iterable
    {
        yield new Transformation(new Pattern('/l[ae]r$/i'), '');
    }

    /** @return Transformation[] */
    public static function getPlural(): iterable
    {
        yield new Transformation(new Pattern('/([eöiü][^aoıueöiü]{0,6})$/u'), '\1ler');
        yield new Transformation(new Pattern('/([aoıu][^aoıueöiü]{0,6})$/u'), '\1lar');
    }

    /** @return Substitution[] */
    public static function getIrregular(): iterable
    {
        yield new Substitution(new Word('ben'), new Word('biz'));
        yield new Substitution(new Word('sen'), new Word('siz'));
        yield new Substitution(new Word('o'), new Word('onlar'));
    }
}


================================================
FILE: src/Rules/Turkish/InflectorFactory.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Turkish;

use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;

final class InflectorFactory extends GenericLanguageInflectorFactory
{
    protected function getSingularRuleset(): Ruleset
    {
        return Rules::getSingularRuleset();
    }

    protected function getPluralRuleset(): Ruleset
    {
        return Rules::getPluralRuleset();
    }
}


================================================
FILE: src/Rules/Turkish/Rules.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Turkish;

use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;

final class Rules
{
    public static function getSingularRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getSingular()),
            new Patterns(...Uninflected::getSingular()),
            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
        );
    }

    public static function getPluralRuleset(): Ruleset
    {
        return new Ruleset(
            new Transformations(...Inflectible::getPlural()),
            new Patterns(...Uninflected::getPlural()),
            new Substitutions(...Inflectible::getIrregular())
        );
    }
}


================================================
FILE: src/Rules/Turkish/Uninflected.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules\Turkish;

use Doctrine\Inflector\Rules\Pattern;

final class Uninflected
{
    /** @return Pattern[] */
    public static function getSingular(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    public static function getPlural(): iterable
    {
        yield from self::getDefault();
    }

    /** @return Pattern[] */
    private static function getDefault(): iterable
    {
        yield new Pattern('lunes');
        yield new Pattern('rompecabezas');
        yield new Pattern('crisis');
    }
}


================================================
FILE: src/Rules/Word.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector\Rules;

class Word
{
    /** @var string */
    private $word;

    public function __construct(string $word)
    {
        $this->word = $word;
    }

    public function getWord(): string
    {
        return $this->word;
    }
}


================================================
FILE: src/RulesetInflector.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

use Doctrine\Inflector\Rules\Ruleset;

use function array_merge;

/**
 * Inflects based on multiple rulesets.
 *
 * Rules:
 * - If the word matches any uninflected word pattern, it is not inflected
 * - The first ruleset that returns a different value for an irregular word wins
 * - The first ruleset that returns a different value for a regular word wins
 * - If none of the above match, the word is left as-is
 */
class RulesetInflector implements WordInflector
{
    /** @var Ruleset[] */
    private $rulesets;

    public function __construct(Ruleset $ruleset, Ruleset ...$rulesets)
    {
        $this->rulesets = array_merge([$ruleset], $rulesets);
    }

    public function inflect(string $word): string
    {
        if ($word === '') {
            return '';
        }

        foreach ($this->rulesets as $ruleset) {
            if ($ruleset->getUninflected()->matches($word)) {
                return $word;
            }

            $inflected = $ruleset->getIrregular()->inflect($word);

            if ($inflected !== $word) {
                return $inflected;
            }

            $inflected = $ruleset->getRegular()->inflect($word);

            if ($inflected !== $word) {
                return $inflected;
            }
        }

        return $word;
    }
}


================================================
FILE: src/WordInflector.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Inflector;

interface WordInflector
{
    public function inflect(string $word): string;
}


================================================
FILE: tests/CachedWordInflectorTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector;

use Doctrine\Inflector\CachedWordInflector;
use Doctrine\Inflector\WordInflector;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class CachedWordInflectorTest extends TestCase
{
    /** @var WordInflector&MockObject */
    private $wordInflector;

    /** @var CachedWordInflector */
    private $cachedWordInflector;

    public function testInflect(): void
    {
        $this->wordInflector->expects(self::once())
            ->method('inflect')
            ->with('in')
            ->willReturn('out');

        self::assertSame('out', $this->cachedWordInflector->inflect('in'));
        self::assertSame('out', $this->cachedWordInflector->inflect('in'));
    }

    protected function setUp(): void
    {
        $this->wordInflector = $this->createMock(WordInflector::class);

        $this->cachedWordInflector = new CachedWordInflector(
            $this->wordInflector
        );
    }
}


================================================
FILE: tests/InflectorFactoryTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector;

use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Inflector\LanguageInflectorFactory;
use Doctrine\Inflector\Rules\English\InflectorFactory as EnglishInflectorFactory;
use Doctrine\Inflector\Rules\Esperanto\InflectorFactory as EsperantoInflectorFactory;
use Doctrine\Inflector\Rules\French\InflectorFactory as FrenchInflectorFactory;
use Doctrine\Inflector\Rules\NorwegianBokmal\InflectorFactory as NorwegianBokmalInflectorFactory;
use Doctrine\Inflector\Rules\Portuguese\InflectorFactory as PortugueseInflectorFactory;
use Doctrine\Inflector\Rules\Spanish\InflectorFactory as SpanishInflectorFactory;
use Doctrine\Inflector\Rules\Turkish\InflectorFactory as TurkishInflectorFactory;
use Generator;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class InflectorFactoryTest extends TestCase
{
    public function testCreateUsesEnglishByDefault(): void
    {
        self::assertInstanceOf(EnglishInflectorFactory::class, InflectorFactory::create());
    }

    /**
     * @phpstan-param class-string<LanguageInflectorFactory> $expectedClass
     *
     * @dataProvider provideLanguages
     */
    #[DataProvider('provideLanguages')]
    public function testCreateForLanguageWithCustomLanguage(string $expectedClass, string $language): void
    {
        self::assertInstanceOf($expectedClass, InflectorFactory::createForLanguage($language));
    }

    /** @phpstan-return Generator<string, array{class-string<LanguageInflectorFactory>, Language::*}> */
    public static function provideLanguages(): Generator
    {
        yield 'English' => [EnglishInflectorFactory::class, Language::ENGLISH];
        yield 'Esperanto' => [EsperantoInflectorFactory::class, Language::ESPERANTO];
        yield 'French' => [FrenchInflectorFactory::class, Language::FRENCH];
        yield 'Norwegian Bokmal' => [NorwegianBokmalInflectorFactory::class, Language::NORWEGIAN_BOKMAL];
        yield 'Portuguese' => [PortugueseInflectorFactory::class, Language::PORTUGUESE];
        yield 'Spanish' => [SpanishInflectorFactory::class, Language::SPANISH];
        yield 'Turkish' => [TurkishInflectorFactory::class, Language::TURKISH];
    }

    public function testCreateForLanguageThrowsInvalidArgumentExceptionForUnsupportedLanguage(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Language "invalid" is not supported.');

        InflectorFactory::createForLanguage('invalid')->build();
    }
}


================================================
FILE: tests/InflectorFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class InflectorFunctionalTest extends TestCase
{
    public function testCapitalize(): void
    {
        self::assertSame(
            'Top-O-The-Morning To All_of_you!',
            $this->createInflector()->capitalize('top-o-the-morning to all_of_you!')
        );
    }

    public function testCapitalizeWithCustomDelimiters(): void
    {
        self::assertSame(
            'Top-O-The-Morning To All_Of_You!',
            $this->createInflector()->capitalize('top-o-the-morning to all_of_you!', '-_ ')
        );
    }

    /** @dataProvider dataStringsTableize */
    #[DataProvider('dataStringsTableize')]
    public function testTableize(string $expected, string $word): void
    {
        self::assertSame($expected, $this->createInflector()->tableize($word));
    }

    /**
     * Strings which are used for testTableize.
     *
     * @return string[][]
     */
    public static function dataStringsTableize(): array
    {
        // In the format array('expected', 'word')
        return [
            ['', ''],
            ['foo_bar', 'FooBar'],
            ['f0o_bar', 'F0oBar'],
        ];
    }

    /** @dataProvider dataStringsClassify */
    #[DataProvider('dataStringsClassify')]
    public function testClassify(string $expected, string $word): void
    {
        self::assertSame($expected, $this->createInflector()->classify($word));
    }

    /**
     * Strings which are used for testClassify.
     *
     * @return string[][]
     */
    public static function dataStringsClassify(): array
    {
        // In the format array('expected', 'word')
        return [
            ['', ''],
            ['FooBar', 'foo_bar'],
            ['FooBar', 'foo bar'],
            ['F0oBar', 'f0o bar'],
            ['F0oBar', 'f0o  bar'],
            ['FooBar', 'foo_bar_'],
        ];
    }

    /** @dataProvider dataStringsCamelize */
    #[DataProvider('dataStringsCamelize')]
    public function testCamelize(string $expected, string $word): void
    {
        self::assertSame($expected, $this->createInflector()->camelize($word));
    }

    /**
     * Strings which are used for testCamelize.
     *
     * @return string[][]
     */
    public static function dataStringsCamelize(): array
    {
        // In the format array('expected', 'word')
        return [
            ['', ''],
            ['fooBar', 'foo_bar'],
            ['fooBar', 'foo bar'],
            ['f0oBar', 'f0o bar'],
            ['f0oBar', 'f0o  bar'],
        ];
    }

    private function createInflector(): Inflector
    {
        return InflectorFactory::create()->build();
    }
}


================================================
FILE: tests/InflectorTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\WordInflector;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\Stub;
use PHPUnit\Framework\TestCase;

class InflectorTest extends TestCase
{
    /** @var WordInflector&Stub */
    private $singularInflector;

    /** @var WordInflector&Stub */
    private $pluralInflector;

    /** @var Inflector */
    private $inflector;

    public function testTableize(): void
    {
        self::assertSame('model_name', $this->inflector->tableize('ModelName'));
        self::assertSame('model_name', $this->inflector->tableize('modelName'));
        self::assertSame('model_name', $this->inflector->tableize('model_name'));
    }

    public function testClassify(): void
    {
        self::assertSame('ModelName', $this->inflector->classify('model_name'));
        self::assertSame('ModelName', $this->inflector->classify('modelName'));
        self::assertSame('ModelName', $this->inflector->classify('ModelName'));
    }

    public function testCamelize(): void
    {
        self::assertSame('modelName', $this->inflector->camelize('ModelName'));
        self::assertSame('modelName', $this->inflector->camelize('model_name'));
        self::assertSame('modelName', $this->inflector->camelize('modelName'));
    }

    public function testCapitalize(): void
    {
        self::assertSame(
            'Top-O-The-Morning To All_of_you!',
            $this->inflector->capitalize('top-o-the-morning to all_of_you!')
        );
    }

    public function testSeemsUtf8(): void
    {
        self::assertTrue($this->inflector->seemsUtf8('teléfono'));
        self::assertTrue($this->inflector->seemsUtf8('král'));
        self::assertTrue($this->inflector->seemsUtf8('telephone'));
    }

    public function testUnaccent(): void
    {
        self::assertSame('telefono', $this->inflector->unaccent('teléfono'));
        self::assertSame('telephone', $this->inflector->unaccent('telephone'));
    }

    /** @dataProvider dataStringsUrlize */
    #[DataProvider('dataStringsUrlize')]
    public function testUrlize(string $expected, string $string): void
    {
        self::assertSame(
            $expected,
            $this->inflector->urlize($string)
        );
    }

    /**
     * Strings which are used for testUrlize.
     *
     * @return string[][]
     */
    public static function dataStringsUrlize(): array
    {
        return [
            [
                'testing-creating-a-slug-from-a-random-string',
                'Testing_Creating a -Slug from a random-string!@#',
            ],
            [
                'contesta-el-telefono',
                'Contesta el teléfono',
            ],
            [
                'den-hund-fuettern',
                'den hund füttern',
            ],
            [
                'jsem-kral-na-severu',
                'Jsem král na severu',
            ],
            [
                'test1-test2',
                'test1::test2',
            ],
            [
                'test1-test2',
                'test1$test2',
            ],
            [
                'testab-test2',
                'TESTAb-test2',
            ],
            [
                'ano',
                'año',
            ],
        ];
    }

    public function testPluralize(): void
    {
        $pluralInflector = $this->createMock(WordInflector::class);

        $pluralInflector->expects(self::once())
            ->method('inflect')
            ->with('in')
            ->willReturn('out');

        $inflector = new Inflector($this->singularInflector, $pluralInflector);

        self::assertSame('out', $inflector->pluralize('in'));
    }

    public function testSingularize(): void
    {
        $singularInflector = $this->createMock(WordInflector::class);

        $inflector = new Inflector($singularInflector, $this->pluralInflector);
        $singularInflector->expects(self::once())
            ->method('inflect')
            ->with('in')
            ->willReturn('out');

        self::assertSame('out', $inflector->singularize('in'));
    }

    protected function setUp(): void
    {
        $this->singularInflector = self::createStub(WordInflector::class);
        $this->pluralInflector   = self::createStub(WordInflector::class);

        $this->inflector = new Inflector($this->singularInflector, $this->pluralInflector);
    }
}


================================================
FILE: tests/NoopWordInflectorTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector;

use Doctrine\Inflector\NoopWordInflector;
use PHPUnit\Framework\TestCase;

class NoopWordInflectorTest extends TestCase
{
    /** @var NoopWordInflector */
    private $inflector;

    public function testInflect(): void
    {
        self::assertSame('foo', $this->inflector->inflect('foo'));
        self::assertSame('bar', $this->inflector->inflect('bar'));
    }

    protected function setUp(): void
    {
        $this->inflector = new NoopWordInflector();
    }
}


================================================
FILE: tests/Rules/English/EnglishFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\English;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;
use PHPUnit\Framework\Attributes\DataProvider;

use function sprintf;

class EnglishFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['', ''],
            ['ability', 'abilities'],
            ['abuse', 'abuses'],
            ['acceptancecriterion', 'acceptancecriteria'],
            ['address', 'addresses'],
            ['advice', 'advice'],
            ['agency', 'agencies'],
            ['aircraft', 'aircraft'],
            ['alga', 'algae'],
            ['alias', 'aliases'],
            ['alumnus', 'alumni'],
            ['amoyese', 'amoyese'],
            ['analysis', 'analyses'],
            ['aquarium', 'aquaria'],
            ['arch', 'arches'],
            ['archive', 'archives'],
            ['art', 'art'],
            ['atlas', 'atlases'],
            ['audio', 'audio'],
            ['avalanche', 'avalanches'],
            ['axe', 'axes'],
            ['baby', 'babies'],
            ['bacillus', 'bacilli'],
            ['bacterium', 'bacteria'],
            ['baggage', 'baggage'],
            ['basis', 'bases'],
            ['bison', 'bison'],
            ['blouse', 'blouses'],
            ['borghese', 'borghese'],
            ['box', 'boxes'],
            ['bream', 'bream'],
            ['breeches', 'breeches'],
            ['britches', 'britches'],
            ['buffalo', 'buffalo'],
            ['bureau', 'bureaus'],
            ['bus', 'buses'],
            ['butter', 'butter'],
            ['cache', 'caches'],
            ['cactus', 'cacti'],
            ['cafe', 'cafes'],
            ['calf', 'calves'],
            ['cantus', 'cantus'],
            ['canvas', 'canvases'],
            ['carp', 'carp'],
            ['case', 'cases'],
            ['cave', 'caves'],
            ['categoria', 'categorias'],
            ['category', 'categories'],
            ['cattle', 'cattle'],
            ['chassis', 'chassis'],
            ['chateau', 'chateaux'],
            ['cherry', 'cherries'],
            ['child', 'children'],
            ['church', 'churches'],
            ['circus', 'circuses'],
            ['city', 'cities'],
            ['clippers', 'clippers'],
            ['clothes', 'clothes'],
            ['clothing', 'clothing'],
            ['coal', 'coal'],
            ['cod', 'cod'],
            ['coitus', 'coitus'],
            ['comment', 'comments'],
            ['compensation', 'compensation'],
            ['congoese', 'congoese'],
            ['contretemps', 'contretemps'],
            ['cookie', 'cookies'],
            ['brownie', 'brownies'],
            ['copy', 'copies'],
            ['coreopsis', 'coreopsis'],
            ['corps', 'corps'],
            ['cotton', 'cotton'],
            ['cow', 'cows'],
            ['crisis', 'crises'],
            ['criterion', 'criteria'],
            ['currency', 'currencies'],
            ['curriculum', 'curricula'],
            ['curve', 'curves'],
            ['data', 'data'],
            ['database', 'databases'],
            ['day', 'days'],
            ['debris', 'debris'],
            ['deer', 'deer'],
            ['demo', 'demos'],
            ['die', 'dice'],
            ['diabetes', 'diabetes'],
            ['diagnosis', 'diagnoses'],
            ['diagnosis_a', 'diagnosis_as'],
            ['dictionary', 'dictionaries'],
            ['dive', 'dives'],
            ['djinn', 'djinn'],
            ['domino', 'dominoes'],
            ['dwarf', 'dwarves'],
            ['echo', 'echoes'],
            ['edge', 'edges'],
            ['education', 'education'],
            ['eland', 'eland'],
            ['elf', 'elves'],
            ['elk', 'elk'],
            ['emoji', 'emoji'],
            ['emphasis', 'emphases'],
            ['employee-child', 'employee-children'],
            ['energy', 'energies'],
            ['epoch', 'epochs'],
            ['equipment', 'equipment'],
            ['evidence', 'evidence'],
            ['experience', 'experiences'],
            ['family', 'families'],
            ['faroese', 'faroese'],
            ['fax', 'faxes'],
            ['feedback', 'feedback'],
            ['fish', 'fish'],
            ['fix', 'fixes'],
            ['flounder', 'flounder'],
            ['flour', 'flour'],
            ['flush', 'flushes'],
            ['fly', 'flies'],
            ['focus', 'foci'],
            ['foe', 'foes'],
            ['foobar', 'foobars'],
            ['foochowese', 'foochowese'],
            ['food', 'food'],
            ['food_menu', 'food_menus'],
            ['foodmenu', 'foodmenus'],
            ['foot', 'feet'],
            ['fungus', 'fungi'],
            ['furniture', 'furniture'],
            ['gallows', 'gallows'],
            ['gas', 'gases'],
            ['genevese', 'genevese'],
            ['genoese', 'genoese'],
            ['genus', 'genera'],
            ['gilbertese', 'gilbertese'],
            ['glove', 'gloves'],
            ['gold', 'gold'],
            ['goose', 'geese'],
            ['grave', 'graves'],
            ['gulf', 'gulfs'],
            ['half', 'halves'],
            ['hardware', 'hardware'],
            ['headquarters', 'headquarters'],
            ['hero', 'heroes'],
            ['herpes', 'herpes'],
            ['hijinks', 'hijinks'],
            ['hippopotamus', 'hippopotami'],
            ['hoax', 'hoaxes'],
            ['homework', 'homework'],
            ['horse', 'horses'],
            ['hottentotese', 'hottentotese'],
            ['house', 'houses'],
            ['human', 'humans'],
            ['identity', 'identities'],
            ['impatience', 'impatience'],
            ['index', 'indices'],
            ['information', 'information'],
            ['innings', 'innings'],
            ['iris', 'irises'],
            ['jackanapes', 'jackanapes'],
            ['jeans', 'jeans'],
            ['jedi', 'jedi'],
            ['kin', 'kin'],
            ['kiplingese', 'kiplingese'],
            ['kiss', 'kisses'],
            ['kitchenware', 'kitchenware'],
            ['knife', 'knives'],
            ['knowledge', 'knowledge'],
            ['kongoese', 'kongoese'],
            ['larva', 'larvae'],
            ['leaf', 'leaves'],
            ['leather', 'leather'],
            ['lens', 'lenses'],
            ['life', 'lives'],
            ['loaf', 'loaves'],
            ['louse', 'lice'],
            ['love', 'love'],
            ['lucchese', 'lucchese'],
            ['luggage', 'luggage'],
            ['mackerel', 'mackerel'],
            ['maltese', 'maltese'],
            ['man', 'men'],
            ['management', 'management'],
            ['matrix', 'matrices'],
            ['matrix_fu', 'matrix_fus'],
            ['matrix_row', 'matrix_rows'],
            ['medium', 'media'],
            ['memorandum', 'memoranda'],
            ['menu', 'menus'],
            ['mess', 'messes'],
            ['metadata', 'metadata'],
            ['mews', 'mews'],
            ['middleware', 'middleware'],
            ['money', 'money'],
            ['moose', 'moose'],
            ['motto', 'mottoes'],
            ['mouse', 'mice'],
            ['move', 'moves'],
            ['movie', 'movies'],
            ['mumps', 'mumps'],
            ['music', 'music'],
            ['my_analysis', 'my_analyses'],
            ['nankingese', 'nankingese'],
            ['neurosis', 'neuroses'],
            ['news', 'news'],
            ['newsletter', 'newsletters'],
            ['nexus', 'nexus'],
            ['niasese', 'niasese'],
            ['niveau', 'niveaux'],
            ['node_child', 'node_children'],
            ['nodemedia', 'nodemedia'],
            ['nucleus', 'nuclei'],
            ['nursery', 'nurseries'],
            ['nutrition', 'nutrition'],
            ['oasis', 'oases'],
            ['octopus', 'octopuses'],
            ['offspring', 'offspring'],
            ['oil', 'oil'],
            ['old_news', 'old_news'],
            ['olive', 'olives'],
            ['ox', 'oxen'],
            ['pactum', 'pacta'],
            ['pants', 'pants'],
            ['pass', 'passes'],
            ['passerby', 'passersby'],
            ['pasta', 'pastas'],
            ['patience', 'patience'],
            ['pekingese', 'pekingese'],
            ['person', 'people'],
            ['perspective', 'perspectives'],
            ['photo', 'photos'],
            ['piedmontese', 'piedmontese'],
            ['pincers', 'pincers'],
            ['pistoiese', 'pistoiese'],
            ['plankton', 'plankton'],
            ['plateau', 'plateaux'],
            ['pliers', 'pliers'],
            ['pokemon', 'pokemon'],
            ['police', 'police'],
            ['polish', 'polish'],
            ['portfolio', 'portfolios'],
            ['portuguese', 'portuguese'],
            ['potato', 'potatoes'],
            ['powerhouse', 'powerhouses'],
            ['prize', 'prizes'],
            ['proceedings', 'proceedings'],
            ['process', 'processes'],
            ['progress', 'progress'],
            ['query', 'queries'],
            ['quiz', 'quizzes'],
            ['rabies', 'rabies'],
            ['radius', 'radii'],
            ['rain', 'rain'],
            ['reflex', 'reflexes'],
            ['regatta', 'regattas'],
            ['research', 'research'],
            ['rhinoceros', 'rhinoceros'],
            ['rice', 'rice'],
            ['roof', 'roofs'],
            ['runner-up', 'runners-up'],
            ['safe', 'safes'],
            ['salesperson', 'salespeople'],
            ['salmon', 'salmon'],
            ['sand', 'sand'],
            ['sarawakese', 'sarawakese'],
            ['save', 'saves'],
            ['scarf', 'scarves'],
            ['scissors', 'scissors'],
            ['scratch', 'scratches'],
            ['sea-bass', 'sea-bass'],
            ['sea bass', 'sea bass'],
            ['search', 'searches'],
            ['series', 'series'],
            ['sex', 'sexes'],
            ['shavese', 'shavese'],
            ['shears', 'shears'],
            ['sheep', 'sheep'],
            ['shelf', 'shelves'],
            ['shoe', 'shoes'],
            ['shorts', 'shorts'],
            ['siemens', 'siemens'],
            ['sieve', 'sieves'],
            ['silk', 'silk'],
            ['sku', 'skus'],
            ['slice', 'slices'],
            ['sms', 'sms'],
            ['soap', 'soap'],
            ['social media', 'social media'],
            ['socialmedia', 'socialmedia'],
            ['software', 'software'],
            ['son-in-law', 'sons-in-law'],
            ['spam', 'spam'],
            ['species', 'species'],
            ['splash', 'splashes'],
            ['spokesman', 'spokesmen'],
            ['spouse', 'spouses'],
            ['spy', 'spies'],
            ['stack', 'stacks'],
            ['stadium', 'stadiums'],
            ['staff', 'staff'],
            ['status', 'statuses'],
            ['status_code', 'status_codes'],
            ['stimulus', 'stimuli'],
            ['stitch', 'stitches'],
            ['story', 'stories'],
            ['stratum', 'strata'],
            ['sugar', 'sugar'],
            ['swine', 'swine'],
            ['switch', 'switches'],
            ['syllabus', 'syllabi'],
            ['talent', 'talent'],
            ['tax', 'taxes'],
            ['taxi', 'taxis'],
            ['taxon', 'taxa'],
            ['terminus', 'termini'],
            ['testis', 'testes'],
            ['thesis', 'theses'],
            ['Thief', 'Thieves'],
            ['tights', 'tights'],
            ['tomato', 'tomatoes'],
            ['tooth', 'teeth'],
            ['toothpaste', 'toothpaste'],
            ['tornado', 'tornadoes'],
            ['traffic', 'traffic'],
            ['travel', 'travel'],
            ['trivia', 'trivia'],
            ['trousers', 'trousers'],
            ['trout', 'trout'],
            ['try', 'tries'],
            ['tuna', 'tuna'],
            ['us', 'us'],
            ['valve', 'valves'],
            ['vermontese', 'vermontese'],
            ['vertex', 'vertices'],
            ['vinegar', 'vinegar'],
            ['virus', 'viri'],
            ['volcano', 'volcanoes'],
            ['ware', 'wares'],
            ['wash', 'washes'],
            ['watch', 'watches'],
            ['wave', 'waves'],
            ['weather', 'weather'],
            ['wenchowese', 'wenchowese'],
            ['wharf', 'wharves'],
            ['wheat', 'wheat'],
            ['whiting', 'whiting'],
            ['wife', 'wives'],
            ['wildebeest', 'wildebeest'],
            ['wish', 'wishes'],
            ['woman', 'women'],
            ['wood', 'wood'],
            ['wool', 'wool'],
            ['work', 'works'],
            ['yengeese', 'yengeese'],
            ['zombie', 'zombies'],
            ['|ice', '|ices'],
        ];
    }

    /**
     * Singulars as Plural test data.
     *
     * A list of singulars that should not yield the given result if passed through `singularize`.
     * Returns an array of sample words.
     *
     * @return string[][]
     */
    public static function dataSingularsUninflectedWhenSingularized(): array
    {
        // In the format array('singular', 'notEquals')
        return [
            ['fuchsia', 'fuchsium'],
            ['militia', 'militium'],
            ['galleria', 'gallerium'],
            ['petunia', 'petunium'],
            ['trivia', 'trivium'],
            ['utopia', 'utopium'],
            ['sepia', 'sepium'],
            ['mafia', 'mafium'],
            ['regatta', 'regattum'],
            ['regattas', 'regattum'],
            ['pactum', 'pactums'],
            ['fascia', 'fascium'],
            ['status', 'statu'],
            ['stratum', 'strati'],
            ['stratum', 'stratums'],
            ['campus', 'campu'],
            ['axis', 'axes'],
        ];
    }

    /** @dataProvider dataSingularsUninflectedWhenSingularized */
    #[DataProvider('dataSingularsUninflectedWhenSingularized')]
    public function testSingularsWhenSingularizedShouldBeUninflected(string $singular, string $notEquals): void
    {
        self::assertNotSame(
            $notEquals,
            $this->createInflector()->singularize($singular),
            sprintf("'%s' should not be singularized to '%s'", $singular, $notEquals)
        );
    }

    /**
     * Words without plural test data.
     *
     * List of words that don't have a plural form.
     *
     * @return string[][]
     */
    public static function dataPluralUninflectedWhenPluralized(): array
    {
        return [
            ['media'],
        ];
    }

    /** @dataProvider dataPluralUninflectedWhenPluralized */
    #[DataProvider('dataPluralUninflectedWhenPluralized')]
    public function testPluralsWhenPluralizedShouldBeUninflected(string $plural): void
    {
        $pluralized = $this->createInflector()->pluralize($plural);

        self::assertSame(
            $plural,
            $pluralized,
            sprintf("'%s' should not be pluralized to '%s'", $plural, $pluralized)
        );
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::ENGLISH)->build();
    }
}


================================================
FILE: tests/Rules/Esperanto/EsperantoFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\Esperanto;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class EsperantoFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['abelo', 'abeloj'],
            ['ĉapelo', 'ĉapeloj'],
            ['domo', 'domoj'],
            ['eĥoŝanĝo', 'eĥoŝanĝoj'],
            ['fervojo', 'fervojoj'],
            ['lingvo', 'lingvoj'],
            ['manĝaĵo', 'manĝaĵoj'],
            ['muzikalo', 'muzikaloj'],
            ['terpomo', 'terpomoj'],
            ['vortaro', 'vortaroj'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::ESPERANTO)->build();
    }
}


================================================
FILE: tests/Rules/French/FrenchFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\French;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class FrenchFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['ami', 'amis'],
            ['chien', 'chiens'],
            ['fidèle', 'fidèles'],
            ['rapport', 'rapports'],
            ['sain', 'sains'],
            ['jouet', 'jouets'],
            ['bijou', 'bijoux'],
            ['caillou', 'cailloux'],
            ['chou', 'choux'],
            ['genou', 'genoux'],
            ['hibou', 'hiboux'],
            ['joujou', 'joujoux'],
            ['pou', 'poux'],
            ['gaz', 'gaz'],
            ['tuyau', 'tuyaux'],
            ['nouveau', 'nouveaux'],
            ['aveu', 'aveux'],
            ['bleu', 'bleus'],
            ['émeu', 'émeus'],
            ['landau', 'landaus'],
            ['lieu', 'lieux'],
            ['pneu', 'pneus'],
            ['sarrau', 'sarraus'],
            ['journal', 'journaux'],
            ['local', 'locaux'],
            ['détail', 'détails'],
            ['bail', 'baux'],
            ['corail', 'coraux'],
            ['émail', 'émaux'],
            ['gemmail', 'gemmaux'],
            ['soupirail', 'soupiraux'],
            ['travail', 'travaux'],
            ['vantail', 'vantaux'],
            ['vitrail', 'vitraux'],
            ['monsieur', 'messieurs'],
            ['madame', 'mesdames'],
            ['mademoiselle', 'mesdemoiselles'],
            ['chacal', 'chacals'],
            ['carnaval', 'carnavals'],
            ['festival', 'festivals'],
            ['récital', 'récitals'],
            ['bois', 'bois'],
            ['mas', 'mas'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::FRENCH)->build();
    }
}


================================================
FILE: tests/Rules/Italian/ItalianFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\Italian;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class ItalianFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            // Empty string and edge cases
            ['', ''],
            [' ', ' '],
            ['123', '123'],
            ['@#!', '@#!'],

            // Invariable nouns (same in singular and plural)
            ['re', 're'],
            ['città', 'città'],
            ['virtù', 'virtù'],
            ['specie', 'specie'],
            ['serie', 'serie'],
            ['crisi', 'crisi'],
            ['superficie', 'superfici'],
            ['metropoli', 'metropoli'],

            // Foreign words and loanwords
            ['film', 'film'],
            ['sport', 'sport'],
            ['bar', 'bar'],
            ['computer', 'computer'],
            ['menu', 'menu'],
            ['taxi', 'taxi'],
            ['quiz', 'quiz'],
            ['smartphone', 'smartphone'],
            ['tablet', 'tablet'],
            ['virus', 'virus'],
            ['campus', 'campus'],

            // Abbreviations and shortened forms
            ['foto', 'foto'],  // from fotografia
            ['moto', 'moto'],  // from motocicletta
            ['auto', 'auto'],  // from automobile

            // Words with accented vowels
            ['caffè', 'caffè'],
            ['tè', 'tè'],
            ['menù', 'menù'],

            // Compound words
            ['dopocena', 'dopocena'],
            ['sottoscala', 'sottoscala'],

            // Nouns with irregular patterns
            ['tempio', 'templi'],
            ['ala', 'ali'],
            ['mano', 'mani'],

            // Words with multiple plural forms
            ['braccio', 'braccia'],  // arm -> arms
            ['ginocchio', 'ginocchia'],  // body part
            ['dito', 'dita'],  // more common
            ['baco', 'bachi'],  // more common

            // Words that change meaning in plural
            ['membro', 'membri'],    // members of an organization
            ['membrana', 'membrane'],  // membranes

            // Words with identical forms but different genders/meanings
            ['capitale', 'capitali'],  // capital (money)
            ['capitale', 'capitali'], // capital city (context determines meaning)

            // Irregular plurals and exceptions
            ['uomo', 'uomini'],
            ['dio', 'dei'],
            ['bue', 'buoi'],

            // Nouns ending in -o (masculine)
            ['libro', 'libri'],
            ['tavolo', 'tavoli'],
            ['ragazzo', 'ragazzi'],

            // Nouns ending in -a (feminine)
            ['casa', 'case'],
            ['penna', 'penne'],
            ['amica', 'amiche'],

            // Nouns ending in -e
            ['fiore', 'fiori'],
            ['cane', 'cani'],
            ['chiave', 'chiavi'],

            // Nouns ending in -ca/ga
            ['banca', 'banche'],

            // Nouns ending in -cia/gia
            ['arancia', 'arance'],
            ['valigia', 'valigie'],
            ['camicia', 'camicie'],
            ['fascia', 'fasce'],
            ['farmacia', 'farmacie'],

            // Nouns ending in -co/go
            ['gioco', 'giochi'],
            ['fuoco', 'fuochi'],
            ['albergo', 'alberghi'],

            // Words that are the same in both singular and plural
            ['sosia', 'sosia'],
            ['vaglia', 'vaglia'],
            ['gorilla', 'gorilla'],
            ['yogurt', 'yogurt'],
            ['boomerang', 'boomerang'],
            ['kamikaze', 'kamikaze'],
            ['karaoke', 'karaoke'],
            ['brindisi', 'brindisi'],
            ['boia', 'boia'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::ITALIAN)->build();
    }
}


================================================
FILE: tests/Rules/LanguageFunctionalTestCase.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Inflector;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

use function sprintf;

abstract class LanguageFunctionalTestCase extends TestCase
{
    /** @return string[][] */
    abstract public static function dataSampleWords(): array;

    /** @dataProvider dataSampleWords */
    #[DataProvider('dataSampleWords')]
    public function testInflectingSingulars(string $singular, string $plural): void
    {
        self::assertSame(
            $singular,
            $this->createInflector()->singularize($plural),
            sprintf("'%s' should be singularized to '%s'", $plural, $singular)
        );
    }

    /** @dataProvider dataSampleWords */
    #[DataProvider('dataSampleWords')]
    public function testInflectingPlurals(string $singular, string $plural): void
    {
        self::assertSame(
            $plural,
            $this->createInflector()->pluralize($singular),
            sprintf("'%s' should be pluralized to '%s'", $singular, $plural)
        );
    }

    abstract protected function createInflector(): Inflector;
}


================================================
FILE: tests/Rules/NorwegianBokmal/NorwegianBokmalFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\NorwegianBokmal;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class NorwegianBokmalFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['dag', 'dager'],
            ['fjord', 'fjorder'],
            ['hund', 'hunder'],
            ['kalender', 'kalendere'],
            ['katt' , 'katter'],
            ['lærer', 'lærere'],
            ['test', 'tester'],
            ['konto', 'konti'],
            ['barn', 'barn'],
            ['fjell', 'fjell'],
            ['hus', 'hus'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::NORWEGIAN_BOKMAL)->build();
    }
}


================================================
FILE: tests/Rules/PatternTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Pattern;
use PHPUnit\Framework\TestCase;

class PatternTest extends TestCase
{
    /** @var Pattern */
    private $pattern;

    public function testGetPattern(): void
    {
        self::assertSame('test', $this->pattern->getPattern());
    }

    public function testGetRegex(): void
    {
        self::assertSame('/test/i', $this->pattern->getRegex());
    }

    public function testPatternWithExplicitRegex(): void
    {
        $pattern = new Pattern('/test/');

        self::assertSame('/test/', $pattern->getRegex());
    }

    public function testMatches(): void
    {
        self::assertTrue($this->pattern->matches('test'));
    }

    protected function setUp(): void
    {
        $this->pattern = new Pattern('test');
    }
}


================================================
FILE: tests/Rules/PatternsTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Patterns;
use PHPUnit\Framework\TestCase;

class PatternsTest extends TestCase
{
    /** @var Patterns */
    private $patterns;

    public function testMatches(): void
    {
        self::assertTrue($this->patterns->matches('test1'));
        self::assertFalse($this->patterns->matches('test2'));
    }

    protected function setUp(): void
    {
        $this->patterns = new Patterns(new Pattern('test1'));
    }
}


================================================
FILE: tests/Rules/Portuguese/PortugueseFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\Portuguese;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class PortugueseFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['livro', 'livros'],
            ['radio', 'radios'],
            ['senhor', 'senhores'],
            ['lei', 'leis'],
            ['rei', 'reis'],
            ['luz', 'luzes'],
            ['juiz', 'juizes'],
            ['avião', 'aviões'],
            ['cão', 'cães'],
            ['interesse', 'interesses'],
            ['ás', 'ases'],
            ['mão', 'mãos'],
            ['peão', 'peões'],
            ['casa', 'casas'],
            ['árvore', 'árvores'],
            ['cor', 'cores'],
            ['álbum', 'álbuns'],
            ['mulher', 'mulheres'],
            ['nação', 'nações'],
            ['país', 'países'],
            ['chão', 'chãos'],
            ['charlatão', 'charlatães'],
            ['cidadão', 'cidadãos'],
            ['cônsul', 'cônsules'],
            ['cristão', 'cristãos'],
            ['difícil', 'difíceis'],
            ['email', 'emails'],
            ['mês', 'meses'],
            ['útil', 'úteis'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::PORTUGUESE)->build();
    }
}


================================================
FILE: tests/Rules/RulesetTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Transformations;
use Doctrine\Inflector\Rules\Word;
use PHPUnit\Framework\TestCase;

class RulesetTest extends TestCase
{
    /** @var Transformations */
    private $regular;

    /** @var Patterns */
    private $uninflected;

    /** @var Substitutions */
    private $irregular;

    /** @var Ruleset */
    private $ruleset;

    public function testGetRegular(): void
    {
        self::assertSame($this->regular, $this->ruleset->getRegular());
    }

    public function testGetUninflected(): void
    {
        self::assertSame($this->uninflected, $this->ruleset->getUninflected());
    }

    public function testGetIrregular(): void
    {
        self::assertSame($this->irregular, $this->ruleset->getIrregular());
    }

    protected function setUp(): void
    {
        $this->regular     = new Transformations(
            new Transformation(new Pattern('test'), 'tests')
        );
        $this->uninflected = new Patterns(new Pattern('uninflected'));
        $this->irregular   = new Substitutions(
            new Substitution(new Word('test'), new Word('tests'))
        );

        $this->ruleset = new Ruleset(
            $this->regular,
            $this->uninflected,
            $this->irregular
        );
    }
}


================================================
FILE: tests/Rules/Spanish/SpanishFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\Spanish;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class SpanishFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['libro', 'libros'],
            ['pluma', 'plumas'],
            ['chico', 'chicos'],
            ['señora', 'señoras'],
            ['radio', 'radios'],
            ['borrador', 'borradores'],
            ['universidad', 'universidades'],
            ['profesor', 'profesores'],
            ['ciudad', 'ciudades'],
            ['señor', 'señores'],
            ['escultor', 'escultores'],
            ['sociedad', 'sociedades'],
            ['azul', 'azules'],
            ['mes', 'meses'],
            ['avión', 'aviones'],
            ['conversación', 'conversaciones'],
            ['sección', 'secciones'],
            ['televisión', 'televisiones'],
            ['interés', 'intereses'],
            ['lápiz', 'lápices'],
            ['voz', 'voces'],
            ['tapiz', 'tapices'],
            ['actriz', 'actrices'],
            ['luz', 'luces'],
            ['mez', 'meces'],
            ['tisú', 'tisúes'],
            ['hindú', 'hindúes'],
            ['ley', 'leyes'],
            ['café', 'cafés'],
            ['el', 'los'],
            ['lunes', 'lunes'],
            ['rompecabezas', 'rompecabezas'],
            ['crisis', 'crisis'],
            ['papá', 'papás'],
            ['mamá', 'mamás'],
            ['sofá', 'sofás'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::SPANISH)->build();
    }
}


================================================
FILE: tests/Rules/SubstitutionTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Word;
use PHPUnit\Framework\TestCase;

class SubstitutionTest extends TestCase
{
    /** @var Substitution */
    private $substitution;

    public function testGetFrom(): void
    {
        self::assertSame('from', $this->substitution->getFrom()->getWord());
    }

    public function testGetTo(): void
    {
        self::assertSame('to', $this->substitution->getTo()->getWord());
    }

    protected function setUp(): void
    {
        $this->substitution = new Substitution(new Word('from'), new Word('to'));
    }
}


================================================
FILE: tests/Rules/SubstitutionsTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Word;
use PHPUnit\Framework\TestCase;

class SubstitutionsTest extends TestCase
{
    /** @var Substitution[] */
    private $substitutions;

    /** @var Substitutions */
    private $irregular;

    public function testGetFlippedSubstitutions(): void
    {
        $substitutions = $this->irregular->getFlippedSubstitutions();

        self::assertSame('spins', $substitutions->inflect('spinor'));
    }

    public function testInflect(): void
    {
        self::assertSame('spinor', $this->irregular->inflect('spins'));
    }

    protected function setUp(): void
    {
        $this->substitutions = [
            new Substitution(new Word('spins'), new Word('spinor')),
        ];

        $this->irregular = new Substitutions(...$this->substitutions);
    }
}


================================================
FILE: tests/Rules/TransformationTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Transformation;
use PHPUnit\Framework\TestCase;

class TransformationTest extends TestCase
{
    /** @var Transformation */
    private $transformation;

    public function testGetPattern(): void
    {
        self::assertSame('s$', $this->transformation->getPattern()->getPattern());
    }

    public function testGetReplacement(): void
    {
        self::assertSame('', $this->transformation->getReplacement());
    }

    public function testInflect(): void
    {
        self::assertSame('test', $this->transformation->inflect('tests'));
    }

    protected function setUp(): void
    {
        $this->transformation = new Transformation(new Pattern('s$'), '');
    }
}


================================================
FILE: tests/Rules/TransformationsTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Transformations;
use PHPUnit\Framework\TestCase;

class TransformationsTest extends TestCase
{
    /** @var Transformations */
    private $transformations;

    public function testInflect(): void
    {
        self::assertSame('customizables', $this->transformations->inflect('custom'));
    }

    protected function setUp(): void
    {
        $this->transformations = new Transformations(new Transformation(new Pattern('/^(custom)$/i'), '\1izables'));
    }
}


================================================
FILE: tests/Rules/Turkish/TurkishFunctionalTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules\Turkish;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
use Doctrine\Tests\Inflector\Rules\LanguageFunctionalTestCase;

class TurkishFunctionalTest extends LanguageFunctionalTestCase
{
    /** @return string[][] */
    public static function dataSampleWords(): array
    {
        return [
            ['gün', 'günler'],
            ['kiraz', 'kirazlar'],
            ['kitap', 'kitaplar'],
            ['köpek', 'köpekler'],
            ['test', 'testler'],
            ['üçgen', 'üçgenler'],
            ['ben', 'biz'],
            ['sen', 'siz'],
            ['o', 'onlar'],
        ];
    }

    protected function createInflector(): Inflector
    {
        return InflectorFactory::createForLanguage(Language::TURKISH)->build();
    }
}


================================================
FILE: tests/Rules/WordTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector\Rules;

use Doctrine\Inflector\Rules\Word;
use PHPUnit\Framework\TestCase;

class WordTest extends TestCase
{
    /** @var Word */
    private $word;

    public function testGetWord(): void
    {
        self::assertSame('test', $this->word->getWord());
    }

    protected function setUp(): void
    {
        $this->word = new Word('test');
    }
}


================================================
FILE: tests/RulesetInflectorTest.php
================================================
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Inflector;

use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Transformations;
use Doctrine\Inflector\Rules\Word;
use Doctrine\Inflector\RulesetInflector;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class RulesetInflectorTest extends TestCase
{
    /** @dataProvider rulesetProvider */
    #[DataProvider('rulesetProvider')]
    public function testInflect(
        Ruleset $firstRuleset,
        Ruleset $secondRuleset,
        string $input,
        string $expected
    ): void {
        $inflector = new RulesetInflector($firstRuleset, $secondRuleset);

        self::assertSame($expected, $inflector->inflect($input));
    }

    /** @return iterable<string, array{Ruleset, Ruleset, string, string}> */
    public static function rulesetProvider(): iterable
    {
        yield 'irregular uses first match' => [
            new Ruleset(
                new Transformations(),
                new Patterns(),
                new Substitutions(new Substitution(new Word('in'), new Word('first')))
            ),
            new Ruleset(
                new Transformations(),
                new Patterns(),
                new Substitutions(new Substitution(new Word('in'), new Word('second')))
            ),
            'in',
            'first',
        ];

        yield 'irregular continues if first ruleset returns original value' => [
            new Ruleset(
                new Transformations(),
                new Patterns(),
                new Substitutions()
            ),
            new Ruleset(
                new Transformations(),
                new Patterns(),
                new Substitutions(new Substitution(new Word('in'), new Word('second')))
            ),
            'in',
            'second',
        ];

        yield 'uninflected skips on first match' => [
            new Ruleset(
                new Transformations(),
                new Patterns(new Pattern('in')),
                new Substitutions()
            ),
            new Ruleset(
                new Transformations(new Transformation(new Pattern('in'), 'should-not-reach')),
                new Patterns(),
                new Substitutions()
            ),
            'in',
            'in',
        ];

        yield 'irregular is inflected even if later ruleset ignores' => [
            new Ruleset(
                new Transformations(),
                new Patterns(),
                new Substitutions(new Substitution(new Word('travel'), new Word('travels')))
            ),
            new Ruleset(
                new Transformations(),
                new Patterns(new Pattern('travel')),
                new Substitutions()
            ),
            'travel',
            'travels',
        ];

        yield 'regular uses first match' => [
            new Ruleset(
                new Transformations(new Transformation(new Pattern('in'), 'first')),
                new Patterns(),
                new Substitutions()
            ),
            new Ruleset(
                new Transformations(new Transformation(new Pattern('in'), 'second')),
                new Patterns(),
                new Substitutions()
            ),
            'in',
            'first',
        ];

        yield 'regular continues if first ruleset returns original value' => [
            new Ruleset(
                new Transformations(new Transformation(new Pattern('nomatch'), 'first')),
                new Patterns(),
                new Substitutions()
            ),
            new Ruleset(
                new Transformations(new Transformation(new Pattern('in'), 'second')),
                new Patterns(),
                new Substitutions()
            ),
            'in',
            'second',
        ];

        yield 'returns original value on no matches' => [
            new Ruleset(
                new Transformations(new Transformation(new Pattern('nomatch'), 'replaced')),
                new Patterns(),
                new Substitutions()
            ),
            new Ruleset(
                new Transformations(new Transformation(new Pattern('nomatch'), 'replaced')),
                new Patterns(),
                new Substitutions()
            ),
            'in',
            'in',
        ];
    }
}
Download .txt
gitextract_4n9ri0yq/

├── .doctrine-project.json
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── coding-standards.yml
│       ├── composer-lint.yml
│       ├── continuous-integration.yml
│       ├── release-on-milestone-closed.yml
│       ├── static-analysis.yml
│       └── website-schema.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── docs/
│   └── en/
│       └── index.rst
├── phpcs.xml.dist
├── phpstan.neon.dist
├── phpunit.xml.dist
├── src/
│   ├── CachedWordInflector.php
│   ├── GenericLanguageInflectorFactory.php
│   ├── Inflector.php
│   ├── InflectorFactory.php
│   ├── Language.php
│   ├── LanguageInflectorFactory.php
│   ├── NoopWordInflector.php
│   ├── Rules/
│   │   ├── English/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Esperanto/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── French/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Italian/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── NorwegianBokmal/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Pattern.php
│   │   ├── Patterns.php
│   │   ├── Portuguese/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Ruleset.php
│   │   ├── Spanish/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   ├── Substitution.php
│   │   ├── Substitutions.php
│   │   ├── Transformation.php
│   │   ├── Transformations.php
│   │   ├── Turkish/
│   │   │   ├── Inflectible.php
│   │   │   ├── InflectorFactory.php
│   │   │   ├── Rules.php
│   │   │   └── Uninflected.php
│   │   └── Word.php
│   ├── RulesetInflector.php
│   └── WordInflector.php
└── tests/
    ├── CachedWordInflectorTest.php
    ├── InflectorFactoryTest.php
    ├── InflectorFunctionalTest.php
    ├── InflectorTest.php
    ├── NoopWordInflectorTest.php
    ├── Rules/
    │   ├── English/
    │   │   └── EnglishFunctionalTest.php
    │   ├── Esperanto/
    │   │   └── EsperantoFunctionalTest.php
    │   ├── French/
    │   │   └── FrenchFunctionalTest.php
    │   ├── Italian/
    │   │   └── ItalianFunctionalTest.php
    │   ├── LanguageFunctionalTestCase.php
    │   ├── NorwegianBokmal/
    │   │   └── NorwegianBokmalFunctionalTest.php
    │   ├── PatternTest.php
    │   ├── PatternsTest.php
    │   ├── Portuguese/
    │   │   └── PortugueseFunctionalTest.php
    │   ├── RulesetTest.php
    │   ├── Spanish/
    │   │   └── SpanishFunctionalTest.php
    │   ├── SubstitutionTest.php
    │   ├── SubstitutionsTest.php
    │   ├── TransformationTest.php
    │   ├── TransformationsTest.php
    │   ├── Turkish/
    │   │   └── TurkishFunctionalTest.php
    │   └── WordTest.php
    └── RulesetInflectorTest.php
Download .txt
SYMBOL INDEX (283 symbols across 72 files)

FILE: src/CachedWordInflector.php
  class CachedWordInflector (line 7) | class CachedWordInflector implements WordInflector
    method __construct (line 15) | public function __construct(WordInflector $wordInflector)
    method inflect (line 20) | public function inflect(string $word): string

FILE: src/GenericLanguageInflectorFactory.php
  class GenericLanguageInflectorFactory (line 11) | abstract class GenericLanguageInflectorFactory implements LanguageInflec...
    method __construct (line 19) | final public function __construct()
    method build (line 25) | final public function build(): Inflector
    method withSingularRules (line 37) | final public function withSingularRules(?Ruleset $singularRules, bool ...
    method withPluralRules (line 50) | final public function withPluralRules(?Ruleset $pluralRules, bool $res...
    method getSingularRuleset (line 63) | abstract protected function getSingularRuleset(): Ruleset;
    method getPluralRuleset (line 65) | abstract protected function getPluralRuleset(): Ruleset;

FILE: src/Inflector.php
  class Inflector (line 24) | class Inflector
    method __construct (line 223) | public function __construct(WordInflector $singularizer, WordInflector...
    method tableize (line 232) | public function tableize(string $word): string
    method classify (line 249) | public function classify(string $word): string
    method camelize (line 257) | public function camelize(string $word): string
    method capitalize (line 287) | public function capitalize(string $string, string $delimiters = " \n\t...
    method seemsUtf8 (line 297) | public function seemsUtf8(string $string): bool
    method unaccent (line 335) | public function unaccent(string $string): string
    method urlize (line 448) | public function urlize(string $string): string
    method singularize (line 491) | public function singularize(string $word): string
    method pluralize (line 503) | public function pluralize(string $word): string

FILE: src/InflectorFactory.php
  class InflectorFactory (line 19) | final class InflectorFactory
    method create (line 21) | public static function create(): LanguageInflectorFactory
    method createForLanguage (line 26) | public static function createForLanguage(string $language): LanguageIn...

FILE: src/Language.php
  class Language (line 7) | final class Language
    method __construct (line 18) | private function __construct()

FILE: src/LanguageInflectorFactory.php
  type LanguageInflectorFactory (line 9) | interface LanguageInflectorFactory
    method withSingularRules (line 18) | public function withSingularRules(?Ruleset $singularRules, bool $reset...
    method withPluralRules (line 27) | public function withPluralRules(?Ruleset $pluralRules, bool $reset = f...
    method build (line 32) | public function build(): Inflector;

FILE: src/NoopWordInflector.php
  class NoopWordInflector (line 7) | class NoopWordInflector implements WordInflector
    method inflect (line 9) | public function inflect(string $word): string

FILE: src/Rules/English/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 63) | public static function getPlural(): iterable
    method getIrregular (line 93) | public static function getIrregular(): iterable

FILE: src/Rules/English/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/English/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/English/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 32) | public static function getPlural(): iterable
    method getDefault (line 43) | private static function getDefault(): iterable

FILE: src/Rules/Esperanto/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 21) | public static function getPlural(): iterable
    method getIrregular (line 27) | public static function getIrregular(): iterable

FILE: src/Rules/Esperanto/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/Esperanto/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/Esperanto/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 18) | public static function getPlural(): iterable
    method getDefault (line 24) | private static function getDefault(): iterable

FILE: src/Rules/French/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 25) | public static function getPlural(): iterable
    method getIrregular (line 38) | public static function getIrregular(): iterable

FILE: src/Rules/French/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/French/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/French/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 21) | public static function getPlural(): iterable
    method getDefault (line 27) | private static function getDefault(): iterable

FILE: src/Rules/Italian/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 63) | public static function getPlural(): iterable
    method getIrregular (line 97) | public static function getIrregular(): iterable

FILE: src/Rules/Italian/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/Italian/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/Italian/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 18) | public static function getPlural(): iterable
    method getDefault (line 24) | private static function getDefault(): iterable

FILE: src/Rules/NorwegianBokmal/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 22) | public static function getPlural(): iterable
    method getIrregular (line 30) | public static function getIrregular(): iterable

FILE: src/Rules/NorwegianBokmal/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/NorwegianBokmal/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/NorwegianBokmal/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 18) | public static function getPlural(): iterable
    method getDefault (line 24) | private static function getDefault(): iterable

FILE: src/Rules/Pattern.php
  class Pattern (line 9) | final class Pattern
    method __construct (line 17) | public function __construct(string $pattern)
    method getPattern (line 28) | public function getPattern(): string
    method getRegex (line 33) | public function getRegex(): string
    method matches (line 38) | public function matches(string $word): bool

FILE: src/Rules/Patterns.php
  class Patterns (line 11) | class Patterns
    method __construct (line 16) | public function __construct(Pattern ...$patterns)
    method matches (line 25) | public function matches(string $word): bool

FILE: src/Rules/Portuguese/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 36) | public static function getPlural(): iterable
    method getIrregular (line 58) | public static function getIrregular(): iterable

FILE: src/Rules/Portuguese/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/Portuguese/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/Portuguese/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 18) | public static function getPlural(): iterable
    method getDefault (line 24) | private static function getDefault(): iterable

FILE: src/Rules/Ruleset.php
  class Ruleset (line 7) | class Ruleset
    method __construct (line 18) | public function __construct(Transformations $regular, Patterns $uninfl...
    method getRegular (line 25) | public function getRegular(): Transformations
    method getUninflected (line 30) | public function getUninflected(): Patterns
    method getIrregular (line 35) | public function getIrregular(): Substitutions

FILE: src/Rules/Spanish/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 25) | public static function getPlural(): iterable
    method getIrregular (line 39) | public static function getIrregular(): iterable

FILE: src/Rules/Spanish/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/Spanish/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/Spanish/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 18) | public static function getPlural(): iterable
    method getDefault (line 24) | private static function getDefault(): iterable

FILE: src/Rules/Substitution.php
  class Substitution (line 7) | final class Substitution
    method __construct (line 15) | public function __construct(Word $from, Word $to)
    method getFrom (line 21) | public function getFrom(): Word
    method getTo (line 26) | public function getTo(): Word

FILE: src/Rules/Substitutions.php
  class Substitutions (line 13) | class Substitutions implements WordInflector
    method __construct (line 18) | public function __construct(Substitution ...$substitutions)
    method getFlippedSubstitutions (line 25) | public function getFlippedSubstitutions(): Substitutions
    method inflect (line 39) | public function inflect(string $word): string

FILE: src/Rules/Transformation.php
  class Transformation (line 11) | final class Transformation implements WordInflector
    method __construct (line 19) | public function __construct(Pattern $pattern, string $replacement)
    method getPattern (line 25) | public function getPattern(): Pattern
    method getReplacement (line 30) | public function getReplacement(): string
    method inflect (line 35) | public function inflect(string $word): string

FILE: src/Rules/Transformations.php
  class Transformations (line 9) | class Transformations implements WordInflector
    method __construct (line 14) | public function __construct(Transformation ...$transformations)
    method inflect (line 19) | public function inflect(string $word): string

FILE: src/Rules/Turkish/Inflectible.php
  class Inflectible (line 12) | class Inflectible
    method getSingular (line 15) | public static function getSingular(): iterable
    method getPlural (line 21) | public static function getPlural(): iterable
    method getIrregular (line 28) | public static function getIrregular(): iterable

FILE: src/Rules/Turkish/InflectorFactory.php
  class InflectorFactory (line 10) | final class InflectorFactory extends GenericLanguageInflectorFactory
    method getSingularRuleset (line 12) | protected function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 17) | protected function getPluralRuleset(): Ruleset

FILE: src/Rules/Turkish/Rules.php
  class Rules (line 12) | final class Rules
    method getSingularRuleset (line 14) | public static function getSingularRuleset(): Ruleset
    method getPluralRuleset (line 23) | public static function getPluralRuleset(): Ruleset

FILE: src/Rules/Turkish/Uninflected.php
  class Uninflected (line 9) | final class Uninflected
    method getSingular (line 12) | public static function getSingular(): iterable
    method getPlural (line 18) | public static function getPlural(): iterable
    method getDefault (line 24) | private static function getDefault(): iterable

FILE: src/Rules/Word.php
  class Word (line 7) | class Word
    method __construct (line 12) | public function __construct(string $word)
    method getWord (line 17) | public function getWord(): string

FILE: src/RulesetInflector.php
  class RulesetInflector (line 20) | class RulesetInflector implements WordInflector
    method __construct (line 25) | public function __construct(Ruleset $ruleset, Ruleset ...$rulesets)
    method inflect (line 30) | public function inflect(string $word): string

FILE: src/WordInflector.php
  type WordInflector (line 7) | interface WordInflector
    method inflect (line 9) | public function inflect(string $word): string;

FILE: tests/CachedWordInflectorTest.php
  class CachedWordInflectorTest (line 12) | class CachedWordInflectorTest extends TestCase
    method testInflect (line 20) | public function testInflect(): void
    method setUp (line 31) | protected function setUp(): void

FILE: tests/InflectorFactoryTest.php
  class InflectorFactoryTest (line 22) | class InflectorFactoryTest extends TestCase
    method testCreateUsesEnglishByDefault (line 24) | public function testCreateUsesEnglishByDefault(): void
    method testCreateForLanguageWithCustomLanguage (line 34) | #[DataProvider('provideLanguages')]
    method provideLanguages (line 41) | public static function provideLanguages(): Generator
    method testCreateForLanguageThrowsInvalidArgumentExceptionForUnsupportedLanguage (line 52) | public function testCreateForLanguageThrowsInvalidArgumentExceptionFor...

FILE: tests/InflectorFunctionalTest.php
  class InflectorFunctionalTest (line 12) | class InflectorFunctionalTest extends TestCase
    method testCapitalize (line 14) | public function testCapitalize(): void
    method testCapitalizeWithCustomDelimiters (line 22) | public function testCapitalizeWithCustomDelimiters(): void
    method testTableize (line 31) | #[DataProvider('dataStringsTableize')]
    method dataStringsTableize (line 42) | public static function dataStringsTableize(): array
    method testClassify (line 53) | #[DataProvider('dataStringsClassify')]
    method dataStringsClassify (line 64) | public static function dataStringsClassify(): array
    method testCamelize (line 78) | #[DataProvider('dataStringsCamelize')]
    method dataStringsCamelize (line 89) | public static function dataStringsCamelize(): array
    method createInflector (line 101) | private function createInflector(): Inflector

FILE: tests/InflectorTest.php
  class InflectorTest (line 13) | class InflectorTest extends TestCase
    method testTableize (line 24) | public function testTableize(): void
    method testClassify (line 31) | public function testClassify(): void
    method testCamelize (line 38) | public function testCamelize(): void
    method testCapitalize (line 45) | public function testCapitalize(): void
    method testSeemsUtf8 (line 53) | public function testSeemsUtf8(): void
    method testUnaccent (line 60) | public function testUnaccent(): void
    method testUrlize (line 67) | #[DataProvider('dataStringsUrlize')]
    method dataStringsUrlize (line 81) | public static function dataStringsUrlize(): array
    method testPluralize (line 119) | public function testPluralize(): void
    method testSingularize (line 133) | public function testSingularize(): void
    method setUp (line 146) | protected function setUp(): void

FILE: tests/NoopWordInflectorTest.php
  class NoopWordInflectorTest (line 10) | class NoopWordInflectorTest extends TestCase
    method testInflect (line 15) | public function testInflect(): void
    method setUp (line 21) | protected function setUp(): void

FILE: tests/Rules/English/EnglishFunctionalTest.php
  class EnglishFunctionalTest (line 15) | class EnglishFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 18) | public static function dataSampleWords(): array
    method dataSingularsUninflectedWhenSingularized (line 386) | public static function dataSingularsUninflectedWhenSingularized(): array
    method testSingularsWhenSingularizedShouldBeUninflected (line 411) | #[DataProvider('dataSingularsUninflectedWhenSingularized')]
    method dataPluralUninflectedWhenPluralized (line 428) | public static function dataPluralUninflectedWhenPluralized(): array
    method testPluralsWhenPluralizedShouldBeUninflected (line 436) | #[DataProvider('dataPluralUninflectedWhenPluralized')]
    method createInflector (line 448) | protected function createInflector(): Inflector

FILE: tests/Rules/Esperanto/EsperantoFunctionalTest.php
  class EsperantoFunctionalTest (line 12) | class EsperantoFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 31) | protected function createInflector(): Inflector

FILE: tests/Rules/French/FrenchFunctionalTest.php
  class FrenchFunctionalTest (line 12) | class FrenchFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 64) | protected function createInflector(): Inflector

FILE: tests/Rules/Italian/ItalianFunctionalTest.php
  class ItalianFunctionalTest (line 12) | class ItalianFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 128) | protected function createInflector(): Inflector

FILE: tests/Rules/LanguageFunctionalTestCase.php
  class LanguageFunctionalTestCase (line 13) | abstract class LanguageFunctionalTestCase extends TestCase
    method dataSampleWords (line 16) | abstract public static function dataSampleWords(): array;
    method testInflectingSingulars (line 19) | #[DataProvider('dataSampleWords')]
    method testInflectingPlurals (line 30) | #[DataProvider('dataSampleWords')]
    method createInflector (line 40) | abstract protected function createInflector(): Inflector;

FILE: tests/Rules/NorwegianBokmal/NorwegianBokmalFunctionalTest.php
  class NorwegianBokmalFunctionalTest (line 12) | class NorwegianBokmalFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 32) | protected function createInflector(): Inflector

FILE: tests/Rules/PatternTest.php
  class PatternTest (line 10) | class PatternTest extends TestCase
    method testGetPattern (line 15) | public function testGetPattern(): void
    method testGetRegex (line 20) | public function testGetRegex(): void
    method testPatternWithExplicitRegex (line 25) | public function testPatternWithExplicitRegex(): void
    method testMatches (line 32) | public function testMatches(): void
    method setUp (line 37) | protected function setUp(): void

FILE: tests/Rules/PatternsTest.php
  class PatternsTest (line 11) | class PatternsTest extends TestCase
    method testMatches (line 16) | public function testMatches(): void
    method setUp (line 22) | protected function setUp(): void

FILE: tests/Rules/Portuguese/PortugueseFunctionalTest.php
  class PortugueseFunctionalTest (line 12) | class PortugueseFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 50) | protected function createInflector(): Inflector

FILE: tests/Rules/RulesetTest.php
  class RulesetTest (line 17) | class RulesetTest extends TestCase
    method testGetRegular (line 31) | public function testGetRegular(): void
    method testGetUninflected (line 36) | public function testGetUninflected(): void
    method testGetIrregular (line 41) | public function testGetIrregular(): void
    method setUp (line 46) | protected function setUp(): void

FILE: tests/Rules/Spanish/SpanishFunctionalTest.php
  class SpanishFunctionalTest (line 12) | class SpanishFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 57) | protected function createInflector(): Inflector

FILE: tests/Rules/SubstitutionTest.php
  class SubstitutionTest (line 11) | class SubstitutionTest extends TestCase
    method testGetFrom (line 16) | public function testGetFrom(): void
    method testGetTo (line 21) | public function testGetTo(): void
    method setUp (line 26) | protected function setUp(): void

FILE: tests/Rules/SubstitutionsTest.php
  class SubstitutionsTest (line 12) | class SubstitutionsTest extends TestCase
    method testGetFlippedSubstitutions (line 20) | public function testGetFlippedSubstitutions(): void
    method testInflect (line 27) | public function testInflect(): void
    method setUp (line 32) | protected function setUp(): void

FILE: tests/Rules/TransformationTest.php
  class TransformationTest (line 11) | class TransformationTest extends TestCase
    method testGetPattern (line 16) | public function testGetPattern(): void
    method testGetReplacement (line 21) | public function testGetReplacement(): void
    method testInflect (line 26) | public function testInflect(): void
    method setUp (line 31) | protected function setUp(): void

FILE: tests/Rules/TransformationsTest.php
  class TransformationsTest (line 12) | class TransformationsTest extends TestCase
    method testInflect (line 17) | public function testInflect(): void
    method setUp (line 22) | protected function setUp(): void

FILE: tests/Rules/Turkish/TurkishFunctionalTest.php
  class TurkishFunctionalTest (line 12) | class TurkishFunctionalTest extends LanguageFunctionalTestCase
    method dataSampleWords (line 15) | public static function dataSampleWords(): array
    method createInflector (line 30) | protected function createInflector(): Inflector

FILE: tests/Rules/WordTest.php
  class WordTest (line 10) | class WordTest extends TestCase
    method testGetWord (line 15) | public function testGetWord(): void
    method setUp (line 20) | protected function setUp(): void

FILE: tests/RulesetInflectorTest.php
  class RulesetInflectorTest (line 19) | class RulesetInflectorTest extends TestCase
    method testInflect (line 22) | #[DataProvider('rulesetProvider')]
    method rulesetProvider (line 35) | public static function rulesetProvider(): iterable
Condensed preview — 90 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (160K chars).
[
  {
    "path": ".doctrine-project.json",
    "chars": 1194,
    "preview": "{\n    \"active\": true,\n    \"name\": \"Inflector\",\n    \"slug\": \"inflector\",\n    \"docsSlug\": \"doctrine-inflector\",\n    \"versi"
  },
  {
    "path": ".gitattributes",
    "chars": 158,
    "preview": "/tests export-ignore\n.* export-ignore\nphpunit.xml.dist export-ignore\nphpcs.xml.dist export-ignore\ncomposer.lock export-i"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 120,
    "preview": "patreon: phpdoctrine\ntidelift: packagist/doctrine%2Finflector\ncustom: https://www.doctrine-project.org/sponsorship.html\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 143,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  "
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "chars": 242,
    "preview": "\nname: \"Coding Standards\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n  push:\n    branches:\n      - \"*.x\"\n\njobs:\n  "
  },
  {
    "path": ".github/workflows/composer-lint.yml",
    "chars": 299,
    "preview": "name: \"Composer Lint\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n    paths:\n      - \"composer.json\"\n  push:\n    br"
  },
  {
    "path": ".github/workflows/continuous-integration.yml",
    "chars": 920,
    "preview": "\nname: \"Continuous Integration\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n  push:\n    branches:\n      - \"*.x\"\n\njo"
  },
  {
    "path": ".github/workflows/release-on-milestone-closed.yml",
    "chars": 482,
    "preview": "name: \"Automatic Releases\"\n\non:\n  milestone:\n    types:\n      - \"closed\"\n\njobs:\n  release:\n    name: \"Git tag, release &"
  },
  {
    "path": ".github/workflows/static-analysis.yml",
    "chars": 238,
    "preview": "\nname: \"Static Analysis\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n  push:\n    branches:\n      - \"*.x\"\n\njobs:\n  s"
  },
  {
    "path": ".github/workflows/website-schema.yml",
    "chars": 432,
    "preview": "\nname: \"Website config validation\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n    paths:\n      - \".doctrine-projec"
  },
  {
    "path": ".gitignore",
    "chars": 114,
    "preview": "/vendor/\n/composer.lock\n/composer.phar\n/phpunit.xml\n/.phpunit.result.cache\n/phpcs.xml\n/.phpcs-cache\n/phpstan.neon\n"
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "Copyright (c) 2006-2015 Doctrine Project\n\nPermission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "README.md",
    "chars": 525,
    "preview": "# Doctrine Inflector\n\nDoctrine Inflector is a small library that can perform string manipulations\nwith regard to upperca"
  },
  {
    "path": "composer.json",
    "chars": 1790,
    "preview": "{\n    \"name\": \"doctrine/inflector\",\n    \"description\": \"PHP Doctrine Inflector is a small library that can perform strin"
  },
  {
    "path": "docs/en/index.rst",
    "chars": 6573,
    "preview": "Introduction\n============\n\nThe Doctrine Inflector has methods for inflecting text. The features include pluralization,\ns"
  },
  {
    "path": "phpcs.xml.dist",
    "chars": 1117,
    "preview": "<?xml version=\"1.0\"?>\n<ruleset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         name=\"PHP_CodeSniffer\"\n   "
  },
  {
    "path": "phpstan.neon.dist",
    "chars": 227,
    "preview": "includes:\n    - vendor/phpstan/phpstan-phpunit/extension.neon\n    - vendor/phpstan/phpstan-phpunit/rules.neon\n    - vend"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 682,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNa"
  },
  {
    "path": "src/CachedWordInflector.php",
    "chars": 511,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nclass CachedWordInflector implements WordInflector\n{\n   "
  },
  {
    "path": "src/GenericLanguageInflectorFactory.php",
    "chars": 1675,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nuse function arra"
  },
  {
    "path": "src/Inflector.php",
    "chars": 12659,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse RuntimeException;\n\nuse function chr;\nuse function fu"
  },
  {
    "path": "src/InflectorFactory.php",
    "chars": 1688,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\English;\nuse Doctrine\\Infle"
  },
  {
    "path": "src/Language.php",
    "chars": 526,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nfinal class Language\n{\n    public const ENGLISH         "
  },
  {
    "path": "src/LanguageInflectorFactory.php",
    "chars": 805,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\ninterface Languag"
  },
  {
    "path": "src/NoopWordInflector.php",
    "chars": 201,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nclass NoopWordInflector implements WordInflector\n{\n    p"
  },
  {
    "path": "src/Rules/English/Inflectible.php",
    "chars": 11939,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse "
  },
  {
    "path": "src/Rules/English/InflectorFactory.php",
    "chars": 461,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\GenericLanguageInfl"
  },
  {
    "path": "src/Rules/English/Rules.php",
    "chars": 874,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse"
  },
  {
    "path": "src/Rules/English/Uninflected.php",
    "chars": 6543,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfin"
  },
  {
    "path": "src/Rules/Esperanto/Inflectible.php",
    "chars": 738,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nus"
  },
  {
    "path": "src/Rules/Esperanto/InflectorFactory.php",
    "chars": 463,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\GenericLanguageIn"
  },
  {
    "path": "src/Rules/Esperanto/Rules.php",
    "chars": 876,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nu"
  },
  {
    "path": "src/Rules/Esperanto/Uninflected.php",
    "chars": 532,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nf"
  },
  {
    "path": "src/Rules/French/Inflectible.php",
    "chars": 1878,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse D"
  },
  {
    "path": "src/Rules/French/InflectorFactory.php",
    "chars": 460,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\GenericLanguageInfle"
  },
  {
    "path": "src/Rules/French/Rules.php",
    "chars": 873,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse "
  },
  {
    "path": "src/Rules/French/Uninflected.php",
    "chars": 599,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfina"
  },
  {
    "path": "src/Rules/Italian/Inflectible.php",
    "chars": 8378,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse "
  },
  {
    "path": "src/Rules/Italian/InflectorFactory.php",
    "chars": 461,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\GenericLanguageInfl"
  },
  {
    "path": "src/Rules/Italian/Rules.php",
    "chars": 874,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse"
  },
  {
    "path": "src/Rules/Italian/Uninflected.php",
    "chars": 1718,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfin"
  },
  {
    "path": "src/Rules/NorwegianBokmal/Inflectible.php",
    "chars": 943,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Rules\\Patte"
  },
  {
    "path": "src/Rules/NorwegianBokmal/InflectorFactory.php",
    "chars": 469,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\GenericLang"
  },
  {
    "path": "src/Rules/NorwegianBokmal/Rules.php",
    "chars": 882,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Rules\\Patte"
  },
  {
    "path": "src/Rules/NorwegianBokmal/Uninflected.php",
    "chars": 612,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Rules\\Patte"
  },
  {
    "path": "src/Rules/Pattern.php",
    "chars": 788,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse function preg_match;\n\nfinal class Pattern\n{\n  "
  },
  {
    "path": "src/Rules/Patterns.php",
    "chars": 602,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse function array_map;\nuse function implode;\nuse "
  },
  {
    "path": "src/Rules/Portuguese/Inflectible.php",
    "chars": 5710,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nu"
  },
  {
    "path": "src/Rules/Portuguese/InflectorFactory.php",
    "chars": 464,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\GenericLanguageI"
  },
  {
    "path": "src/Rules/Portuguese/Rules.php",
    "chars": 877,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\n"
  },
  {
    "path": "src/Rules/Portuguese/Uninflected.php",
    "chars": 1228,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\n"
  },
  {
    "path": "src/Rules/Ruleset.php",
    "chars": 777,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nclass Ruleset\n{\n    /** @var Transformations */\n  "
  },
  {
    "path": "src/Rules/Spanish/Inflectible.php",
    "chars": 1835,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse "
  },
  {
    "path": "src/Rules/Spanish/InflectorFactory.php",
    "chars": 461,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\GenericLanguageInfl"
  },
  {
    "path": "src/Rules/Spanish/Rules.php",
    "chars": 874,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse"
  },
  {
    "path": "src/Rules/Spanish/Uninflected.php",
    "chars": 615,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfin"
  },
  {
    "path": "src/Rules/Substitution.php",
    "chars": 451,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nfinal class Substitution\n{\n    /** @var Word */\n  "
  },
  {
    "path": "src/Rules/Substitutions.php",
    "chars": 1372,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\WordInflector;\n\nuse functio"
  },
  {
    "path": "src/Rules/Transformation.php",
    "chars": 790,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\WordInflector;\n\nuse functio"
  },
  {
    "path": "src/Rules/Transformations.php",
    "chars": 648,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\WordInflector;\n\nclass Trans"
  },
  {
    "path": "src/Rules/Turkish/Inflectible.php",
    "chars": 997,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse "
  },
  {
    "path": "src/Rules/Turkish/InflectorFactory.php",
    "chars": 461,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\GenericLanguageInfl"
  },
  {
    "path": "src/Rules/Turkish/Rules.php",
    "chars": 874,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse"
  },
  {
    "path": "src/Rules/Turkish/Uninflected.php",
    "chars": 615,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfin"
  },
  {
    "path": "src/Rules/Word.php",
    "chars": 294,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nclass Word\n{\n    /** @var string */\n    private $w"
  },
  {
    "path": "src/RulesetInflector.php",
    "chars": 1355,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nuse function arra"
  },
  {
    "path": "src/WordInflector.php",
    "chars": 143,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\ninterface WordInflector\n{\n    public function inflect(st"
  },
  {
    "path": "tests/CachedWordInflectorTest.php",
    "chars": 998,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\CachedWordInflector;\nuse Do"
  },
  {
    "path": "tests/InflectorFactoryTest.php",
    "chars": 2618,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctr"
  },
  {
    "path": "tests/InflectorFunctionalTest.php",
    "chars": 2814,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inf"
  },
  {
    "path": "tests/InflectorTest.php",
    "chars": 4463,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inf"
  },
  {
    "path": "tests/NoopWordInflectorTest.php",
    "chars": 541,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\NoopWordInflector;\nuse PHPU"
  },
  {
    "path": "tests/Rules/English/EnglishFunctionalTest.php",
    "chars": 15351,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Inflector;\nus"
  },
  {
    "path": "tests/Rules/Esperanto/EsperantoFunctionalTest.php",
    "chars": 956,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Inflector;\n"
  },
  {
    "path": "tests/Rules/French/FrenchFunctionalTest.php",
    "chars": 2055,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Inflector;\nuse"
  },
  {
    "path": "tests/Rules/Italian/ItalianFunctionalTest.php",
    "chars": 4065,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Inflector;\nus"
  },
  {
    "path": "tests/Rules/LanguageFunctionalTestCase.php",
    "chars": 1177,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Inflector;\nuse PHPUni"
  },
  {
    "path": "tests/Rules/NorwegianBokmal/NorwegianBokmalFunctionalTest.php",
    "chars": 970,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Infle"
  },
  {
    "path": "tests/Rules/PatternTest.php",
    "chars": 850,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse PH"
  },
  {
    "path": "tests/Rules/PatternsTest.php",
    "chars": 562,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Do"
  },
  {
    "path": "tests/Rules/Portuguese/PortugueseFunctionalTest.php",
    "chars": 1555,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Inflector;"
  },
  {
    "path": "tests/Rules/RulesetTest.php",
    "chars": 1593,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Do"
  },
  {
    "path": "tests/Rules/Spanish/SpanishFunctionalTest.php",
    "chars": 1850,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Inflector;\nus"
  },
  {
    "path": "tests/Rules/SubstitutionTest.php",
    "chars": 673,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Substitution;\nu"
  },
  {
    "path": "tests/Rules/SubstitutionsTest.php",
    "chars": 960,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Substitution;\nu"
  },
  {
    "path": "tests/Rules/TransformationTest.php",
    "chars": 821,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Do"
  },
  {
    "path": "tests/Rules/TransformationsTest.php",
    "chars": 653,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Do"
  },
  {
    "path": "tests/Rules/Turkish/TurkishFunctionalTest.php",
    "chars": 878,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Inflector;\nus"
  },
  {
    "path": "tests/Rules/WordTest.php",
    "chars": 421,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Word;\nuse PHPUn"
  },
  {
    "path": "tests/RulesetInflectorTest.php",
    "chars": 4559,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine"
  }
]

About this extraction

This page contains the full source code of the doctrine/inflector GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 90 files (145.1 KB), approximately 39.4k tokens, and a symbol index with 283 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!