Skip to content

Commit

Permalink
Add write protection with examples and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
seba-aln committed Feb 9, 2025
1 parent c476491 commit e37b681
Show file tree
Hide file tree
Showing 16 changed files with 525 additions and 142 deletions.
91 changes: 91 additions & 0 deletions examples/cli/partial_updates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

set_time_limit(0);

require('../../vendor/autoload.php');

use PubNub\PNConfiguration;
use PubNub\PubNub;

$pnconf = new PNConfiguration();
$pnconf->setPublishKey("demo");
$pnconf->setSubscribeKey("demo");
$pnconf->setUuid("example");

$pubnub = new PubNub($pnconf);

$channel = "demo_example";

print("We're setting the channel's $channel additional info.\n");
print("\tTo exit type '/exit'\n");
print("\tTo show the current object type '/show'\n");
print("\tTo show this help type '/help'\n");

print("Enter the channel name: ");
$name = trim(fgets(STDIN));

print("Enter the channel description: ");
$description = trim(fgets(STDIN));

// Set channel metadata
$pubnub->setChannelMetadata()
->channel($channel)
->meta([
"name" => $name,
"description" => $description,
])
->sync();

print("The channel has been created with name and description.\n");

while (true) {
// Fetch current channel metadata
$response = $pubnub->getChannelMetadata()
->channel($channel)
->sync();

print("Enter the field name: ");
$fieldName = trim(fgets(STDIN));

if ($fieldName === '/exit') {
exit();
} elseif ($fieldName === '/show') {
print_r($response);
continue;
} elseif ($fieldName === '/help') {
print("\tTo exit type '/exit'\n");
print("\tTo show the current object type '/show'\n");
print("\tTo show this help type '/help'\n");
continue;
}

print("Enter the field value: ");
$fieldValue = trim(fgets(STDIN));

// Prepare custom fields
$custom = (array)$response->getCustom();

if (isset($custom[$fieldName])) {
print("Field $fieldName already has a value. Overwrite? (y/n): ");
$confirmation = trim(fgets(STDIN));

if (strtolower($confirmation) !== 'y') {
print("Object will not be updated.\n");
continue;
}
}

// Update custom field
$custom[$fieldName] = $fieldValue;

// Writing the updated object back to the server
$pubnub->setChannelMetadata()
->channel($channel)
->meta([
"name" => $response->getName(),
"description" => $response->getDescription(),
"custom" => $custom,
])
->sync();
print("Object has been updated.\n");
}
Binary file added examples/cli/pn.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions examples/cli/using_etag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

require_once 'vendor/autoload.php';

use PubNub\Exceptions\PubNubServerException;
use PubNub\PubNub;
use PubNub\PNConfiguration;

$config = new PNConfiguration();
$config->setPublishKey('demo');
$config->setSubscribeKey('demo');
$config->setUuid("example");

$config_2 = clone $config;
$config_2->setUuid("example_2");

$pubnub = new PubNub($config);
$pubnub_2 = new PubNub($config_2);

$sample_user = [
"uuid" => "SampleUser",
"name" => "John Doe",
"email" => "jd@example.com",
"custom" => ["age" => 42, "address" => "123 Main St."]
];

// One client creates a metadata for the user "SampleUser" and successfully writes it to the server.
$set_result = $pubnub->setUUIDMetadata()->uuid("SampleUser")->meta($sample_user)->sync();

// We store the eTag for the user for further updates.
$original_e_tag = $set_result->getETag();

print("We receive the eTag for the user: $original_e_tag" . PHP_EOL);

// Another client sets the user meta with the same UUID but different data.
$overwrite_result = $pubnub_2->setUUIDMetadata()->uuid("SampleUser")->meta(["name" => "Jane Doe"])->sync();
$new_e_tag = $overwrite_result->getETag();

// We can verify that there is a new eTag for the user.
print(
"After overwrite there's a new eTag: $original_e_tag === $new_e_tag? "
. ($original_e_tag === $new_e_tag ? "true" : "false") . PHP_EOL
);

// We modify the user and try to update it.
$updated_user = array_merge($sample_user, ["custom" => ["age" => 43, "address" => "321 Other St."]]);

try {
$update_result = $pubnub->setUUIDMetadata()
->uuid("SampleUser")
->meta($updated_user)
->ifMatchesETag($original_e_tag)
->sync();
print_r($update_result);
} catch (PubNubServerException $e) {
if ($e->getStatusCode() === 412) {
print(
"Update failed because eTag mismatch: " . $e->getServerErrorMessage()
. "\nHTTP Status Code: " . $e->getStatusCode() . PHP_EOL
);
} else {
print( "Unexpected error: " . $e->getMessage() . PHP_EOL);
}
} catch (Exception $e) {
echo "Unexpected error: " . $e->getMessage() . PHP_EOL;
}
6 changes: 5 additions & 1 deletion src/PubNub/Endpoints/Endpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PubNub\Endpoints;

