diff --git a/MIGRATION_FROM_3_x.md b/MIGRATION_FROM_3_x.md new file mode 100644 index 00000000..25111518 --- /dev/null +++ b/MIGRATION_FROM_3_x.md @@ -0,0 +1,598 @@ +# Migration Guide for 3.x Branch + +Couchbase PHP SDK v4 brought a lot of improvements to the API. Some of the changes unfortunately break +compatibility with v3. This guide helps upgrade applications, and highlights the most important +differences. + +API Reference: https://docs.couchbase.com/sdk-api/couchbase-php-client + +## Components Overview + +The following diagram illustrates the architecture of PHP SDK v3: + +```mermaid +block-beta + columns 3 + + developer("Developer") + block:user:2 + columns 2 + Applications + ODMs + Frameworks + Integrations + end + + low_level("Extension\n(exposes high-level API)") + extension("PHP Binary Extension\n(PECL: couchbase)"):2 + + dependencies("System Dependencies") + block:deps:2 + lcb("libcouchbase") + libevent("libevent") + boring_ssl("OpenSSL") + end +``` + + +The following diagram illustrates the architecture of PHP SDK v4: + +```mermaid +block-beta + columns 3 + + developer("Developer") + block:user:2 + columns 2 + Applications + ODMs + Frameworks + Integrations + end + + high_level("High-Level API") + library("PHP Library\n(Composer: couchbase/couchbase)"):2 + + low_level("Extension") + extension("PHP Binary Extension\n(PECL: couchbase)"):2 + + dependencies("Static Dependencies") + block:deps:2 + cxx_core("C++ Core SDK") + boring_ssl("BoringSSL") + end +``` + +So let us point out immediate differences: + +1. SDKv4 uses Composer to deliver high-level API classes, while SDKv3 defines everything in the + extension. While it adds an extra step for the developer, it really simplifies maintenance, reduces + potential memory issues, allows to keep documentation consistent and improves IDE integration. + + It is really easy add classes to the application using Composer. + ```bash + composer require couchbase/couchbase + ``` + + the resulting `composer.json` needs only two lines to be added: + ```diff + diff --git i/composer.json w/composer.json + index b743a66..88e69da 100644 + --- i/composer.json + +++ w/composer.json + @@ -24,6 +24,8 @@ + "require": { + "php": "^7.4 || ^8.0", + "ext-json": "*", + + "ext-couchbase": "^4.2", + + "couchbase/couchbase": "^4.2", + "monolog/monolog": "^2.3", + "php-di/php-di": "^6.3", + "slim/psr7": "^1.5", + ``` + + In case Composer cannot be used, we also have autoloader script `Couchbase\autoload.php`, + that sets up a hook to resolve and automatically require SDK classes, but to use this autoloader, + the headers must be installed somewhere in the PHP include path, which could be found using one of + the following commands, or in the `phpinfo()` output. + ``` + $ pecl config-get php_dir + /usr/share/pear + ``` + ``` + $ php -r 'printf("%s\n", ini_get("include_path"));' + .:/usr/share/pear:/usr/share/php + ``` + +2. Another important observation is that SDKv4 does not expect any system libraries or headers to be + installed to work and uses a statically compiled core implementation (just like all other + wrappers). Additionally, it statically links the TLS library to the extension, which, again, simplifies + deployment on the Windows platform. + +## Transcoder API + +SDKv3 used a non-standard way of specifying an encoder and decoder for the document objects for the KV API. +This API was inherited from SDKv2, where the developer needed to specify a decoder or +encoder function in the options. SDKv4 fixes this and instead defines the `\Couchbase\Transcoder` +interface which encapsulates all the logic. Additionally, it provides four implementations of it +and uses `\Couchbase\JsonTranscoder` by default. + +Let's say we want to read/write documents without any conversion, just as a binary streams. To do so, +we would need to override the transcoder, because otherwise our byte strings will be serialized as JSON +strings. + +Here is how it is done with SDKv3: + +```php +$options = new \Couchbase\ClusterOptions(); +$options->credentials("Administrator", "password"); +$cluster = new \Couchbase\Cluster("couchbase://localhost", $options); +$collection = $cluster->bucket("default")->defaultCollection(); + +$passThruDecoder = function($bytes, $flags, $datatype) { + // do not check anything just return bytes as passed by core + return $bytes; +}; + +$passThruEncoder = function($value) { + // do not do conversion, and return zeroes for datatype and flags + return [$value, 0x00, 0x00]; +}; + + +// Mutation operations in SDKv3 explicitly ask for encoder callable +$options = new \Couchbase\UpsertOptions(); +$options->encoder($passThruEncoder); +$collection->upsert("foo", "BINARY DATA: \xDE\xAD\xBE\xEF", $options); + +// Retrieval operation in SDKv3 only allow decoder callable +$options = new \Couchbase\GetOptions(); +$options->decoder($passThruDecoder); +$res = $collection->get("foo", $options); +var_dump($res->content()); +``` + +With SDKv4 we ship `\Couchbase\RawBinaryTranscoder`, which could be reimplemented as the following: + +```php +class PassThruTranscoder implements \Couchbase\Transcoder +{ + public function encode($value): array + { + return [ + $value, + (new \Couchbase\TranscoderFlags(\Couchbase\TranscoderFlags::DATA_FORMAT_BINARY))->encode(), + ]; + } + + public function decode(string $bytes, int $flags) + { + return $bytes; + } +} + +// RawBinaryTranscoder like any other implementation has static method getInstance() returning +// singleton object. +$passThruTranscoder = new PassThruTranscoder(); + +$options = new \Couchbase\UpsertOptions(); +$options->transcoder($passThruTranscoder); +$collection->upsert("foo", "BINARY DATA: \xDE\xAD\xBE\xEF", $options); + +$options = new \Couchbase\GetOptions(); +$options->transcoder($passThruTranscoder); +$res = $collection->get("foo", $options); +var_dump($res->content()); +``` + +## Error Handling + +SDKv4 moved exceptions into the `\Couchbase\Exception` namespace, so if the application used to catch +and handle exceptions from the SDK, those places should update to use the new names. + +
SDKv3 exception | +SDKv4 exception | +
---|---|
\Couchbase\AuthenticationException | \Couchbase\Exception\AuthenticationFailureException |
\Couchbase\BadInputException | \Couchbase\Exception\InvalidArgumentException |
+
|
+ \Couchbase\Exception\BucketNotFoundException | +
\Couchbase\CasMismatchException | \Couchbase\Exception\CasMismatchException |
\Couchbase\CollectionMissingException | \Couchbase\Exception\CollectionNotFoundException |
\Couchbase\DurabilityException | +
+ One of the more specific exceptions.
+
|
+
\Couchbase\DmlFailureException | +
+ The SDK will detect the underlying error that caused the query to fail and throw
+ the specific exception.
+
|
+
\Couchbase\DocumentNotFoundException | \Couchbase\Exception\DocumentNotFoundException |
\Couchbase\IndexFailureException | \Couchbase\Exception\IndexFailureException |
\Couchbase\IndexNotFoundException | \Couchbase\Exception\IndexNotFoundException |
\Couchbase\InvalidRangeException | \Couchbase\Exception\DeltaInvalidException |
\Couchbase\KeyDeletedException | Removed |
\Couchbase\KeyExistsException | \Couchbase\Exception\DocumentExistsException |
\Couchbase\KeyLockedException | \Couchbase\Exception\DocumentLockedException |
\Couchbase\ParsingFailureException | \Couchbase\Exception\ParsingFailureException |
\Couchbase\PartialViewException | Removed |
\Couchbase\PathExistsException | \Couchbase\Exception\PathExistsException |
\Couchbase\PathNotFoundException | \Couchbase\Exception\PathNotFoundException |
\Couchbase\PlanningFailureException | \Couchbase\Exception\PlanningFailureException |
\Couchbase\PreparedStatementFailureException | \Couchbase\Exception\PreparedStatementFailureException |
+
|
+ Rate and Quota limit exceptions redesigned, and the SDK will not use them. | +
\Couchbase\RequestCanceledException | \Couchbase\Exception\RequestCanceledException |
\Couchbase\ScopeMissingException | \Couchbase\Exception\ScopeNotFoundException |
\Couchbase\ServiceNotAvailableException | \Couchbase\Exception\ServiceNotAvailableException |
\Couchbase\TempFailException | \Couchbase\Exception\TemporaryFailureException |
\Couchbase\TimeoutException | \Couchbase\Exception\TimeoutException |
\Couchbase\ValueTooBigException | \Couchbase\Exception\ValueTooLargeException |
+
|
+
+ All generic exceptions mapped to \Couchbase\Exception\CouchbaseException or to one
+ of the new, more specific exceptions.
+
|
+
SDKv3 | +SDKv4 | +
---|---|
\Couchbase\ViewScanConsistency::NOT_BOUNDED (0) | +\Couchbase\ViewConsistency::NOT_BOUNDED ("notBounded") | +
\Couchbase\ViewScanConsistency::REQUEST_PLUS (1) | +\Couchbase\ViewConsistency::REQUEST_PLUS ("requestPlus") | +
\Couchbase\ViewScanConsistency::UPDATE_AFTER (2) | +\Couchbase\ViewConsistency::UPDATE_AFTER ("updateAfter") | +
SDKv3 | +SDKv4 | +
---|---|
\Couchbase\ViewOrdering::ASCENDING (0) | +\Couchbase\ViewOrdering::ASCENDING ("ascending") | +
\Couchbase\ViewOrdering::DESCENDING (1) | +\Couchbase\ViewOrdering::DESCENDING ("descending") | +
SDKv3 | +SDKv4 | +
---|---|
\Couchbase\QueryScanConsistency::NOT_BOUNDED (1) | +\Couchbase\QueryScanConsistency::NOT_BOUNDED ("notBounded") | +
\Couchbase\QueryScanConsistency::REQUEST_PLUS (2) | +\Couchbase\QueryScanConsistency::REQUEST_PLUS ("requestPlus") | +
\Couchbase\QueryScanConsistency::STATEMENT_PLUS (3) | +Removed | +
SDKv3 | +SDKv4 | +
---|---|
\Couchbase\QueryProfile::OFF (1) | +\Couchbase\QueryProfile::OFF ("off") | +
\Couchbase\QueryProfile::PHASES (2) | +\Couchbase\QueryProfile::PHASES ("phases") | +
\Couchbase\QueryProfile::TIMINGS (3) | +\Couchbase\QueryProfile::TIMINGS ("timings") | +
SDKv3 | +SDKv4 | +
---|---|
"" | +\Couchbase\AnalyticsScanConsistency::NOT_BOUNDED ("notBounded") | +
"request_plus" | +\Couchbase\AnalyticsScanConsistency::REQUEST_PLUS ("requestPlus") | +
SDKv3 (in microseconds) | +SDKv4 (in milliseconds) | +
---|---|
Bucket::operationTimeout() | +ClusterOptions::keyValueTimeout() | +
Bucket::viewTimeout() | +ClusterOptions::viewTimeout() | +
Bucket::n1qlTimeout() | +ClusterOptions::queryTimeout() | +
Bucket::durabilityInterval() | +Removed | +
Bucket::durabilityTimeout() | +ClusterOptions::keyValueDurableTimeout() | +
Bucket::configTimeout() | +ClusterOptions::bootstrapTimeout() | +
Bucket::configDelay() | +Removed | +
Bucket::configNodeTimeout() | +Removed | +
Bucket::htconfigIdleTimeout() | +Removed | +
Bucket::configPollInterval() | +ClusterOptions::configPollInterval() | +
Bucket::httpTimeout() | +
+ One of the service-related should be used:
+
|
+
SDKv3 | +SDKv4 | +
---|---|
+ couchbase.log_level
+
|
+
+ couchbase.log_level
+
|
+
couchbase.encoder.format | +Removed | +
couchbase.encoder.compression | +Removed | +
couchbase.encoder.compression_threshold | +Removed | +
couchbase.encoder.compression_factor | +Removed | +
couchbase.decoder.json_arrays | +Removed | +
couchbase.pool.max_idle_time_sec | +couchbase.persistent_timeout | +
couchbase.allow_fallback_to_bucket_connection | +Removed | +