Thank you for your interest in contributing to php-cuid2! This document provides guidelines and instructions for contributing to this project.
- Code of Conduct
- Getting Started
- Development Setup
- Development Workflow
- Testing
- Code Style
- Commit Messages
- Submitting Changes
- Project Architecture
This project follows standard open source community guidelines. Please be respectful and constructive in all interactions.
- PHP 8.2 or higher
- Composer
- Git
- Recommended: GMP extension for better performance
- Optional: Xdebug for code coverage reports
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/php-cuid2.git cd php-cuid2 - Add the upstream repository:
git remote add upstream https://github.com/visus/php-cuid2.git
composer installThis will install:
- Development dependencies (PHPUnit, PHPStan, PHP_CodeSniffer, etc.)
- Git hooks via CaptainHook
- Project dependencies
The project uses CaptainHook to automatically enforce quality standards:
Pre-commit hooks:
- Validates
composer.jsonwhen modified - Runs
composer normalize(dry-run) for composer.json changes - Runs syntax checking on staged PHP files (
composer dev:lint:syntax) - Runs code style checking on staged PHP files (
composer dev:lint:style)
Commit-msg hook:
- Validates conventional commit message format
Post-merge/Post-checkout hooks:
- Auto-runs
composer installwhen composer files change
Hooks are installed automatically during composer install.
-
Create a new branch for your changes:
git checkout -b feature/my-new-feature
-
Make your changes following the Code Style guidelines
-
Add tests for your changes (see Testing)
-
Run the full test suite:
composer dev:test:unit
-
Run static analysis:
composer dev:analyze:phpstan
-
Check code style:
composer dev:lint:style
-
Run all checks at once:
composer dev:test
This runs linting, benchmarks, static analysis, and unit tests.
-
Commit your changes (see Commit Messages)
-
Push to your fork and submit a pull request
# Run unit tests (no coverage)
composer dev:test:unit
# Run tests with HTML coverage report (requires Xdebug)
composer dev:test:coverage:html
# View report at: build/coverage/html/index.html
# Run tests with coverage for CI (requires Xdebug)
composer dev:test:coverage:ci
# Run full test suite (linting, benchmarks, analysis, unit tests)
composer dev:testYou can also run PHPUnit directly:
# Run all tests
vendor/bin/phpunit
# Run tests with coverage
XDEBUG_MODE=coverage vendor/bin/phpunit
# Run specific test class
vendor/bin/phpunit tests/Cuid2Test.php
# Run specific test method
vendor/bin/phpunit --filter testMethodName- All code changes require corresponding tests
- Maintain 100% code coverage (excluding
src/compat.php) - Tests are located in the
tests/directory - Use data providers for parameterized tests
- Test both success and failure scenarios
- Test edge cases and boundary conditions
Example test structure:
<?php
declare(strict_types=1);
namespace Visus\Cuid2\Test;
use PHPUnit\Framework\TestCase;
class MyFeatureTest extends TestCase
{
public function testMyFeature(): void
{
// Arrange
$input = 'test';
// Act
$result = myFunction($input);
// Assert
$this->assertEquals('expected', $result);
}
}- PSR-12: All code must follow PSR-12 coding standard
- Strict Types: Enable strict types in all PHP files:
declare(strict_types=1) - Type Safety: All parameters and return types must be explicitly declared
- PHPDoc: Use PHPDoc blocks for complex array types and class/method documentation
# Check code style compliance
composer dev:lint:style
# Auto-fix code style issues
composer dev:lint:fix
# Check syntax errors
composer dev:lint:syntax
# Run all linting checks (syntax + style)
composer dev:lintThe project uses PHPStan at maximum level:
composer dev:analyze:phpstan
# Or run all analysis checks
composer dev:analyzeAll code must pass PHPStan analysis with no errors.
When modifying composer.json:
# Validate composer.json
composer validate
# Normalize composer.json
composer normalizeTo ensure performance remains optimal:
composer dev:benchmarkThis runs performance benchmarks located in tests/benchmark/.
This project follows the Conventional Commits specification.
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
feat: A new featurefix: A bug fixdocs: Documentation only changesstyle: Code style changes (formatting, missing semi colons, etc.)refactor: Code change that neither fixes a bug nor adds a featureperf: Performance improvementtest: Adding missing tests or correcting existing testschore: Changes to build process or auxiliary toolsci: Changes to CI configuration files and scripts
# Feature
git commit -m "feat(cuid2): add support for custom random generator"
# Bug fix
git commit -m "fix(counter): prevent integer overflow in counter increment"
# Documentation
git commit -m "docs: update installation instructions in README"
# Refactoring
git commit -m "refactor(utils): optimize base conversion algorithm"
# Tests
git commit -m "test(fingerprint): add test for environment variable handling"
# Performance
git commit -m "perf(utils): improve hexToBase36 conversion speed"-
Ensure your code passes all tests, static analysis, and code style checks:
composer dev:test
-
Update documentation if needed (README.md, CLAUDE.md, etc.)
-
Create a pull request with a clear title and description
-
Reference any related issues in the PR description (e.g., "Fixes #123")
-
Wait for review and address any feedback
Before submitting your PR, verify:
- Tests added/updated and passing (
composer dev:test:unit) - Code coverage maintained at 100% (
composer dev:test:coverage:html) - PHPStan analysis passes (
composer dev:analyze:phpstan) - Code style follows PSR-12 (
composer dev:lint:style) - Syntax is valid (
composer dev:lint:syntax) - Benchmarks run successfully (
composer dev:benchmark) - All checks pass (
composer dev:test) - Commit messages follow Conventional Commits
- Documentation updated if needed
- No breaking changes (or clearly documented)
Understanding the project structure will help you contribute effectively:
Cuid2 (src/Cuid2.php)
- Main class that generates CUID2 identifiers
- Immutable once constructed
- Implements JsonSerializable for JSON encoding
Counter (src/Counter.php)
- Singleton maintaining monotonically increasing counter
- Prevents collisions for rapid CUID generation
- Thread-safe through singleton pattern
- Uses bias-free random initialization within range of 476782367
Fingerprint (src/Fingerprint.php)
- Singleton creating unique machine/process fingerprint
- Combines hostname, process ID, environment variables, and random data
- Ensures uniqueness across different machines/processes
- Computed once per process lifecycle
- Uses SHA3-512 for fingerprint generation
InvalidOperationException (src/InvalidOperationException.php)
- Custom exception class
- Thrown when SHA3-512 hashing algorithm is unavailable
- Used to signal invalid operations based on system state
Utils (src/Utils.php)
- Utility class for base conversion
- Provides
hexToBase36()method for arbitrary precision base conversion - Pure PHP implementation without external dependencies
- Uses intermediate large base (100 million) for efficient arithmetic operations
- Fallback when GMP extension unavailable
- Final utility class with private constructor (cannot be instantiated)
compat.php (src/compat.php)
- Compatibility polyfills for missing system functions
- Polyfills
getmypid()- returns random value if function unavailable - Polyfills
gethostname()- returns random string if function unavailable - Excluded from code coverage requirements
- Auto-loaded via composer.json files configuration
- Initialization: Random lowercase letter prefix (a-z)
- Timestamp: Current time in milliseconds
- Counter: Monotonically increasing value from singleton
- Fingerprint: Machine/process identification from singleton
- Random: Cryptographically secure random bytes
- Hash: All components hashed with SHA3-512
- Convert: Base16 hash converted to base36
- Truncate: Result trimmed to requested length (minus prefix)
The library uses two strategies for base16 to base36 conversion:
- Preferred: GMP extension (
gmp_initandgmp_strval) - significantly faster - Fallback:
Utils::hexToBase36()- pure PHP implementation used when GMP not available- Uses arbitrary precision arithmetic with intermediate large base (100 million)
- No external dependencies required
- Converts hex to large base representation, then to base36
- Singleton Pattern: Counter and Fingerprint persist across generations within the same process
- Immutability: Cuid2 instances cannot be modified after creation
- SHA3-512 Required: Library checks for algorithm availability at runtime
- GMP Recommended: Significantly faster base conversion when available
- Length Constraints: Valid lengths are 4-32 characters (enforced by OutOfRangeException)
- Format: CUIDs always start with lowercase letter followed by base36 characters (0-9, a-z)
The Cuid2::isValid() method performs format validation only:
- Checks length (4-32 characters)
- Verifies pattern (starts with lowercase letter, followed by base36 chars)
- Does NOT guarantee the string was generated by this library
- Optionally accepts expected length parameter for strict validation
Quick reference for common development tasks:
# Testing
composer dev:test # Run all tests and checks
composer dev:test:unit # Run unit tests only
composer dev:test:coverage:html # Generate HTML coverage report
# Code Quality
composer dev:lint # Run syntax + style checks
composer dev:lint:syntax # Check PHP syntax
composer dev:lint:style # Check code style (PSR-12)
composer dev:lint:fix # Auto-fix code style issues
composer dev:analyze # Run static analysis
composer dev:analyze:phpstan # Run PHPStan analysis
# Performance
composer dev:benchmark # Run performance benchmarks
# Composer
composer validate # Validate composer.json
composer normalize # Normalize composer.json
# Cleanup
composer dev:build:clean # Clean build artifactsIf you have questions about contributing, feel free to:
- Open an issue for discussion
- Ask in your pull request
- Review existing issues and PRs for similar questions
Thank you for contributing to php-cuid2!