From 0cba65e8ff906db8cfb4e2d329e2a62854f91171 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sun, 29 Mar 2026 12:56:41 +0200 Subject: [PATCH 1/3] docs: Document the .horde.yml format as understood by this library --- README.md | 15 +- doc/FORMAT.md | 730 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 738 insertions(+), 7 deletions(-) create mode 100644 doc/FORMAT.md diff --git a/README.md b/README.md index 64ff081..207693e 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ # horde/hordeymlfile -Read and write .horde.yml files and changelog.yml files. +Handles the .horde.yml and changelog.yml file formats. -## Usage +## Purpose -## File Format +The .horde.yml format is the source of truth for composer.json generation and other package meta information. -https://wiki.horde.org/Doc/Dev/HordeYmlFormat +## Documentation + +Format documentation is available at [doc/FORMAT.md](doc/FORMAT.md). ## Origin -Same or similar implementations existed in +Same or similar implementations existed in: - horde/horde-installer-plugin Composer plugin - horde/components Developer CLI - horde/hordectl Admin CLI -Depending on these packages for further use cases was not practical so I split off and generalized as far as I could. - +Refactoring into a separate library facilitates reuse. \ No newline at end of file diff --git a/doc/FORMAT.md b/doc/FORMAT.md new file mode 100644 index 0000000..80aa05d --- /dev/null +++ b/doc/FORMAT.md @@ -0,0 +1,730 @@ +# .horde.yml Format Specification + +Version: 1.0 +Last Updated: 2026-03-29 + +## Overview + +The `.horde.yml` file is the source of truth for metadata in the horde framework. +It is used to generate various formats (composer.json, website content etc.). + +**Location:** Must be named `.horde.yml` and located in the root directory of a component. + +**Format:** YAML (YAML Ain't Markup Language) - specifically, the subset supported by the Horde_Yaml library. + +## File Structure + +```yaml +--- +id: string +name: string +vendor: string +full: string +description: string +type: string +homepage: string +list: string +keywords: [string, ...] +version: + release: string + api: string +state: + release: string + api: string +license: + identifier: string + uri: string +authors: + - name: string + user: string + email: string + active: boolean + role: string +dependencies: + uses: string + required: + php: string + ext: {extension: version, ...} + pear: {package: version, ...} + composer: {package: version, ...} + optional: + ext: {extension: version, ...} + pear: {package: version, ...} + composer: {package: version, ...} + dev: + composer: {package: version, ...} +autoload: + psr-0: {namespace: path, ...} + psr-4: {namespace: path, ...} + classmap: [path, ...] + files: [path, ...] +autoload-dev: + psr-4: {namespace: path, ...} + classmap: [path, ...] +provides: + interface: version +allow-plugins: + plugin/name: boolean +config: + key: value +extra: + key: value +commands: [path, ...] +nocommands: [path, ...] +``` + +## Field Reference + +### Core Identification Fields + +#### `id` (string, required) +Internal identifier for the component. Typically matches the component name. + +**Example:** +```yaml +id: Http +``` + +#### `name` (string, required) +The component name. Uppercase for libraries, lowercase for apps. + +**Example:** +```yaml +name: Http +``` + +**Generated Composer Name:** Combined with `vendor` to create the composer package name: `vendor/name` (lowercase). + +Example: horde/http + +#### `vendor` (string, required) +The vendor/organization name. Defaults to `horde` if not specified. + +**Example:** +```yaml +vendor: horde +``` + +#### `full` (string, required) +The full human-readable name of the component. + +**Example:** +```yaml +full: HTTP Client Library +``` + +#### `description` (string, required) +A detailed description of what the component does. Used as the package description in composer.json. + +**Example:** +```yaml +description: Provides HTTP client functionality with support for multiple adapters +``` + +**Multi-line variant:** +```yaml +description: | + This package provides HTTP client functionality with support for + multiple adapters including cURL, sockets, and PSR-18 clients. +``` + +#### `type` (string, required) +The component type. Determines the composer type and installation behavior. + +**Valid values:** +- `library` - Standard PHP composer library. Composer tool may auto-sense horde-library in some cases. +- `horde-library` - Library which needs features of the horde/horde-installer-plugin. +- `application` - Standard PHP composer application. Composer tool may auto-sense to transform to horde-application in some cases. +- `horde-theme` - A Horde Theme package. +- `composer-plugin` - A composer plugin. + +**Example:** +```yaml +type: library +``` + +### Discovery and Metadata Fields + +#### `homepage` (string, optional) +URL to the project's homepage or documentation. + +**Example:** +```yaml +homepage: https://www.horde.org/libraries/Horde_Http +``` + +**Default:** `https://www.horde.org` if not specified. + +#### `list` (string, optional) +Mailing list identifier. Typically `horde`, `dev`, or vendor-specific. + +**Example:** +```yaml +list: horde +``` + +#### `keywords` (array of strings, optional) +Tags used for package discovery on Packagist and other repositories. Used to improve searchability and categorization. + +**Best Practices:** +- Use 3-10 keywords per package +- Use lowercase for consistency +- Include relevant standards (e.g., `psr-7`, `psr-18`) +- Include domain/technology terms (e.g., `http`, `mail`, `database`) +- Avoid redundant keywords (name/vendor already indexed) + +**Example:** +```yaml +keywords: + - http + - client + - web + - psr-7 + - psr-18 + - rest +``` + +**Empty variant** (explicitly states no keywords): +```yaml +keywords: [] +``` + +**Note:** Omitting the `keywords` field is equivalent to an empty array. Explicit empty key is preferred over omitting. + +### Version and State Fields + +#### `version` (object, required) +Version information for the component. + +**Structure:** +```yaml +version: + release: string # Release version (semver or semver-like format) + api: string # API version (semver or semver-like format) +``` + +**Example:** +```yaml +version: + release: 3.0.1 + api: 3.0.0 +``` + +**Semantic Versioning:** Prefer strict [semver](https://semver.org/) format `MAJOR.MINOR.PATCH[-PRERELEASE]` over semver-like formats. + +#### `state` (object, required) +Stability state for release and API. + +**Valid states:** `alpha`, `beta`, `stable` + +**Structure:** +```yaml +state: + release: string + api: string +``` + +**Example:** +```yaml +state: + release: stable + api: stable +``` + +### Legal and Attribution Fields + +#### `license` (object, required) +License information using SPDX identifiers. + +**Structure:** +```yaml +license: + identifier: string # SPDX license identifier + uri: string # URL to license text +``` + +**Example:** +```yaml +license: + identifier: LGPL-2.1-only + uri: http://www.horde.org/licenses/lgpl21 +``` + +**Common SPDX identifiers:** +- `LGPL-2.1-only` - Most Horde libraries +- `GPL-2.0-only` - Some applications +- `BSD-2-Clause` - BSD-licensed components +- `MIT` - MIT-licensed components + +#### `authors` (array of objects, required) +List of component authors/maintainers. + +**Structure:** +```yaml +authors: + - name: string # Full name + user: string # Username/handle (optional) + email: string # Email address (optional) + active: boolean # Currently active maintainer (optional) + role: string # Role: lead, developer, contributor (optional) +``` + +**Example:** +```yaml +authors: + - name: Jan Schneider + user: jan + email: jan@horde.org + active: true + role: lead + - name: Chuck Hagenbuch + user: chuck + email: chuck@horde.org + active: false + role: lead +``` + +**Minimal example:** +```yaml +authors: + - name: Developer Name + role: lead +``` + +### Dependency Management + +#### `dependencies` (object, optional) +Declares all component dependencies. + +**Structure:** +```yaml +dependencies: + uses: string # Special dependency mode (optional) + required: # Production dependencies + php: string + ext: object + pear: object + composer: object + optional: # Optional/suggested dependencies + ext: object + pear: object + composer: object + dev: # Development-only dependencies + composer: object +``` + +#### `dependencies.uses` (string, optional) +Special dependency handling mode. + +**Valid values:** +- `readonly-parameters` - Use readonly constructor parameters (PHP 8.1+) + +**Example:** +```yaml +dependencies: + uses: readonly-parameters +``` + +#### `dependencies.required` (object, optional) +Required production dependencies. + +**PHP version:** +```yaml +dependencies: + required: + php: ^8.2 +``` + +**PHP extensions:** +```yaml +dependencies: + required: + ext: + json: '*' + mbstring: '*' + xml: ^8.0 +``` + +**Composer packages:** +```yaml +dependencies: + required: + composer: + horde/exception: ^3 + psr/log: ^3.0 +``` + +**PEAR packages** (legacy): +```yaml +dependencies: + required: + pear: + pear.horde.org/Horde_Exception: '*' +``` + +**Complete example:** +```yaml +dependencies: + required: + php: ^8.2 + ext: + json: '*' + mbstring: '*' + composer: + horde/exception: ^3 + psr/http-message: ^2 +``` + +#### `dependencies.optional` (object, optional) +Optional dependencies that enhance functionality but are not required. +In composer.json these translate to suggests field + +**Example:** +```yaml +dependencies: + optional: + ext: + curl: '*' + imagick: '*' + composer: + horde/cache: ^3 +``` + +#### `dependencies.dev` (object, optional) +Development and testing dependencies. +In composer.json these translate to require-dev field + +**Example:** +```yaml +dependencies: + dev: + composer: + phpunit/phpunit: ^12 + phpstan/phpstan: ^2 +``` + +**Note:** We prefer not to list global development tools (phpunit, phpstan, php-cs-fixer) as dependencies in libraries. +They are installed globally via phive or composer. It is however valid and possible to do so, i.e. if your code provides its own plugins to these systems and phpstan needs these for analysis. + +### Autoloading Configuration + +#### `autoload` (object, optional) +Defines how to autoload the component's classes. + +**Structure:** +```yaml +autoload: + psr-0: {namespace: path} + psr-4: {namespace: path} + classmap: [path, ...] + files: [path, ...] +``` + +**PSR-4 example** (recommended for new code): +```yaml +autoload: + psr-4: + Horde\Http\: src/ +``` + +**PSR-0 example** (legacy Horde libraries): +```yaml +autoload: + psr-0: + Horde_Http: lib/ +``` + +**Mixed example:** +```yaml +autoload: + psr-0: + Horde_Http: lib/ + psr-4: + Horde\Http\: src/ +``` + +**Classmap example:** +```yaml +autoload: + classmap: + - lib/ +``` + +**Files example:** +```yaml +autoload: + files: + - lib/functions.php +``` + +**Auto-detection:** If `autoload` is not specified: +- If `lib/` directory exists → PSR-0 autoloading from `lib/` +- If `src/` directory exists → PSR-4 autoloading from `src/` + +#### `autoload-dev` (object, optional) +Defines autoloading for development/test files. + +**Example:** +```yaml +autoload-dev: + psr-4: + Horde\Http\Test\: test/ +``` + +**Auto-detection:** If `autoload-dev` is not specified and `test/` directory exists, PSR-4 autoloading is configured automatically. + +### Virtual Packages + +#### `provides` (object, optional) +Declares that this package provides specific interfaces or virtual packages. + +**Example:** +```yaml +provides: + psr/log-implementation: 3.0.0 + psr/http-client-implementation: 1.0.0 +``` + +**Use case:** Allows other packages to depend on an interface rather than specific implementation. + +### Composer Configuration + +#### `allow-plugins` (object or boolean, optional) +Controls which Composer plugins are allowed to execute. + +**Allow all plugins:** +```yaml +allow-plugins: true +``` + +**Specific plugins:** +```yaml +allow-plugins: + horde/horde-installer-plugin: true + composer/installers: true +``` + +**Auto-detection:** Known plugins (horde-installer-plugin, composer/installers) are auto-allowed if present in dependencies. + +#### `config` (object, optional) +Additional Composer configuration options. + +**Example:** +```yaml +config: + platform: + php: 8.2.0 + optimize-autoloader: true +``` + +**Note:** Most config options are not needed in library .horde.yml files. + +#### `extra` (object, optional) +Arbitrary data for use by scripts or plugins. + +**Example:** +```yaml +extra: + branch-alias: + dev-FRAMEWORK_6_0: 3.x-dev +``` + +**Deep merge:** If the composer generator creates `extra` fields, .horde.yml values are deep-merged on top. + +### Binary Commands + +#### `commands` (array of strings, optional) +Explicit list of executable commands to expose in `vendor/bin/`. + +**Example:** +```yaml +commands: + - bin/horde-components + - bin/horde-git-tools +``` + +**Auto-detection:** If not specified, all executable files in `bin/` directory are automatically included. + +#### `nocommands` (array of strings, optional) +Exclusion list of files which explicitly should NOT show up in `vendor/bin/` (applied after auto-detection or `commands`). + +**Example:** +```yaml +nocommands: + - bin/dev-helper + - bin/local-test +``` + +## Changelog File + +**Note:** The changelog format will be documented in a separate section. + + + +## Complete Example + +```yaml +--- +id: Http +name: Http +vendor: horde +full: HTTP Client Library +description: | + Provides HTTP client functionality with support for multiple + adapters including cURL, sockets, and PSR-18 clients. +type: library +homepage: https://www.horde.org/libraries/Horde_Http +list: horde +keywords: + - http + - client + - web + - psr-7 + - psr-18 + - rest +version: + release: 3.0.1 + api: 3.0.0 +state: + release: stable + api: stable +license: + identifier: LGPL-2.1-only + uri: http://www.horde.org/licenses/lgpl21 +authors: + - name: Jan Schneider + user: jan + email: jan@horde.org + active: true + role: lead + - name: Chuck Hagenbuch + user: chuck + email: chuck@horde.org + active: false + role: lead +dependencies: + uses: readonly-parameters + required: + php: ^8.2 + ext: + json: '*' + composer: + horde/exception: ^3 + horde/support: ^3 + psr/http-message: ^2 + psr/http-factory: ^1.0.2 + psr/http-client: ^1.0.3 + optional: + ext: + curl: '*' + composer: + horde/url: ^3 + dev: + composer: + phpunit/phpunit: ^12 + phpstan/phpstan: ^2 +autoload: + psr-0: + Horde_Http: lib/ + psr-4: + Horde\Http\: src/ +autoload-dev: + psr-4: + Horde\Http\Test\: test/ +provides: + psr/http-client-implementation: 1.0.0 +allow-plugins: + horde/horde-installer-plugin: true +``` + +## Minimal Example + +The absolute minimum required for a valid .horde.yml: + +```yaml +--- +id: MyComponent +name: MyComponent +vendor: horde +full: My Component +description: A brief description +type: library +version: + release: 0.0.1 + api: 0.0.1 +state: + release: alpha + api: alpha +license: + identifier: LGPL-2.1-only + uri: http://www.horde.org/licenses/lgpl21 +authors: + - name: Developer Name + role: lead +``` + +## Field Processing Rules + +### String Normalization +- **vendor** and **name**: Combined and lowercased for composer package name +- **keywords**: Normalized to lowercase, duplicates removed, empty strings filtered + +### Default Values +- **vendor**: Defaults to `horde` if not specified +- **name**: Defaults to directory name if not specified +- **homepage**: Defaults to `https://www.horde.org` if not specified +- **state**: Defaults to `alpha` for both release and API if not specified + +### Required vs Optional +Fields marked **required** must be present for successful composer.json generation. Optional fields are only included in output if present in .horde.yml. + +## Validation + +The HordeYmlFile library performs minimal validation: +- File must exist and be readable +- File must be valid YAML +- No schema validation is performed at library level + +Component-level validation (horde-components qc command) performs additional checks: +- Required fields presence +- Version format validity +- License identifier validity +- Author structure completeness + +## Usage with horde-components + +The .horde.yml file is the primary input for horde-components tool: + +**Generate composer.json:** +```bash +horde-components composer +``` + +**Quality check:** +```bash +horde-components qc +``` + +**Quality check with auto-fix:** +```bash +horde-components qc --fix +``` + +**Create release:** +```bash +horde-components release h6 +``` + +## Version History + +- **1.0** (2026-03-29): Initial comprehensive documentation + - Documented all current fields + - Added keywords field specification + - Added examples and best practices + +## References + +- [Composer Schema](https://getcomposer.org/doc/04-schema.md) +- [SPDX License List](https://spdx.org/licenses/) +- [Semantic Versioning](https://semver.org/) +- [PSR Standards](https://www.php-fig.org/psr/) From 5019a82a3f2ee2fb16c91992ffcb0404888a88c4 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sun, 29 Mar 2026 12:56:52 +0200 Subject: [PATCH 2/3] feat: Support for composer.json and packagist.org keywords --- src/HordeYmlFile.php | 24 +++++++ test/unit/HordeYmlFileTest.php | 112 +++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/HordeYmlFile.php b/src/HordeYmlFile.php index d258084..b33efe3 100644 --- a/src/HordeYmlFile.php +++ b/src/HordeYmlFile.php @@ -160,6 +160,30 @@ public function setList(string $list): self return $this; } + // ========== Keywords Methods ========== + + public function getKeywords(): array + { + if (!isset($this->hordeYml->keywords)) { + return []; + } + $keywords = $this->hordeYml->keywords; + // Convert to simple array of strings + $keywords = json_decode(json_encode($keywords), true) ?: []; + // Normalize: filter out garbage, lowercase, deduplicate + $keywords = array_filter($keywords, fn($k) => is_string($k) && trim($k) !== ''); + $keywords = array_map('strtolower', $keywords); + $keywords = array_map('trim', $keywords); + $keywords = array_unique($keywords); + return array_values($keywords); + } + + public function setKeywords(array $keywords): self + { + $this->hordeYml->keywords = $keywords; + return $this; + } + // ========== Autoload Methods ========== public function getAutoload(): ?Autoload diff --git a/test/unit/HordeYmlFileTest.php b/test/unit/HordeYmlFileTest.php index 9e649a2..686e496 100644 --- a/test/unit/HordeYmlFileTest.php +++ b/test/unit/HordeYmlFileTest.php @@ -373,6 +373,118 @@ public function testSetList(): void $this->assertEquals('horde', $horde->getList()); } + // ========== Keywords Tests ========== + + public function testGetKeywords(): void + { + $file = $this->createTempHordeYml([ + 'keywords' => ['http', 'client', 'web'], + ]); + + $horde = new HordeYmlFile($file); + $keywords = $horde->getKeywords(); + + $this->assertCount(3, $keywords); + $this->assertContains('http', $keywords); + $this->assertContains('client', $keywords); + $this->assertContains('web', $keywords); + } + + public function testGetKeywordsReturnsEmptyArrayWhenMissing(): void + { + $file = $this->createTempHordeYml(['id' => 'test']); + + $horde = new HordeYmlFile($file); + $this->assertEquals([], $horde->getKeywords()); + } + + public function testSetKeywords(): void + { + $file = $this->createTempHordeYml(['id' => 'test']); + + $horde = new HordeYmlFile($file); + $horde->setKeywords(['keyword1', 'keyword2']); + + $keywords = $horde->getKeywords(); + $this->assertCount(2, $keywords); + $this->assertContains('keyword1', $keywords); + $this->assertContains('keyword2', $keywords); + } + + public function testSetKeywordsEmpty(): void + { + $file = $this->createTempHordeYml([ + 'keywords' => ['old', 'keywords'], + ]); + + $horde = new HordeYmlFile($file); + $horde->setKeywords([]); + + $this->assertEquals([], $horde->getKeywords()); + } + + public function testKeywordsPersistAfterSave(): void + { + $file = $this->createTempHordeYml(['id' => 'test']); + + $horde = new HordeYmlFile($file); + $horde->setKeywords(['persistence', 'test']); + $horde->save(); + + // Reload and verify + $reloaded = new HordeYmlFile($file); + $keywords = $reloaded->getKeywords(); + $this->assertCount(2, $keywords); + $this->assertContains('persistence', $keywords); + $this->assertContains('test', $keywords); + } + + public function testGetKeywordsNormalizes(): void + { + $file = $this->createTempHordeYml([ + 'keywords' => ['HTTP', 'Client', 'WEB', 'http'], // Mixed case, duplicates + ]); + + $horde = new HordeYmlFile($file); + $keywords = $horde->getKeywords(); + + // Should be normalized to lowercase and deduplicated + $this->assertCount(3, $keywords); + $this->assertContains('http', $keywords); + $this->assertContains('client', $keywords); + $this->assertContains('web', $keywords); + } + + public function testGetKeywordsFiltersGarbage(): void + { + $file = $this->createTempHordeYml([ + 'keywords' => ['valid', '', ' ', 'also-valid', null], + ]); + + $horde = new HordeYmlFile($file); + $keywords = $horde->getKeywords(); + + // Should filter out empty strings and null + $this->assertCount(2, $keywords); + $this->assertContains('valid', $keywords); + $this->assertContains('also-valid', $keywords); + } + + public function testGetKeywordsTrimsWhitespace(): void + { + $file = $this->createTempHordeYml([ + 'keywords' => [' spaces ', 'tabs ', ' mixed '], + ]); + + $horde = new HordeYmlFile($file); + $keywords = $horde->getKeywords(); + + $this->assertCount(3, $keywords); + $this->assertContains('spaces', $keywords); + $this->assertContains('tabs', $keywords); + $this->assertContains('mixed', $keywords); + } + // ========== Autoload Tests ========== public function testGetAutoload(): void From 11fea5d65922de6f21a8765db054de2d0064f0fc Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sun, 29 Mar 2026 12:59:45 +0200 Subject: [PATCH 3/3] style: php-cs-fixer --- .horde.yml | 4 ++++ composer.json | 16 ++++++++-------- src/Autoload.php | 6 +++--- src/ChangelogYmlFile.php | 14 +++++++------- src/DependencySet.php | 6 +++--- src/HordeYmlFile.php | 8 ++++---- src/InvalidChangelogFileException.php | 1 + src/InvalidHordeYmlFileException.php | 1 + src/Provides.php | 4 ++-- test/unit/ChangelogYmlFileTest.php | 6 +++--- test/unit/HordeYmlFileTest.php | 4 ++-- 11 files changed, 38 insertions(+), 32 deletions(-) diff --git a/.horde.yml b/.horde.yml index 92bdc7b..f7c0198 100644 --- a/.horde.yml +++ b/.horde.yml @@ -5,6 +5,10 @@ full: Handle .horde.yml file and changelog.yml file description: Handle .horde.yml file and changelog.yml file list: horde type: library +keywords: + - metadata + - changelog + - composer authors: - name: Ralf Lang diff --git a/composer.json b/composer.json index 08b9929..208697e 100644 --- a/composer.json +++ b/composer.json @@ -11,12 +11,17 @@ "role": "contributor" } ], - "time": "2026-03-04", + "keywords": [ + "metadata", + "changelog", + "composer" + ], + "time": "2026-03-29", "repositories": [], "require": { "php": "^8.2", - "horde/version": "^1 || dev-FRAMEWORK_6_0", - "horde/yaml": "^3 || dev-FRAMEWORK_6_0" + "horde/version": "^1", + "horde/yaml": "^3" }, "require-dev": { "phpunit/phpunit": "^12", @@ -37,10 +42,5 @@ }, "config": { "allow-plugins": {} - }, - "extra": { - "branch-alias": { - "dev-FRAMEWORK_6_0": "0.x-dev" - } } } \ No newline at end of file diff --git a/src/Autoload.php b/src/Autoload.php index 70bd506..46289a4 100644 --- a/src/Autoload.php +++ b/src/Autoload.php @@ -51,8 +51,8 @@ public static function fromStdClass(stdClass $data): self return new self( psr4: $psr4, - classmap: isset($data->classmap) ? (array)$data->classmap : [], - files: isset($data->files) ? (array)$data->files : [], + classmap: isset($data->classmap) ? (array) $data->classmap : [], + files: isset($data->files) ? (array) $data->files : [], ); } @@ -93,7 +93,7 @@ public function toStdClass(): stdClass { $result = new stdClass(); if (!empty($this->psr4)) { - $result->{'psr-4'} = (object)$this->psr4; + $result->{'psr-4'} = (object) $this->psr4; } if (!empty($this->classmap)) { $result->classmap = $this->classmap; diff --git a/src/ChangelogYmlFile.php b/src/ChangelogYmlFile.php index a3416f8..7ed9765 100644 --- a/src/ChangelogYmlFile.php +++ b/src/ChangelogYmlFile.php @@ -80,31 +80,31 @@ public function save(): void public function hasVersion(string $version): bool { - $changelog = (array)$this->changelogYml; + $changelog = (array) $this->changelogYml; return isset($changelog[$version]); } public function getVersionEntry(string $version): ?stdClass { - $changelog = (array)$this->changelogYml; - return isset($changelog[$version]) ? (object)$changelog[$version] : null; + $changelog = (array) $this->changelogYml; + return isset($changelog[$version]) ? (object) $changelog[$version] : null; } public function addVersionEntry(string $version, array $entry): self { - $changelog = (array)$this->changelogYml; - $changelog[$version] = (object)$entry; + $changelog = (array) $this->changelogYml; + $changelog[$version] = (object) $entry; // Sort by version (newest first) using natural comparison uksort($changelog, fn($a, $b) => strnatcmp($b, $a)); - $this->changelogYml = (object)$changelog; + $this->changelogYml = (object) $changelog; return $this; } public function getVersions(): array { - return array_keys((array)$this->changelogYml); + return array_keys((array) $this->changelogYml); } public function toArray(): array diff --git a/src/DependencySet.php b/src/DependencySet.php index 16aa6e4..0bd6b52 100644 --- a/src/DependencySet.php +++ b/src/DependencySet.php @@ -65,7 +65,7 @@ public static function fromStdClass(stdClass $data): self $ext = []; if (isset($data->ext)) { // Extensions can be array or object (legacy formats) - $ext = is_array($data->ext) ? $data->ext : (array)$data->ext; + $ext = is_array($data->ext) ? $data->ext : (array) $data->ext; } return new self( @@ -135,10 +135,10 @@ public function toStdClass(): stdClass $result->php = $this->php; } if (!empty($this->composer)) { - $result->composer = (object)$this->composer; + $result->composer = (object) $this->composer; } if (!empty($this->pear)) { - $result->pear = (object)$this->pear; + $result->pear = (object) $this->pear; } if (!empty($this->ext)) { $result->ext = $this->ext; diff --git a/src/HordeYmlFile.php b/src/HordeYmlFile.php index b33efe3..607b9dd 100644 --- a/src/HordeYmlFile.php +++ b/src/HordeYmlFile.php @@ -324,12 +324,12 @@ public function setDescription(string $description): self public function getLicense(): ?stdClass { - return isset($this->hordeYml->license) ? (object)$this->hordeYml->license : null; + return isset($this->hordeYml->license) ? (object) $this->hordeYml->license : null; } public function setLicense(string $identifier, string $uri): self { - $this->hordeYml->license = (object)[ + $this->hordeYml->license = (object) [ 'identifier' => $identifier, 'uri' => $uri, ]; @@ -392,12 +392,12 @@ public function getAllowedPlugins(): array if ($plugins === true) { return ['*' => true]; } - return is_array($plugins) ? $plugins : (array)$plugins; + return is_array($plugins) ? $plugins : (array) $plugins; } public function setAllowedPlugins(array $plugins): self { - $this->hordeYml->{'allow-plugins'} = (object)$plugins; + $this->hordeYml->{'allow-plugins'} = (object) $plugins; return $this; } diff --git a/src/InvalidChangelogFileException.php b/src/InvalidChangelogFileException.php index 5c88032..3a4f842 100644 --- a/src/InvalidChangelogFileException.php +++ b/src/InvalidChangelogFileException.php @@ -1,6 +1,7 @@ implementations; + return (object) $this->implementations; } /** diff --git a/test/unit/ChangelogYmlFileTest.php b/test/unit/ChangelogYmlFileTest.php index 4e210ad..7a32b09 100644 --- a/test/unit/ChangelogYmlFileTest.php +++ b/test/unit/ChangelogYmlFileTest.php @@ -25,7 +25,7 @@ protected function setUp(): void { $this->testDataDir = dirname(__DIR__) . '/fixtures/generated'; if (!is_dir($this->testDataDir)) { - mkdir($this->testDataDir, 0755, true); + mkdir($this->testDataDir, 0o755, true); } } @@ -282,7 +282,7 @@ public function testToStringReturnsYaml(): void ]); $changelog = new ChangelogYmlFile($file); - $yaml = (string)$changelog; + $yaml = (string) $changelog; $this->assertStringContainsString('1.0.0', $yaml); $this->assertStringContainsString('notes', $yaml); @@ -297,7 +297,7 @@ public function testToStringWithMultipleVersions(): void ]); $changelog = new ChangelogYmlFile($file); - $yaml = (string)$changelog; + $yaml = (string) $changelog; $this->assertStringContainsString('1.0.0', $yaml); $this->assertStringContainsString('2.0.0', $yaml); diff --git a/test/unit/HordeYmlFileTest.php b/test/unit/HordeYmlFileTest.php index 686e496..557a57c 100644 --- a/test/unit/HordeYmlFileTest.php +++ b/test/unit/HordeYmlFileTest.php @@ -25,7 +25,7 @@ protected function setUp(): void { $this->testDataDir = dirname(__DIR__) . '/fixtures/generated'; if (!is_dir($this->testDataDir)) { - mkdir($this->testDataDir, 0755, true); + mkdir($this->testDataDir, 0o755, true); } } @@ -826,7 +826,7 @@ public function testToStringReturnsYaml(): void ]); $horde = new HordeYmlFile($file); - $yaml = (string)$horde; + $yaml = (string) $horde; $this->assertStringContainsString('id:', $yaml); $this->assertStringContainsString('Test', $yaml);