diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 000000000..ed3ebc9d1 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,144 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is **friendsofhyperf/components**, a monorepo containing popular PHP components for the Hyperf framework. It provides 40+ modular packages that extend Hyperf's capabilities with features like caching, mail, notifications, database enhancements, debugging tools, and more. + +## Key Commands + +### Testing & Quality Assurance + +```bash +# Run all tests (includes lint, unit tests, and type coverage) +composer test + +# Run unit tests only +composer test:unit + +# Run linting (PHP-CS-Fixer dry run) +composer test:lint + +# Run type coverage analysis +composer test:types + +# Run PHPStan static analysis +composer analyse +``` + +### Code Formatting + +```bash +# Fix code style issues +composer cs-fix + +# Fix code style for specific file/directory +composer cs-fix src/cache/ +``` + +### Development Utilities + +```bash +# Fix all composer.json files and normalize them +composer json-fix + +# Regenerate README files +composer gen:readme + +# Manage repository components +composer repo:pending +``` + +## Architecture & Structure + +### Monorepo Organization + +- **`src/`** - Contains all component packages, each is a separate publishable package +- **`tests/`** - Centralized tests for all components using Pest testing framework +- **`docs/`** - Multi-language documentation (en, zh-cn, zh-hk, zh-tw) +- **`bin/`** - Development scripts for repository management and tooling + +### Component Structure + +Each component in `src/` follows this pattern: + +- `composer.json` - Package definition and dependencies +- `src/` - Source code with PSR-4 autoloading +- `ConfigProvider.php` - Hyperf configuration provider +- Optional: `publish/`, `migrations/`, `resources/` + +### Key Component Categories + +- **Infrastructure**: cache, lock, ipc-broadcaster, tcp-sender +- **Database**: compoships, model-*, fast-paginate, mysql-grammar-addon +- **Messaging**: mail, notification, amqp-job, redis-subscriber +- **Development**: ide-helper, tinker, web-tinker, telescope, pest-plugin-hyperf +- **Utilities**: helpers, macros, facade, encryption, purifier +- **Integrations**: elasticsearch, sentry, openai-client, confd + +### Namespace Convention + +All components use `FriendsOfHyperf\{ComponentName}\` namespace pattern. + +### Configuration + +Components are auto-discoverable via Hyperf's ConfigProvider system. Each component registers itself in the main `composer.json` under `extra.hyperf.config`. + +## Testing Strategy + +- **Pest Framework**: Modern PHP testing framework used throughout +- **Centralized Tests**: All component tests are in `/tests` directory +- **Test Structure**: Organized by component (e.g., `tests/Cache/`, `tests/Mail/`) +- **Type Coverage**: Enforced via Pest plugin for type safety + +## Development Workflow + +### Working with Individual Components + +Each component can be developed independently but shares: + +- Common dependencies in root `composer.json` +- Shared coding standards via `.php-cs-fixer.php` +- Unified testing via central `tests/` directory + +### Code Standards + +- PHP 8.1+ required +- PSR-4 autoloading +- Hyperf ~3.1.0 compatibility +- PHP-CS-Fixer for code formatting +- PHPStan Level 5 for static analysis + +### Adding New Components + +1. Create directory under `src/new-component/` +2. Add composer.json with proper namespace +3. Add ConfigProvider.php +4. Register in root composer.json autoload and hyperf config +5. Add tests in `tests/NewComponent/` + +## Hyperf Integration + +This project extends Hyperf framework capabilities. When working with components: + +- Understand Hyperf's dependency injection system +- Use ConfigProvider pattern for service registration +- Follow Hyperf's annotation-based configuration where applicable +- Leverage Hyperf's event system for component integration + +## Documentation + +Components are documented in `docs/` with: + +- English documentation in `docs/en/` +- Chinese variants in `docs/zh-cn/`, `docs/zh-hk/`, `docs/zh-tw/` +- Each component has its own markdown file explaining usage + +## Release Management + +The repository uses automated tooling for: + +- Repository splitting for individual component releases +- Documentation generation and synchronization +- Dependency version management across components diff --git a/.claude/commands/fix-github-issue.md b/.claude/commands/fix-github-issue.md new file mode 100644 index 000000000..4334bf626 --- /dev/null +++ b/.claude/commands/fix-github-issue.md @@ -0,0 +1,14 @@ +Please analyze and fix the GitHub issue: $ARGUMENTS. + +Follow these steps: + +1. Use `gh issue view` to get the issue details +2. Understand the problem described in the issue +3. Search the codebase for relevant files +4. Implement the necessary changes to fix the issue +5. Write and run tests to verify the fix +6. Ensure code passes linting and type checking +7. Create a descriptive commit message +8. Push and create a PR + +Remember to use the GitHub CLI (`gh`) for all GitHub-related tasks. diff --git a/src/macros/src/StrMixin.php b/src/macros/src/StrMixin.php index b19da3761..ca0fd0bb3 100644 --- a/src/macros/src/StrMixin.php +++ b/src/macros/src/StrMixin.php @@ -72,4 +72,18 @@ public function transliterate() { return fn ($string, $unknown = '?', $strict = false) => ASCII::to_transliterate($string, $unknown, $strict); } + + public function doesntEndWith() + { + return function ($haystack, $needles) { + return ! Str::endsWith($haystack, $needles); + }; + } + + public function doesntStartWith() + { + return function ($haystack, $needles) { + return ! Str::startsWith($haystack, $needles); + }; + } } diff --git a/src/macros/src/StringableMixin.php b/src/macros/src/StringableMixin.php index 967aae557..bb3fe24c5 100644 --- a/src/macros/src/StringableMixin.php +++ b/src/macros/src/StringableMixin.php @@ -80,4 +80,16 @@ public function whenIsAscii() /* @phpstan-ignore-next-line */ return fn ($callback, $default = null) => $this->when($this->isAscii(), $callback, $default); } + + public function doesntEndWith() + { + /* @phpstan-ignore-next-line */ + return fn ($needles) => ! $this->endsWith($needles); + } + + public function doesntStartWith() + { + /* @phpstan-ignore-next-line */ + return fn ($needles) => ! $this->startsWith($needles); + } } diff --git a/tests/Macros/StrTest.php b/tests/Macros/StrTest.php index 7b2dffb62..035fc71c6 100644 --- a/tests/Macros/StrTest.php +++ b/tests/Macros/StrTest.php @@ -64,3 +64,23 @@ test('test transliterateStrict', function (string $value, string $expected): void { $this->assertSame($expected, Str::transliterate($value, '?', true)); })->with('ialCharacterProvider'); + +test('test doesntEndWith', function () { + expect(Str::doesntEndWith('hello world', 'world'))->toBeFalse(); + expect(Str::doesntEndWith('hello world', 'foo'))->toBeTrue(); + expect(Str::doesntEndWith('hello world', ['foo', 'bar']))->toBeTrue(); + expect(Str::doesntEndWith('hello world', ['foo', 'world']))->toBeFalse(); + expect(Str::doesntEndWith('hello world', ''))->toBeTrue(); + expect(Str::doesntEndWith('', 'world'))->toBeTrue(); + expect(Str::doesntEndWith('', ''))->toBeTrue(); +}); + +test('test doesntStartWith', function () { + expect(Str::doesntStartWith('hello world', 'hello'))->toBeFalse(); + expect(Str::doesntStartWith('hello world', 'foo'))->toBeTrue(); + expect(Str::doesntStartWith('hello world', ['foo', 'bar']))->toBeTrue(); + expect(Str::doesntStartWith('hello world', ['foo', 'hello']))->toBeFalse(); + expect(Str::doesntStartWith('hello world', ''))->toBeTrue(); + expect(Str::doesntStartWith('', 'hello'))->toBeTrue(); + expect(Str::doesntStartWith('', ''))->toBeTrue(); +}); diff --git a/tests/Macros/StringableTest.php b/tests/Macros/StringableTest.php index eb6a1eb8f..86cb5fc18 100644 --- a/tests/Macros/StringableTest.php +++ b/tests/Macros/StringableTest.php @@ -84,3 +84,23 @@ $this->assertNotSame('foo', $encrypted->value()); $this->assertSame('foo', $encrypted->decrypt()->value()); }); + +test('test doesntEndWith', function () { + expect($this->stringable('hello world')->doesntEndWith('world'))->toBeFalse(); + expect($this->stringable('hello world')->doesntEndWith('foo'))->toBeTrue(); + expect($this->stringable('hello world')->doesntEndWith(['foo', 'bar']))->toBeTrue(); + expect($this->stringable('hello world')->doesntEndWith(['foo', 'world']))->toBeFalse(); + expect($this->stringable('hello world')->doesntEndWith(''))->toBeTrue(); + expect($this->stringable('')->doesntEndWith('world'))->toBeTrue(); + expect($this->stringable('')->doesntEndWith(''))->toBeTrue(); +}); + +test('test doesntStartWith', function () { + expect($this->stringable('hello world')->doesntStartWith('hello'))->toBeFalse(); + expect($this->stringable('hello world')->doesntStartWith('foo'))->toBeTrue(); + expect($this->stringable('hello world')->doesntStartWith(['foo', 'bar']))->toBeTrue(); + expect($this->stringable('hello world')->doesntStartWith(['foo', 'hello']))->toBeFalse(); + expect($this->stringable('hello world')->doesntStartWith(''))->toBeTrue(); + expect($this->stringable('')->doesntStartWith('hello'))->toBeTrue(); + expect($this->stringable('')->doesntStartWith(''))->toBeTrue(); +});