From 76884ad519fc1732ee39e99a54c91dd4719fe457 Mon Sep 17 00:00:00 2001 From: Matthew J Mucklo Date: Sun, 12 Apr 2026 14:42:38 -0700 Subject: [PATCH 1/2] Modernize PHP baseline: PHP 8.1+, PSR-4, PHPUnit 10 Roadmap item #1. - composer.json: require PHP >= 8.1, require-dev phpunit/phpunit ^10.5, switch autoload from PSR-0 to PSR-4, add PSR-4 autoload-dev mapping for tests. - Relocate src/Inflect/Inflect.php to src/Inflect.php to conform to PSR-4 (namespace Inflect, class Inflect). - Rewrite phpunit.xml.dist against the PHPUnit 10 schema (new element, Composer autoload bootstrap, failOnWarning/failOnNotice). Rename phpunit.xml-dist -> phpunit.xml.dist per PHPUnit 10 conventions. - Port tests to namespaced PHPUnit\Framework\TestCase with strict types, #[DataProvider] attributes, and assertSame. Tests now live in the Inflect\Tests namespace. - Drop autoload.php.dist (obsoleted by Composer PSR-4 autoload + the PHPUnit 10 bootstrap pointing at vendor/autoload.php). - Ignore .phpunit.result.cache and .phpunit.cache/. Note: .travis.yml still references PHP 5.3/5.4 and will fail until the CI modernization (roadmap item #2) replaces it with GitHub Actions. Verified: 109 tests / 109 assertions pass on PHP 8.1 and 8.3. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 4 +- autoload.php.dist | 11 -- composer.json | 34 ++-- phpunit.xml-dist | 40 ----- phpunit.xml.dist | 19 +++ src/{Inflect => }/Inflect.php | 0 tests/InflectTest.php | 296 +++++++++++++++------------------- 7 files changed, 171 insertions(+), 233 deletions(-) delete mode 100644 autoload.php.dist delete mode 100644 phpunit.xml-dist create mode 100644 phpunit.xml.dist rename src/{Inflect => }/Inflect.php (100%) diff --git a/.gitignore b/.gitignore index 17eccdf..a9a6c72 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ composer.lock *~ \#*# .\#* -vendor/* \ No newline at end of file +vendor/* +.phpunit.result.cache +.phpunit.cache/ \ No newline at end of file diff --git a/autoload.php.dist b/autoload.php.dist deleted file mode 100644 index 7b2f839..0000000 --- a/autoload.php.dist +++ /dev/null @@ -1,11 +0,0 @@ -=') && gc_enabled()) { - // Disabling Zend Garbage Collection to prevent segfaults with PHP5.4+ - // https://bugs.php.net/bug.php?id=53976 - gc_disable(); -} - -$loader = require_once __DIR__.'/vendor/autoload.php'; - -return $loader; diff --git a/composer.json b/composer.json index 0b5dac6..1ba40f0 100644 --- a/composer.json +++ b/composer.json @@ -5,25 +5,29 @@ "license": "MIT", "keywords": ["singularize", "pluralize", "inflect", "inflector", "urlify"], "authors": [ - { - "name": "Matthew J. Mucklo", - "email": "mmucklo@gmail.com" - }, - { - "name": "Sho Kuwamoto", - "email": "sho@kuwamoto.org" + { + "name": "Matthew J. Mucklo", + "email": "mmucklo@gmail.com" + }, + { + "name": "Sho Kuwamoto", + "email": "sho@kuwamoto.org" } ], - "require-dev": { - "phpunit/phpunit": "3.7.*" - }, "require": { - "php": ">=5.3.17" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" }, "autoload": { - "psr-0": { - "Inflect": "src/" - } + "psr-4": { + "Inflect\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Inflect\\Tests\\": "tests/" + } } } - diff --git a/phpunit.xml-dist b/phpunit.xml-dist deleted file mode 100644 index 1300db6..0000000 --- a/phpunit.xml-dist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - ./tests/ - - - - - - benchmark - - - - - - ./src/ - - ./tests/ - - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..5e02e1b --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Inflect/Inflect.php b/src/Inflect.php similarity index 100% rename from src/Inflect/Inflect.php rename to src/Inflect.php diff --git a/tests/InflectTest.php b/tests/InflectTest.php index aace58b..0869593 100644 --- a/tests/InflectTest.php +++ b/tests/InflectTest.php @@ -1,181 +1,145 @@ 'ox', - 'cats' => 'cat', - 'oxen' => 'ox', - 'cats' => 'cat', - 'purses' => 'purse', - 'analyses' => 'analysis', - 'houses' => 'house', - 'sheep' => 'sheep', - 'buses' => 'bus', - 'uses' => 'use', - 'databases' => 'database', - 'quizzes' => 'quiz', - 'matrices' => 'matrix', - 'vertices' => 'vertex', - 'alias' => 'alias', - 'aliases' => 'alias', - 'octopi' => 'octopus', - 'axes' => 'axis', - 'axis' => 'axis', - 'crises' => 'crisis', - 'crisis' => 'crisis', - 'shoes' => 'shoe', - 'foes' => 'foe', - 'pianos' => 'piano', - 'wierdos' => 'wierdo', - 'toes' => 'toe', - 'banjoes' => 'banjo', - 'vetoes' => 'veto', - 'cows' => 'cow', - 'businesses' => 'business', - 'business' => 'business', - 'wellness' => 'wellness', - ); - - foreach ($inflections as $key => $value) - { - print "Testing $key singularizes to: $value\n"; - $this->assertEquals($value, Inflect::singularize($key)); - } - - print "\n"; + $this->assertSame($expected, Inflect::singularize($input)); } - public function testPluralize() + #[DataProvider('pluralizeProvider')] + public function testPluralize(string $input, string $expected): void { - $inflections = array('oxen' => 'ox', - 'cats' => 'cat', - 'cats' => 'cat', - 'purses' => 'purse', - 'analyses' => 'analysis', - 'houses' => 'house', - 'sheep' => 'sheep', - 'buses' => 'bus', - 'axes' => 'axis', - 'uses' => 'use', - 'databases' => 'database', - 'quizzes' => 'quiz', - 'matrices' => 'matrix', - 'vertices' => 'vertex', - 'aliases' => 'aliases', - 'aliases' => 'alias', - 'octopi' => 'octopus', - 'axes' => 'axis', - 'crises' => 'crisis', - 'crises' => 'crises', - 'shoes' => 'shoe', - 'foes' => 'foe', - 'pianos' => 'piano', - 'wierdos' => 'wierdo', - 'toes' => 'toe', - 'banjos' => 'banjo', - 'vetoes' => 'veto', - 'cows' => 'cow', - ); - foreach ($inflections as $key => $value) - { - print "Testing $value pluralizes to: $key\n"; - $this->assertEquals($key, Inflect::pluralize($value)); - } - print "\n"; + $this->assertSame($expected, Inflect::pluralize($input)); } - // Uses a list of [input, expected] pairs to avoid duplicate-key dedup. - public function testNewIrregularsAndGuards() + public static function singularizeProvider(): array { - $singularizeCases = array( - // new irregulars: plural -> singular - array('data', 'datum'), - array('criteria', 'criterion'), - array('phenomena', 'phenomenon'), - array('cacti', 'cactus'), - array('nuclei', 'nucleus'), - array('syllabi', 'syllabus'), - array('curricula', 'curriculum'), - array('media', 'medium'), - array('bacteria', 'bacterium'), - // already-singular guard: singular -> singular - array('datum', 'datum'), - array('criterion', 'criterion'), - array('phenomenon', 'phenomenon'), - array('cactus', 'cactus'), - array('nucleus', 'nucleus'), - array('syllabus', 'syllabus'), - array('curriculum', 'curriculum'), - array('medium', 'medium'), - array('bacterium', 'bacterium'), - // new uncountables - array('news', 'news'), - array('aircraft', 'aircraft'), - array('software', 'software'), - array('hardware', 'hardware'), - array('luggage', 'luggage'), - array('advice', 'advice'), - array('traffic', 'traffic'), - array('furniture', 'furniture'), - array('metadata', 'metadata'), - array('multimedia', 'multimedia'), - // case preservation on irregulars - array('Children', 'Child'), - array('Men', 'Man'), - array('People', 'Person'), - array('Teeth', 'Tooth'), - ); - - foreach ($singularizeCases as $case) - { - list($input, $expected) = $case; - print "Testing $input singularizes to: $expected\n"; - $this->assertEquals($expected, Inflect::singularize($input)); - } - - $pluralizeCases = array( - // new irregulars: singular -> plural - array('datum', 'data'), - array('criterion', 'criteria'), - array('phenomenon', 'phenomena'), - array('cactus', 'cacti'), - array('nucleus', 'nuclei'), - array('syllabus', 'syllabi'), - array('curriculum', 'curricula'), - array('medium', 'media'), - array('bacterium', 'bacteria'), - // already-plural guard - array('data', 'data'), - array('criteria', 'criteria'), - array('phenomena', 'phenomena'), - array('people', 'people'), - array('men', 'men'), - array('children', 'children'), - // uncountables - array('news', 'news'), - array('News', 'News'), - array('aircraft', 'aircraft'), - array('metadata', 'metadata'), - // case preservation on irregulars - array('Man', 'Men'), - array('Child', 'Children'), - array('Person', 'People'), - array('Tooth', 'Teeth'), - ); + return [ + ['ox', 'ox'], + ['oxen', 'ox'], + ['cats', 'cat'], + ['purses', 'purse'], + ['analyses', 'analysis'], + ['houses', 'house'], + ['sheep', 'sheep'], + ['buses', 'bus'], + ['uses', 'use'], + ['databases', 'database'], + ['quizzes', 'quiz'], + ['matrices', 'matrix'], + ['vertices', 'vertex'], + ['alias', 'alias'], + ['aliases', 'alias'], + ['octopi', 'octopus'], + ['axes', 'axis'], + ['axis', 'axis'], + ['crises', 'crisis'], + ['crisis', 'crisis'], + ['shoes', 'shoe'], + ['foes', 'foe'], + ['pianos', 'piano'], + ['wierdos', 'wierdo'], + ['toes', 'toe'], + ['banjoes', 'banjo'], + ['vetoes', 'veto'], + ['cows', 'cow'], + ['businesses', 'business'], + ['business', 'business'], + ['wellness', 'wellness'], + ['data', 'datum'], + ['criteria', 'criterion'], + ['phenomena', 'phenomenon'], + ['cacti', 'cactus'], + ['nuclei', 'nucleus'], + ['syllabi', 'syllabus'], + ['curricula', 'curriculum'], + ['media', 'medium'], + ['bacteria', 'bacterium'], + ['datum', 'datum'], + ['criterion', 'criterion'], + ['phenomenon', 'phenomenon'], + ['cactus', 'cactus'], + ['nucleus', 'nucleus'], + ['syllabus', 'syllabus'], + ['curriculum', 'curriculum'], + ['medium', 'medium'], + ['bacterium', 'bacterium'], + ['news', 'news'], + ['aircraft', 'aircraft'], + ['software', 'software'], + ['hardware', 'hardware'], + ['luggage', 'luggage'], + ['advice', 'advice'], + ['traffic', 'traffic'], + ['furniture', 'furniture'], + ['metadata', 'metadata'], + ['multimedia', 'multimedia'], + ['Children', 'Child'], + ['Men', 'Man'], + ['People', 'Person'], + ['Teeth', 'Tooth'], + ]; + } - foreach ($pluralizeCases as $case) - { - list($input, $expected) = $case; - print "Testing $input pluralizes to: $expected\n"; - $this->assertEquals($expected, Inflect::pluralize($input)); - } - print "\n"; + public static function pluralizeProvider(): array + { + return [ + ['ox', 'oxen'], + ['cat', 'cats'], + ['purse', 'purses'], + ['analysis', 'analyses'], + ['house', 'houses'], + ['sheep', 'sheep'], + ['bus', 'buses'], + ['axis', 'axes'], + ['use', 'uses'], + ['database', 'databases'], + ['quiz', 'quizzes'], + ['matrix', 'matrices'], + ['vertex', 'vertices'], + ['alias', 'aliases'], + ['octopus', 'octopi'], + ['crisis', 'crises'], + ['shoe', 'shoes'], + ['foe', 'foes'], + ['piano', 'pianos'], + ['wierdo', 'wierdos'], + ['toe', 'toes'], + ['veto', 'vetoes'], + ['cow', 'cows'], + ['datum', 'data'], + ['criterion', 'criteria'], + ['phenomenon', 'phenomena'], + ['cactus', 'cacti'], + ['nucleus', 'nuclei'], + ['syllabus', 'syllabi'], + ['curriculum', 'curricula'], + ['medium', 'media'], + ['bacterium', 'bacteria'], + ['data', 'data'], + ['criteria', 'criteria'], + ['phenomena', 'phenomena'], + ['people', 'people'], + ['men', 'men'], + ['children', 'children'], + ['news', 'news'], + ['News', 'News'], + ['aircraft', 'aircraft'], + ['metadata', 'metadata'], + ['Man', 'Men'], + ['Child', 'Children'], + ['Person', 'People'], + ['Tooth', 'Teeth'], + ]; } -} \ No newline at end of file +} From 0236fff3fd2f5651f4bb79b32e2d3749d0074adc Mon Sep 17 00:00:00 2001 From: Matthew J Mucklo Date: Sun, 12 Apr 2026 15:19:16 -0700 Subject: [PATCH 2/2] Replace Travis with GitHub Actions, add Codecov + Scrutinizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roadmap item #2. - Delete .travis.yml (pinned to PHP 5.3/5.4 and the pre-PSR-4 test layout). - Add .github/workflows/ci.yml running a matrix of PHP 8.1/8.2/8.3/8.4 on every push to master and every pull request. Runs composer validate, installs dependencies with Composer cache, and executes PHPUnit with Clover coverage. - Upload coverage to Codecov (PHP 8.3 matrix leg) via codecov-action@v4. - Upload coverage to Scrutinizer (PHP 8.3 matrix leg) via ocular.phar; marked continue-on-error so a Scrutinizer outage does not fail the build. - Add .scrutinizer.yml with php-scrutinizer-run + phpcs-run analysis and external_code_coverage wired up. - Add CI, Codecov, and Scrutinizer badges to README.md. Stacked on top of the roadmap #1 PHP baseline modernization — this PR targets the modernize-php-baseline branch and should merge after #12. Codecov and Scrutinizer need repo registration; Codecov also needs CODECOV_TOKEN as a repo secret (optional for public repos but recommended by the action). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 63 ++++++++++++++++++++++++++++++++++++++++ .scrutinizer.yml | 27 +++++++++++++++++ .travis.yml | 10 ------- README.md | 4 +++ 4 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .scrutinizer.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5b21b64 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +jobs: + test: + name: PHP ${{ matrix.php }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.1', '8.2', '8.3', '8.4'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + tools: composer:v2 + + - name: Get Composer cache dir + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer packages + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ runner.os }}-php-${{ matrix.php }}- + + - name: Validate composer.json + run: composer validate --strict + + - name: Install dependencies + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run PHPUnit + run: vendor/bin/phpunit --coverage-clover=coverage.clover + + - name: Upload coverage to Codecov + if: matrix.php == '8.3' + uses: codecov/codecov-action@v4 + with: + files: coverage.clover + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload coverage to Scrutinizer + if: matrix.php == '8.3' + continue-on-error: true + run: | + wget -q https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..c56ac99 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,27 @@ +build: + environment: + php: 8.3 + + nodes: + analysis: + tests: + override: + - php-scrutinizer-run + - phpcs-run + +checks: + php: + code_rating: true + duplication: true + +tools: + external_code_coverage: + timeout: 900 + runs: 1 + +filter: + paths: + - src/* + excluded_paths: + - tests/* + - vendor/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 53801d0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - -before_script: - - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source --dev install - -script: vendor/phpunit/phpunit/composer/bin/phpunit --configuration phpunit.xml-dist \ No newline at end of file diff --git a/README.md b/README.md index b556a83..5b44e29 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ Inflect ======= +[![CI](https://github.com/mmucklo/inflect/actions/workflows/ci.yml/badge.svg)](https://github.com/mmucklo/inflect/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/mmucklo/inflect/branch/master/graph/badge.svg)](https://codecov.io/gh/mmucklo/inflect) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mmucklo/inflect/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mmucklo/inflect/?branch=master) + Inflect is an Inflector for PHP Installation: