Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ["8.3", "8.4"]
php-versions: ["8.3", "8.4", "8.5"]

steps:
# This step checks out a copy of your repository.
Expand Down Expand Up @@ -51,9 +51,15 @@ jobs:
run: composer validate --strict

# This step installs the project dependencies.
# PHP 8.5 requires --ignore-platform-req=php for packages not yet updated.
- name: Install dependencies
id: composer-install
run: composer install --prefer-dist --no-progress
run: |
if [ "${{ matrix.php-versions }}" = "8.5" ]; then
composer install --prefer-dist --no-progress --ignore-platform-req=php
else
composer install --prefer-dist --no-progress
fi

# This step sets up Go environment for the job.
- name: Set up Go
Expand Down Expand Up @@ -111,22 +117,10 @@ jobs:
if: steps.infection.outcome == 'success'
run: composer test:phpstan

# This step runs static analysis with Phan.
- name: Run static analysis with phan
id: phan
if: steps.phpstan.outcome == 'success'
run: composer test:phan

# This step runs static analysis with Psalm.
- name: Run static analysis with psalm
id: psalm
if: steps.phan.outcome == 'success'
run: composer test:psalm

# This step runs Rector for code quality.
- name: Run rector for code quality
id: rector
if: steps.psalm.outcome == 'success'
if: steps.phpstan.outcome == 'success'
run: composer test:rector

release:
Expand Down
11 changes: 2 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,15 @@
"pestphp/pest": "^4.1",
"pestphp/pest-plugin-drift": "^4.0",
"pestphp/pest-plugin-type-coverage": "^4.0",
"phan/phan": ">=5.5.1",
"php-parallel-lint/php-parallel-lint": ">=1.4.0",
"phpmd/phpmd": ">=2.15",
"phpstan/extension-installer": ">=1.4.3",
"phpstan/phpstan": ">=2.1.22",
"phpstan/phpstan-strict-rules": ">=2.0.6",
"psalm/plugin-phpunit": ">=0.19.3",
"rector/rector": ">=2.1.4",
"rector/type-perfect": "^2.1",
"roave/security-advisories": "dev-latest",
"tomasvotruba/type-coverage": "^2.0",
"vimeo/psalm": ">=6.7"
"tomasvotruba/type-coverage": "^2.0"
},
"scripts-descriptions": {
"test:code-style": "Check code for stylistic consistency using Laravel Pint",
Expand Down Expand Up @@ -96,8 +93,6 @@
"@test:pest",
"@test:infection",
"@test:phpstan",
"@test:phan",
"@test:psalm",
"@test:rector"
],
"test:code-style": "pint --test",
Expand All @@ -114,9 +109,7 @@
"fix:code-style": "pint",
"fix:rector": "rector",
"analyse:all": [
"@test:phpstan",
"@test:psalm",
"@test:phan"
"@test:phpstan"
]
}
}
13 changes: 0 additions & 13 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,6 @@ services:
extends: tests
command: composer test:phpstan

test-psalm:
extends: tests
command: composer test:psalm

test-phan:
extends: tests
environment:
- PHAN_DISABLE_XDEBUG_WARN=1
- PHAN_ALLOW_XDEBUG=1
command: composer test:phan

test-phpmd:
extends: tests
command: composer test:phpmd
Expand Down Expand Up @@ -62,8 +51,6 @@ services:
composer test:lint &&
composer test:code-style &&
composer test:phpstan &&
composer test:psalm &&
composer test:phan &&
composer test:phpmd &&
composer test:pest &&
composer test:rector &&
Expand Down
62 changes: 29 additions & 33 deletions docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,39 @@ Benchmarks and optimisation details for the StringManipulation library.

## Overview

The StringManipulation library has undergone extensive performance tuning, resulting in **2-5x speed improvements** through O(n) optimisation algorithms. All core methods are designed with predictable, linear performance scaling.
The StringManipulation library has undergone extensive performance tuning, resulting in **2-5x speed improvements** through O(n) optimisation algorithms and pre-computed compile-time constants. All core methods are designed with predictable, linear performance scaling.

---

## Benchmarks

| Method | Operations/Second | Complexity | Optimisation |
|:------------------|:------------------|:-----------|:--------------------------------|
| `removeAccents()` | **~450,000** | O(n) | Hash table lookups with strtr() |
| `searchWords()` | **~195,000** | O(n) | Single-pass combined mapping |
| `nameFix()` | **~130,000** | O(n) | Consolidated regex operations |
| Method | Operations/Second | Complexity | Optimisation |
|:------------------|:------------------|:-----------|:------------------------------------|
| `removeAccents()` | **~750,000** | O(n) | Pre-computed constant with strtr() |
| `searchWords()` | **~400,000** | O(n) | Pre-computed constant with strtr() |
| `nameFix()` | **~180,000** | O(n) | Consolidated regex operations |

*Benchmarks measured in Docker with PHP 8.3. Actual performance varies based on hardware, string length, and character complexity.*

---

## Optimisation Techniques

### Hash Table Lookups
### Pre-Computed Constants

The `removeAccents()` method uses PHP's `strtr()` function with a pre-built character mapping array. This provides O(1) lookup time for each character, resulting in overall O(n) complexity.
The `removeAccents()` and `searchWords()` methods use PHP's `strtr()` function with pre-computed compile-time constants. This eliminates runtime array construction overhead and provides O(1) lookup time for each character, resulting in overall O(n) complexity.

```php
// Internal implementation concept
private static ?array $accentsReplacement = null;
// Pre-computed at compile time - no runtime overhead
private const array ACCENT_MAPPING = [
'À' => 'A', 'Á' => 'A', 'Â' => 'A', // ... full mapping
'à' => 'a', 'á' => 'a', 'â' => 'a', // ... preserves case
];

public static function removeAccents(string $str): string
{
// Lazy initialisation - build mapping once
if (self::$accentsReplacement === null) {
self::$accentsReplacement = array_combine(
self::REMOVE_ACCENTS_FROM,
self::REMOVE_ACCENTS_TO
);
}

// O(n) string traversal with O(1) lookups
return strtr($str, self::$accentsReplacement);
// Single strtr() call with O(1) lookups per character
return strtr($str, self::ACCENT_MAPPING);
}
```

Expand All @@ -71,16 +66,18 @@ The `searchWords()` method performs all transformations in a single pass through

This reduces memory allocations and cache misses compared to chaining multiple operations.

### Static Caching
### Compile-Time Constants

Character mapping tables are stored as static properties and initialised lazily. Subsequent calls reuse the cached data:
Character mapping tables are defined as typed constants (`private const array`), computed at compile time by PHP. This provides:

```php
// First call: builds and caches mapping
$result1 = StringManipulation::removeAccents('Cafe');
- **Zero first-call overhead**: No lazy initialisation required
- **Guaranteed consistency**: Constants cannot be modified at runtime
- **Optimal memory usage**: PHP optimises constant storage

// Subsequent calls: uses cached mapping
$result2 = StringManipulation::removeAccents('Munchen');
```php
// Every call uses the same pre-computed constant
$result1 = StringManipulation::removeAccents('Cafe'); // Fast
$result2 = StringManipulation::removeAccents('Munchen'); // Equally fast
```

### Consolidated Regex Operations
Expand Down Expand Up @@ -193,14 +190,13 @@ $search = StringManipulation::searchWords($name);
$search = StringManipulation::searchWords($name);
```

### Pre-warm Cache for Critical Paths
### No Warm-up Required

If first-call latency matters, pre-warm the caches during application bootstrap:
Unlike libraries that use lazy initialisation, StringManipulation uses compile-time constants. There is no first-call penalty, so no warm-up is needed:

```php
// In bootstrap.php or service provider
StringManipulation::removeAccents('warmup');
StringManipulation::searchWords('warmup');
// First call is just as fast as subsequent calls
$result = StringManipulation::removeAccents($userInput);
```

---
Expand All @@ -211,7 +207,7 @@ The library outperforms common alternatives:

| Library/Approach | removeAccents equivalent | Notes |
|:-----------------|:-------------------------|:------|
| StringManipulation | ~450,000 ops/sec | Optimised strtr() |
| StringManipulation | ~750,000 ops/sec | Pre-computed constant with strtr() |
| Manual preg_replace | ~150,000 ops/sec | Multiple regex passes |
| iconv transliteration | ~200,000 ops/sec | System-dependent |
| Multiple str_replace | ~100,000 ops/sec | Linear per pattern |
Expand Down
Loading
Loading