use Exception;
use PubNub\Enums\PNOperationType;
use PubNub\Enums\PNHttpMethod;
use PubNub\Enums\PNStatusCategory;
Expand Down Expand Up @@ -32,6 +33,9 @@ abstract class Endpoint
protected int $endpointOperationType;
protected string $endpointName;

/** @var string[] */
protected array $customHeaders = [];

protected const RESPONSE_IS_JSON = true;

/** @var PubNub */
Expand Down Expand Up @@ -178,7 +182,7 @@ protected function defaultHeaders()

protected function customHeaders()
{
return [];
return $this->customHeaders;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/PubNub/Endpoints/Objects/Channel/SetChannelMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PubNub\Endpoints\Objects\Channel;

use PubNub\Endpoints\Endpoint;
use PubNub\Endpoints\Objects\MatchesETagTrait;
use PubNub\Enums\PNHttpMethod;
use PubNub\Enums\PNOperationType;
use PubNub\Exceptions\PubNubValidationException;
Expand All @@ -11,6 +12,8 @@

class SetChannelMetadata extends Endpoint
{
use MatchesETagTrait;

protected const PATH = "/v2/objects/%s/channels/%s";

/** @var string */
Expand Down
20 changes: 20 additions & 0 deletions src/PubNub/Endpoints/Objects/MatchesETagTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace PubNub\Endpoints\Objects;

trait MatchesETagTrait
{
protected ?string $eTag = null;
protected array $customHeaders = [];

/**
* @param string $eTag
* @return $this
*/
public function ifMatchesETag(string $eTag): self
{
$this->eTag = $eTag;
$this->customHeaders['If-Match'] = $eTag;
return $this;
}
}
3 changes: 1 addition & 2 deletions src/PubNub/Endpoints/Objects/ObjectsCollectionEndpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,4 @@ public function sort($sort)

return $this;
}

}
}
3 changes: 3 additions & 0 deletions src/PubNub/Endpoints/Objects/UUID/SetUUIDMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PubNub\Endpoints\Objects\UUID;

use PubNub\Endpoints\Endpoint;
use PubNub\Endpoints\Objects\MatchesETagTrait;
use PubNub\Enums\PNHttpMethod;
use PubNub\Enums\PNOperationType;
use PubNub\Exceptions\PubNubValidationException;
Expand All @@ -11,6 +12,8 @@

class SetUUIDMetadata extends Endpoint
{
use MatchesETagTrait;

protected const PATH = "/v2/objects/%s/uuids/%s";

/** @var string */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace PubNub\Models\Consumer\Objects\Channel;


class PNGetAllChannelMetadataResult
{
/** @var integer */
Expand All @@ -17,19 +16,24 @@ class PNGetAllChannelMetadataResult
/** @var array */
protected $data;

/** @var ?string */
protected $eTag;

/**
* PNGetAllChannelMetadataResult constructor.
* @param integer $totalCount
* @param string $prev
* @param string $next
* @param array $data
* @param $string $eTag
*/
function __construct($totalCount, $prev, $next, $data)
public function __construct($totalCount, $prev, $next, $data, ?string $eTag = null)
{
$this->totalCount = $totalCount;
$this->prev = $prev;
$this->next = $next;
$this->data = $data;
$this->eTag = $eTag;
}

/**
Expand Down Expand Up @@ -64,15 +68,28 @@ public function getData()
return $this->data;
}

/**
* @return ?string
*/
public function getETag(): ?string
{
return $this->eTag;
}

public function __toString()
{
if (!empty($data))
{
$data_string = json_encode($data);
if (!empty($data)) {
$data_string = json_encode($data);
}

return sprintf("totalCount: %s, prev: %s, next: %s, data: %s",
$this->totalCount, $this->prev, $this->next, $data_string);
return sprintf(
"totalCount: %s, prev: %s, next: %s, data: %s, eTag: %s",
$this->totalCount,
$this->prev,
$this->next,
$data_string,
$this->eTag
);
}

/**
Expand All @@ -85,32 +102,32 @@ public static function fromPayload(array $payload)
$prev = null;
$next = null;
$data = null;
$eTag = null;

if (array_key_exists("totalCount", $payload))
{
if (array_key_exists("totalCount", $payload)) {
$totalCount = $payload["totalCount"];
}

if (array_key_exists("prev", $payload))
{
if (array_key_exists("prev", $payload)) {
$prev = $payload["prev"];
}

if (array_key_exists("next", $payload))
{
if (array_key_exists("next", $payload)) {
$next = $payload["next"];
}

if (array_key_exists("data", $payload))
{
if (array_key_exists("data", $payload)) {
$data = [];

foreach($payload["data"] as $value)
{
foreach ($payload["data"] as $value) {
array_push($data, PNGetChannelMetadataResult::fromPayload([ "data" => $value ]));
}
}

return new PNGetAllChannelMetadataResult($totalCount, $prev, $next, $data);
if (array_key_exists("eTag", $payload)) {
$eTag = $payload["eTag"];
}

return new PNGetAllChannelMetadataResult($totalCount, $prev, $next, $data, $eTag);
}
}
}
Loading

0 comments on commit e37b681

Please sign in to comment.