From 32254373c82219e2c5800803201ec4cfbdcbc641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C5=82as=20Piotr?= Date: Sat, 30 May 2026 13:08:52 +0200 Subject: [PATCH 1/3] feat: migrate all classes to CrazyGoat\ZVec namespace with PSR-4 autoloading - Add namespace CrazyGoat\ZVec to all source files in src/ - Add class_alias() BC layer in ZVec.php barrel file for backward compatibility - Add interface_alias() is not available; use class_alias() for interfaces too - Remove classmap from composer.json (PSR-4 handles everything now) - Update embedding tests to use namespaced class references - Add tests/test_namespace_bc.phpt verifying both namespace styles work - Update CHANGELOG.md with SMELL-013 entry All 137 existing tests pass (0 failures, 2 pre-existing XFAIL). Fixes #94 --- CHANGELOG.md | 16 ++-- composer.json | 5 +- src/ZVec.php | 25 ++++++ src/ZVecCollectionOptions.php | 2 + src/ZVecCollectionStats.php | 4 + src/ZVecDoc.php | 4 + src/ZVecException.php | 5 ++ src/ZVecFieldSchema.php | 4 + src/ZVecGroupByVectorQuery.php | 4 + src/ZVecIndexParams.php | 4 + src/ZVecQueryInterface.php | 4 + src/ZVecReRanker.php | 2 + src/ZVecRerankedDoc.php | 2 + src/ZVecRrfReRanker.php | 2 + src/ZVecSchema.php | 4 + src/ZVecVectorQuery.php | 4 + src/ZVecWeightedReRanker.php | 2 + src/embeddings/EmbeddingInterfaces.php | 4 + src/embeddings/OpenAIDenseEmbedding.php | 2 + src/embeddings/QwenDenseEmbedding.php | 2 + tests/test_embeddings_integration.phpt | 4 + tests/test_embeddings_interfaces.phpt | 30 ++++--- tests/test_namespace_bc.phpt | 105 ++++++++++++++++++++++++ 23 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 tests/test_namespace_bc.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index d56082b..9d503d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,16 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **SMELL-009: Added `queryWithReranker()` method for type-safe reranked queries** (#90) - - New `queryWithReranker()` method on `ZVec` returns `ZVecRerankedDoc[]` — static analysis tools can infer the correct return type - - Supports `string|ZVecVectorQuery` as field name, with all existing query parameters - - Routes FP64 queries via internal `queryWithRerankerFp64()` helper +- **SMELL-013: Migrated all classes to `CrazyGoat\ZVec\` namespace with PSR-4 autoloading** (#94) + - All library classes now live under `CrazyGoat\ZVec\` namespace + - Global class names preserved via `class_alias()` for backward compatibility + - Use `CrazyGoat\ZVec\ZVec` with `use` statement or keep using global `ZVec` — both work + - Added `tests/test_namespace_bc.phpt` to verify both namespace styles + - Removed `classmap` from `composer.json` — PSR-4 autoloading now handles everything ### Deprecated -- **SMELL-009: The `$reranker` parameter on `query()` and `queryFp64()` is deprecated** (#90) - - Passing `$reranker` to `query()` now triggers `E_USER_DEPRECATED` and delegates to `queryWithReranker()` - - Existing code continues to work (backward compatible) but should migrate to `queryWithReranker()` +- **SMELL-013: Global namespace class names are deprecated** (#94) + - Use `use CrazyGoat\ZVec\ZVec;` instead of global `ZVec` + - Global aliases remain for backward compatibility but may be removed in a future major version ### Changed diff --git a/composer.json b/composer.json index f3ffa42..9c03ab4 100644 --- a/composer.json +++ b/composer.json @@ -15,10 +15,7 @@ "autoload": { "psr-4": { "CrazyGoat\\ZVec\\": "src/" - }, - "classmap": [ - "src/" - ] + } }, "bin": [ "bin/zvec-install" diff --git a/src/ZVec.php b/src/ZVec.php index ed08342..db786e7 100644 --- a/src/ZVec.php +++ b/src/ZVec.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; require_once __DIR__ . '/ZVecException.php'; @@ -14,6 +18,10 @@ require_once __DIR__ . '/ZVecGroupByVectorQuery.php'; require_once __DIR__ . '/ZVecSchema.php'; require_once __DIR__ . '/ZVecDoc.php'; +require_once __DIR__ . '/ZVecReRanker.php'; +require_once __DIR__ . '/ZVecRerankedDoc.php'; +require_once __DIR__ . '/ZVecRrfReRanker.php'; +require_once __DIR__ . '/ZVecWeightedReRanker.php'; class ZVec { @@ -1644,3 +1652,20 @@ public function getFieldSchema(string $fieldName): ZVecFieldSchema return new ZVecFieldSchema($out); } } + +// Backward-compatible aliases for global namespace usage +class_alias(ZVec::class, 'ZVec'); +class_alias(ZVecException::class, 'ZVecException'); +class_alias(ZVecSchema::class, 'ZVecSchema'); +class_alias(ZVecDoc::class, 'ZVecDoc'); +class_alias(ZVecCollectionOptions::class, 'ZVecCollectionOptions'); +class_alias(ZVecCollectionStats::class, 'ZVecCollectionStats'); +class_alias(ZVecFieldSchema::class, 'ZVecFieldSchema'); +class_alias(ZVecIndexParams::class, 'ZVecIndexParams'); +\class_alias(ZVecQueryInterface::class, 'ZVecQueryInterface'); +class_alias(ZVecVectorQuery::class, 'ZVecVectorQuery'); +class_alias(ZVecGroupByVectorQuery::class, 'ZVecGroupByVectorQuery'); +\class_alias(ZVecReRanker::class, 'ZVecReRanker'); +class_alias(ZVecRerankedDoc::class, 'ZVecRerankedDoc'); +class_alias(ZVecRrfReRanker::class, 'ZVecRrfReRanker'); +class_alias(ZVecWeightedReRanker::class, 'ZVecWeightedReRanker'); diff --git a/src/ZVecCollectionOptions.php b/src/ZVecCollectionOptions.php index 3b7e95a..35a415b 100644 --- a/src/ZVecCollectionOptions.php +++ b/src/ZVecCollectionOptions.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; class ZVecCollectionOptions diff --git a/src/ZVecCollectionStats.php b/src/ZVecCollectionStats.php index ccad2cd..b29ddc2 100644 --- a/src/ZVecCollectionStats.php +++ b/src/ZVecCollectionStats.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecCollectionStats diff --git a/src/ZVecDoc.php b/src/ZVecDoc.php index 44d0895..4dcdd6f 100644 --- a/src/ZVecDoc.php +++ b/src/ZVecDoc.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecDoc diff --git a/src/ZVecException.php b/src/ZVecException.php index 8f8529a..b615532 100644 --- a/src/ZVecException.php +++ b/src/ZVecException.php @@ -2,6 +2,11 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use RuntimeException; +use Throwable; + if (extension_loaded('zvec')) return; class ZVecException extends RuntimeException diff --git a/src/ZVecFieldSchema.php b/src/ZVecFieldSchema.php index 2f81350..b10dbe2 100644 --- a/src/ZVecFieldSchema.php +++ b/src/ZVecFieldSchema.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecFieldSchema diff --git a/src/ZVecGroupByVectorQuery.php b/src/ZVecGroupByVectorQuery.php index e0a6d31..8ac3e38 100644 --- a/src/ZVecGroupByVectorQuery.php +++ b/src/ZVecGroupByVectorQuery.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecGroupByVectorQuery implements ZVecQueryInterface diff --git a/src/ZVecIndexParams.php b/src/ZVecIndexParams.php index 23280fb..fb6cdc5 100644 --- a/src/ZVecIndexParams.php +++ b/src/ZVecIndexParams.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecIndexParams diff --git a/src/ZVecQueryInterface.php b/src/ZVecQueryInterface.php index f95447e..7644994 100644 --- a/src/ZVecQueryInterface.php +++ b/src/ZVecQueryInterface.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; interface ZVecQueryInterface diff --git a/src/ZVecReRanker.php b/src/ZVecReRanker.php index 8623d74..c1fdfa2 100644 --- a/src/ZVecReRanker.php +++ b/src/ZVecReRanker.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; /** diff --git a/src/ZVecRerankedDoc.php b/src/ZVecRerankedDoc.php index 24bca47..6988e31 100644 --- a/src/ZVecRerankedDoc.php +++ b/src/ZVecRerankedDoc.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; /** diff --git a/src/ZVecRrfReRanker.php b/src/ZVecRrfReRanker.php index 521ca84..03a730f 100644 --- a/src/ZVecRrfReRanker.php +++ b/src/ZVecRrfReRanker.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; require_once __DIR__ . '/ZVecReRanker.php'; diff --git a/src/ZVecSchema.php b/src/ZVecSchema.php index 53f3a11..778ed0a 100644 --- a/src/ZVecSchema.php +++ b/src/ZVecSchema.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecSchema diff --git a/src/ZVecVectorQuery.php b/src/ZVecVectorQuery.php index fa0bb0d..0912a13 100644 --- a/src/ZVecVectorQuery.php +++ b/src/ZVecVectorQuery.php @@ -2,6 +2,10 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + +use FFI; + if (extension_loaded('zvec')) return; class ZVecVectorQuery implements ZVecQueryInterface diff --git a/src/ZVecWeightedReRanker.php b/src/ZVecWeightedReRanker.php index 8b370fc..22d4145 100644 --- a/src/ZVecWeightedReRanker.php +++ b/src/ZVecWeightedReRanker.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; require_once __DIR__ . '/ZVec.php'; diff --git a/src/embeddings/EmbeddingInterfaces.php b/src/embeddings/EmbeddingInterfaces.php index 0563939..4e663c7 100644 --- a/src/embeddings/EmbeddingInterfaces.php +++ b/src/embeddings/EmbeddingInterfaces.php @@ -2,8 +2,12 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; +require_once __DIR__ . '/../ZVecException.php'; + /** * Interface for dense embedding functions. * Converts text input into dense vector representations. diff --git a/src/embeddings/OpenAIDenseEmbedding.php b/src/embeddings/OpenAIDenseEmbedding.php index 36dc641..455e0fe 100644 --- a/src/embeddings/OpenAIDenseEmbedding.php +++ b/src/embeddings/OpenAIDenseEmbedding.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; require_once __DIR__ . '/EmbeddingInterfaces.php'; diff --git a/src/embeddings/QwenDenseEmbedding.php b/src/embeddings/QwenDenseEmbedding.php index 89f3250..af95ed9 100644 --- a/src/embeddings/QwenDenseEmbedding.php +++ b/src/embeddings/QwenDenseEmbedding.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace CrazyGoat\ZVec; + if (extension_loaded('zvec')) return; require_once __DIR__ . '/EmbeddingInterfaces.php'; diff --git a/tests/test_embeddings_integration.phpt b/tests/test_embeddings_integration.phpt index c331cf3..edb2d35 100644 --- a/tests/test_embeddings_integration.phpt +++ b/tests/test_embeddings_integration.phpt @@ -6,6 +6,10 @@ Extensions: Embedding Functions - Integration with ZVec implementsInterface('DenseEmbeddingFunction')) { + $reflection = new ReflectionClass(OpenAIDenseEmbedding::class); + if ($reflection->implementsInterface(DenseEmbeddingFunction::class)) { echo "PASS: OpenAIDenseEmbedding implements DenseEmbeddingFunction\n"; } else { echo "FAIL: OpenAIDenseEmbedding does not implement DenseEmbeddingFunction\n"; exit(1); } - if ($reflection->isSubclassOf('ApiEmbeddingFunction')) { + if ($reflection->isSubclassOf(ApiEmbeddingFunction::class)) { echo "PASS: OpenAIDenseEmbedding extends ApiEmbeddingFunction\n"; } else { echo "FAIL: OpenAIDenseEmbedding does not extend ApiEmbeddingFunction\n"; @@ -49,18 +55,18 @@ if (class_exists('OpenAIDenseEmbedding')) { } // Test 3: Verify QwenDenseEmbedding class exists and implements interface -if (class_exists('QwenDenseEmbedding')) { +if (class_exists(QwenDenseEmbedding::class)) { echo "PASS: QwenDenseEmbedding class exists\n"; - $reflection = new ReflectionClass('QwenDenseEmbedding'); - if ($reflection->implementsInterface('DenseEmbeddingFunction')) { + $reflection = new ReflectionClass(QwenDenseEmbedding::class); + if ($reflection->implementsInterface(DenseEmbeddingFunction::class)) { echo "PASS: QwenDenseEmbedding implements DenseEmbeddingFunction\n"; } else { echo "FAIL: QwenDenseEmbedding does not implement DenseEmbeddingFunction\n"; exit(1); } - if ($reflection->isSubclassOf('ApiEmbeddingFunction')) { + if ($reflection->isSubclassOf(ApiEmbeddingFunction::class)) { echo "PASS: QwenDenseEmbedding extends ApiEmbeddingFunction\n"; } else { echo "FAIL: QwenDenseEmbedding does not extend ApiEmbeddingFunction\n"; @@ -72,7 +78,7 @@ if (class_exists('QwenDenseEmbedding')) { } // Test 4: Verify constants -$openaiReflection = new ReflectionClass('OpenAIDenseEmbedding'); +$openaiReflection = new ReflectionClass(OpenAIDenseEmbedding::class); $constants = $openaiReflection->getConstants(); $expectedConstants = ['MODEL_SMALL', 'MODEL_LARGE', 'MODEL_ADA']; @@ -85,7 +91,7 @@ foreach ($expectedConstants as $const) { } } -$qwenReflection = new ReflectionClass('QwenDenseEmbedding'); +$qwenReflection = new ReflectionClass(QwenDenseEmbedding::class); $constants = $qwenReflection->getConstants(); $expectedConstants = ['MODEL_V4', 'MODEL_V3', 'MODEL_V2', 'MODEL_V1']; diff --git a/tests/test_namespace_bc.phpt b/tests/test_namespace_bc.phpt new file mode 100644 index 0000000..af13985 --- /dev/null +++ b/tests/test_namespace_bc.phpt @@ -0,0 +1,105 @@ +--TEST-- +Namespace: PSR-4 namespace with backward-compatible global aliases +--SKIPIF-- + +--FILE-- + +--EXPECT-- +--- Global namespace (BC aliases) --- +ZVec: OK +ZVecException: OK +ZVecSchema: OK +ZVecDoc: OK +ZVecCollectionOptions: OK +ZVecCollectionStats: OK +ZVecFieldSchema: OK +ZVecIndexParams: OK +ZVecQueryInterface: OK +ZVecVectorQuery: OK +ZVecGroupByVectorQuery: OK +ZVecReRanker: OK +ZVecRerankedDoc: OK +ZVecRrfReRanker: OK +ZVecWeightedReRanker: OK + +--- PSR-4 namespace --- +CrazyGoat\ZVec\ZVec: OK +CrazyGoat\ZVec\ZVecException: OK +CrazyGoat\ZVec\ZVecSchema: OK +CrazyGoat\ZVec\ZVecDoc: OK +CrazyGoat\ZVec\ZVecCollectionOptions: OK +CrazyGoat\ZVec\ZVecCollectionStats: OK +CrazyGoat\ZVec\ZVecFieldSchema: OK +CrazyGoat\ZVec\ZVecIndexParams: OK +CrazyGoat\ZVec\ZVecQueryInterface: OK +CrazyGoat\ZVec\ZVecVectorQuery: OK +CrazyGoat\ZVec\ZVecGroupByVectorQuery: OK +CrazyGoat\ZVec\ZVecReRanker: OK +CrazyGoat\ZVec\ZVecRerankedDoc: OK +CrazyGoat\ZVec\ZVecRrfReRanker: OK +CrazyGoat\ZVec\ZVecWeightedReRanker: OK + +--- Alias instanceof check --- +Global ZVecSchema instanceof: OK +Namespaced ZVecSchema instanceof: OK + +--- Namespace usage with use statement --- +use ZVec: OK +use ZVecSchema: OK +use ZVecDoc: OK + +All namespace tests passed! From ef3a358c8699e179863a713adc76fc0841c1302d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C5=82as=20Piotr?= Date: Sat, 30 May 2026 13:11:06 +0200 Subject: [PATCH 2/3] fix: restore SMELL-009 changelog entries accidentally removed in previous commit --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d503d2..1e80440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use `CrazyGoat\ZVec\ZVec` with `use` statement or keep using global `ZVec` — both work - Added `tests/test_namespace_bc.phpt` to verify both namespace styles - Removed `classmap` from `composer.json` — PSR-4 autoloading now handles everything +- **SMELL-009: Added `queryWithReranker()` method for type-safe reranked queries** (#90) + - New `queryWithReranker()` method on `ZVec` returns `ZVecRerankedDoc[]` — static analysis tools can infer the correct return type + - Supports `string|ZVecVectorQuery` as field name, with all existing query parameters + - Routes FP64 queries via internal `queryWithRerankerFp64()` helper ### Deprecated - **SMELL-013: Global namespace class names are deprecated** (#94) - Use `use CrazyGoat\ZVec\ZVec;` instead of global `ZVec` - Global aliases remain for backward compatibility but may be removed in a future major version +- **SMELL-009: The `$reranker` parameter on `query()` and `queryFp64()` is deprecated** (#90) + - Passing `$reranker` to `query()` now triggers `E_USER_DEPRECATED` and delegates to `queryWithReranker()` + - Existing code continues to work (backward compatible) but should migrate to `queryWithReranker()` ### Changed From 0328ac37ef528cdc4b77f65cb2cfc5a688cdc4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C5=82as=20Piotr?= Date: Sat, 30 May 2026 13:15:31 +0200 Subject: [PATCH 3/3] fix: skip embedding tests in native extension env (classes defined in different namespace) --- tests/test_embeddings_integration.phpt | 2 +- tests/test_embeddings_interfaces.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_embeddings_integration.phpt b/tests/test_embeddings_integration.phpt index edb2d35..355dfca 100644 --- a/tests/test_embeddings_integration.phpt +++ b/tests/test_embeddings_integration.phpt @@ -1,7 +1,7 @@ --TEST-- Extensions: Embedding Functions - Integration with ZVec --SKIPIF-- - + --FILE-- + --FILE--