diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5a929b9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,48 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# PHP files +[*.php] +indent_style = space +indent_size = 4 + +# YAML files +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +# JSON files +[*.json] +indent_style = space +indent_size = 2 + +# Markdown files +[*.md] +trim_trailing_whitespace = false + +# XML files +[*.xml] +indent_style = space +indent_size = 2 + +# Makefile +[Makefile] +indent_style = tab + +# Batch files +[*.bat] +end_of_line = crlf + +# Git files +[.git*] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..03e83f0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,41 @@ +# Exclude files from distribution archives +/.github export-ignore +/tests export-ignore +/examples export-ignore +/build export-ignore +/docs export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.php export-ignore +/phpunit.xml.dist export-ignore +/phpstan.neon.dist export-ignore +/CHANGELOG.md export-ignore +/CONTRIBUTING.md export-ignore +/Makefile export-ignore +/.editorconfig export-ignore + +# Ensure all files use LF line endings +* text=auto eol=lf + +# Explicitly declare text files +*.php text +*.json text +*.md text +*.xml text +*.yml text +*.yaml text +*.txt text +*.sh text +*.sql text + +# Declare files that will always have CRLF line endings on checkout +*.bat text eol=crlf + +# Denote all files that are binary and should not be modified +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.phar binary \ No newline at end of file diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..9ef867f --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,46 @@ +name: Static Analysis + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + coverage: none + + - name: Install composer dependencies + run: composer update --prefer-stable --prefer-dist --no-interaction + + - name: Run PHPStan + run: composer analyse + + php-cs-fixer: + name: PHP CS Fixer + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + coverage: none + + - name: Install composer dependencies + run: composer update --prefer-stable --prefer-dist --no-interaction + + - name: Run PHP CS Fixer + run: composer cs-check \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..8e403cc --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +name: Tests + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest] + php: [8.1, 8.2, 8.3] + dependency-version: [prefer-lowest, prefer-stable] + + name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick + coverage: xdebug + + - name: Install dependencies + run: | + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + + - name: Execute tests + run: composer test + + - name: Upload coverage to Codecov + if: matrix.php == '8.2' && matrix.dependency-version == 'prefer-stable' + uses: codecov/codecov-action@v3 + with: + files: ./build/logs/clover.xml + verbose: true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdd2461 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Composer +/vendor/ +composer.lock + +# PHPUnit +/build/ +/coverage/ +.phpunit.result.cache +phpunit.xml + +# PHP CS Fixer +.php-cs-fixer.cache +.php_cs.cache + +# PHPStan +phpstan.neon + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# OS +Thumbs.db + +# Environment +.env +.env.local +.env.*.local + +# Logs +*.log + +# Cache +.cache/ + +# Test artifacts +/tests/tmp/ +/tests/fixtures/tmp/ + +# Documentation build +/docs/_build/ + +# Node (if using any JS tools) +node_modules/ +npm-debug.log +yarn-error.log \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..b7781c4 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,90 @@ +in(__DIR__) + ->exclude('vendor') + ->exclude('build') + ->exclude('storage') + ->exclude('bootstrap/cache') + ->notPath('*.blade.php') + ->notName('*.md') + ->notName('*.xml') + ->notName('*.yml') + ->notName('_ide_helper.php') + ->notName('_ide_helper_models.php') + ->notName('.phpstorm.meta.php'); + +$config = new PhpCsFixer\Config(); + +return $config + ->setRules([ + '@PSR12' => true, + '@PHP81Migration' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'no_unused_imports' => true, + 'single_line_after_imports' => true, + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'declare_strict_types' => true, + 'single_blank_line_at_eof' => true, + 'elseif' => true, + 'function_declaration' => true, + 'indentation_type' => true, + 'line_ending' => true, + 'lowercase_keywords' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + ], + 'no_closing_tag' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'encoding' => true, + 'full_opening_tag' => true, + 'binary_operator_spaces' => [ + 'default' => 'single_space', + ], + 'blank_line_before_statement' => [ + 'statements' => ['return'], + ], + 'cast_spaces' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'method' => 'one', + ], + ], + 'concat_space' => [ + 'spacing' => 'one', + ], + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_whitespace_in_blank_line' => true, + 'return_type_declaration' => true, + 'short_scalar_cast' => true, + 'single_quote' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'visibility_required' => true, + 'void_return' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_var_without_name' => true, + ]) + ->setFinder($finder) + ->setRiskyAllowed(true) + ->setUsingCache(true); \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0b41ede --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.0] - 2025-01-20 + +### Added +- Initial release of SpaceTime PHP library +- Core streaming functionality with `SpaceTimeStream` class +- Memory-efficient array implementation with `SpaceTimeArray` +- External sorting algorithm for datasets larger than memory +- External group-by algorithm for large-scale data aggregation +- Batch processing system with checkpoint support +- Memory pressure monitoring and automatic handling +- Laravel integration with service provider and Eloquent support +- Symfony bundle with console commands and DI configuration +- File processing utilities (CSV, JSON Lines) +- Comprehensive test suite +- Documentation and examples + +### Features +- Process files larger than available memory +- Streaming operations: map, filter, flatMap, chunk, batch +- Automatic memory management with configurable thresholds +- Progress tracking and resumable operations +- Framework integrations for Laravel and Symfony +- Type-safe operations with PHP 8.1+ features + +[Unreleased]: https://github.com/sqrtspace/spacetime-php/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/sqrtspace/spacetime-php/releases/tag/v1.0.0 \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..00d20b3 --- /dev/null +++ b/composer.json @@ -0,0 +1,89 @@ +{ + "name": "sqrtspace/spacetime", + "description": "High-performance PHP library for memory-efficient processing of large datasets with streaming, batching, and time/space complexity optimization", + "type": "library", + "keywords": [ + "streaming", + "memory-efficient", + "large-datasets", + "batch-processing", + "external-sort", + "laravel", + "symfony", + "performance", + "big-data" + ], + "homepage": "https://github.com/sqrtspace/spacetime-php", + "license": "Apache-2.0", + "authors": [ + { + "name": "David H. Friedel Jr.", + "email": "dfriedel@marketally.ai" + } + ], + "require": { + "php": ">=8.1", + "psr/log": "^3.0", + "psr/container": "^2.0", + "psr/simple-cache": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.10", + "friendsofphp/php-cs-fixer": "^3.40", + "symfony/console": "^6.0|^7.0", + "symfony/dependency-injection": "^6.0|^7.0", + "symfony/config": "^6.0|^7.0", + "illuminate/support": "^10.0|^11.0", + "illuminate/database": "^10.0|^11.0" + }, + "suggest": { + "ext-pcntl": "For better memory monitoring and signal handling", + "ext-apcu": "For high-performance caching", + "illuminate/support": "For Laravel integration", + "symfony/framework-bundle": "For Symfony integration" + }, + "autoload": { + "psr-4": { + "SqrtSpace\\SpaceTime\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "SqrtSpace\\SpaceTime\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-coverage": "vendor/bin/phpunit --coverage-html coverage", + "analyse": "vendor/bin/phpstan analyse", + "cs-check": "vendor/bin/php-cs-fixer fix --dry-run --diff", + "cs-fix": "vendor/bin/php-cs-fixer fix", + "check": [ + "@cs-check", + "@analyse", + "@test" + ] + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true + } + }, + "extra": { + "laravel": { + "providers": [ + "SqrtSpace\\SpaceTime\\Laravel\\SpaceTimeServiceProvider" + ] + }, + "symfony": { + "bundles": [ + "SqrtSpace\\SpaceTime\\Symfony\\SpaceTimeBundle" + ] + } + }, + "minimum-stability": "stable", + "prefer-stable": true +} \ No newline at end of file diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..942f0b1 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,24 @@ +parameters: + level: 8 + paths: + - src + - tests + excludePaths: + - tests/fixtures/* + treatPhpDocTypesAsCertain: false + checkMissingIterableValueType: false + checkGenericClassInNonGenericObjectType: false + reportUnmatchedIgnoredErrors: true + ignoreErrors: + # Allow using superglobals for framework integrations + - '#Accessing superglobal#' + # Allow dynamic properties in tests + - + message: '#Access to an undefined property#' + path: tests/* + parallel: + jobSize: 20 + maximumNumberOfProcesses: 4 + cache: + nodesByFileCountMax: 512 + nodesByStringCountMax: 512 \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..dfde436 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,46 @@ + + + + + tests + + + + + + src + + + src/Laravel + src/Symfony + + + + + + + + + + + + + + + + + + \ No newline at end of file