Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@ $customer = ChartMogul\Customer::retrieve($cus->uuid);
$tags = $customer->addTags("important", "Prio1");
```

**Add Tags Using Array (Alternative)**

```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);
$tagsArray = ["important", "Prio1", "enterprise"];
$tags = $customer->addTags($tagsArray);
```

**Add Tags to Customers with email**

```php
Expand All @@ -292,6 +300,14 @@ $customer = ChartMogul\Customer::retrieve($cus->uuid);
$tags = $customer->removeTags("important", "Prio1");
```

**Remove Tags Using Array (Alternative)**

```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);
$tagsToRemove = ["important", "Prio1"];
$tags = $customer->removeTags($tagsToRemove);
```

#### Custom Attributes

**Add Custom Attributes to a Customer**
Expand All @@ -304,6 +320,23 @@ $custom = $customer->addCustomAttributes(
);
```

**Add Custom Attributes Using Array (Alternative)**

```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);

// Build attributes array dynamically
$attributes = [];
if (!empty($channel)) {
$attributes[] = ['type' => 'String', 'key' => 'channel', 'value' => $channel];
}
if (!empty($age)) {
$attributes[] = ['type' => 'Integer', 'key' => 'age', 'value' => $age];
}

$custom = $customer->addCustomAttributes($attributes);
```


**Add Custom Attributes to Customers with email**

Expand All @@ -323,18 +356,40 @@ foreach ($customers->entries as $customer) {
```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);
$custom = $customer->updateCustomAttributes(
['channel' => 'Twitter'],
['age' => 18]
['type' => 'String', 'key' => 'channel', 'value' => 'Twitter'],
['type' => 'Integer', 'key' => 'age', 'value' => 18]
);
```

**Update Custom Attributes Using Array (Alternative)**

```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);
$attributeUpdates = [
['type' => 'String', 'key' => 'channel', 'value' => 'Twitter'],
['type' => 'Integer', 'key' => 'age', 'value' => 18]
];
$custom = $customer->updateCustomAttributes($attributeUpdates);
```

**Remove Custom Attributes from a Customer**

```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);
$tags = $customer->removeCustomAttributes("age", "channel");
```

**Remove Custom Attributes Using Array (Alternative)**

```php
$customer = ChartMogul\Customer::retrieve($cus->uuid);
$attributesToRemove = [
['key' => 'age'],
['key' => 'channel']
];
$tags = $customer->removeCustomAttributes($attributesToRemove);
```

**List Contacts from a customer**

```php
Expand Down Expand Up @@ -1005,6 +1060,7 @@ The library throws following Exceptions:
- `ChartMogul\Exceptions\ChartMogulException`
- `ChartMogul\Exceptions\ConfigurationException`
- `ChartMogul\Exceptions\ForbiddenException`
- `ChartMogul\Exceptions\NetworkException`
- `ChartMogul\Exceptions\NotFoundException`
- `ChartMogul\Exceptions\ResourceInvalidException`
- `ChartMogul\Exceptions\SchemaInvalidException`
Expand Down
123 changes: 111 additions & 12 deletions src/Customer.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,32 @@ public static function disconnectSubscriptions($customerUUID, array $data = [],
/**
* Add tags to a customer
*
* @param mixed $tags,...
* Supports both individual arguments and array input:
* - Individual args: $customer->addTags($tag1, $tag2, ...)
* - Array input: $customer->addTags($tagsArray)
*
* @param mixed $tags,... Individual tag strings OR array of tags
* @return array
*/
public function addTags($tags)
{
$args = func_get_args();

// Detect if first argument is an array of tags vs individual tag
if (count($args) === 1 && is_array($tags)) {
// New behavior: Array of tags passed
$tagsToSend = $tags;
} else {
// Existing behavior: Individual arguments (maintain backwards compatibility)
$tagsToSend = $args;
}

$result = $this->getClient()
->send(
'/v1/customers/'.$this->uuid.'/attributes/tags',
'POST',
[
'tags' => func_get_args()
'tags' => $tagsToSend
]
);

Expand All @@ -258,17 +273,32 @@ public function addTags($tags)
/**
* Remove Tags from a Customer
*
* @param mixed $tags,...
* Supports both individual arguments and array input:
* - Individual args: $customer->removeTags($tag1, $tag2, ...)
* - Array input: $customer->removeTags($tagsArray)
*
* @param mixed $tags,... Individual tag strings OR array of tags
* @return array
*/
public function removeTags($tags)
{
$args = func_get_args();

// Detect if first argument is an array of tags vs individual tag
if (count($args) === 1 && is_array($tags)) {
// New behavior: Array of tags passed
$tagsToSend = $tags;
} else {
// Existing behavior: Individual arguments (maintain backwards compatibility)
$tagsToSend = $args;
}

$result = $this->getClient()
->send(
'/v1/customers/'.$this->uuid.'/attributes/tags',
'DELETE',
[
'tags' => func_get_args()
'tags' => $tagsToSend
]
);

Expand All @@ -279,17 +309,33 @@ public function removeTags($tags)
/**
* Add Custom Attributes to a Customer
*
* @param mixed $custom,...
* Supports both individual arguments and array input:
* - Individual args: $customer->addCustomAttributes($attr1, $attr2, ...)
* - Array input: $customer->addCustomAttributes($attributesArray)
*
* @param mixed $custom,... Individual attribute objects OR array of attributes
* @return array
*/
public function addCustomAttributes($custom)
{
$args = func_get_args();

// Detect if first argument is an array of attributes vs individual attribute
$firstKey = array_key_first($custom);
if (count($args) === 1 && is_array($custom) && $firstKey !== null && is_array($custom[$firstKey])) {
// New behavior: Array of attribute objects passed
$attributesToSend = $custom;
} else {
// Existing behavior: Individual arguments (maintain backwards compatibility)
$attributesToSend = $args;
}

$result = $this->getClient()
->send(
'/v1/customers/'.$this->uuid.'/attributes/custom',
'POST',
[
'custom' => func_get_args()
'custom' => $attributesToSend
]
);

Expand All @@ -301,17 +347,33 @@ public function addCustomAttributes($custom)
/**
* Remove Custom Attributes from a Customer
*
* @param mixed $custom,...
* Supports both individual arguments and array input:
* - Individual args: $customer->removeCustomAttributes($attr1, $attr2, ...)
* - Array input: $customer->removeCustomAttributes($attributesArray)
*
* @param mixed $custom,... Individual attribute objects OR array of attributes
* @return array
*/
public function removeCustomAttributes($custom)
{
$args = func_get_args();

// Detect if first argument is an array of attributes vs individual attribute
$firstKey = array_key_first($custom);
if (count($args) === 1 && is_array($custom) && $firstKey !== null && is_array($custom[$firstKey])) {
// New behavior: Array of attribute objects passed
$attributesToSend = $custom;
} else {
// Existing behavior: Individual arguments (maintain backwards compatibility)
$attributesToSend = $args;
}

$result = $this->getClient()
->send(
'/v1/customers/'.$this->uuid.'/attributes/custom',
'DELETE',
[
'custom' => func_get_args()
'custom' => $attributesToSend
]
);

Expand All @@ -322,15 +384,52 @@ public function removeCustomAttributes($custom)
/**
* Update Custom Attributes of a Customer
*
* @param mixed $custom,...
* Supports multiple input formats:
* - Individual attribute arrays: $customer->updateCustomAttributes($attr1, $attr2, ...)
* - Array of attributes: $customer->updateCustomAttributes($attributesArray)
* - Single attribute array: $customer->updateCustomAttributes($singleAttribute)
*
* @param mixed $custom,... Individual attribute arrays OR array of attributes
* @return array
*/
public function updateCustomAttributes($custom)
{
$data = [];
foreach (func_get_args() as $value) {
$data = array_merge($data, $value);
$args = func_get_args();

// Handle different input formats
if (count($args) === 1) {
if (is_array($custom)) {
// Check if this is an array of attribute objects vs single attribute object
// An array of attributes will have sub-arrays as values
$firstKey = array_key_first($custom);
$isArrayOfAttributes = $firstKey !== null && is_array($custom[$firstKey]);

if ($isArrayOfAttributes) {
// Array of attribute objects
$data = [];
foreach ($custom as $attr) {
if (is_array($attr)) {
$data = array_merge($data, $attr);
}
}
} else {
// Single attribute object
$data = $custom;
}
} else {
// Single non-array argument (shouldn't happen but handle gracefully)
$data = [$custom];
}
} else {
// Multiple arguments - existing behavior
$data = [];
foreach ($args as $value) {
if (is_array($value)) {
$data = array_merge($data, $value);
}
}
}

$result = $this->getClient()
->send(
'/v1/customers/'.$this->uuid.'/attributes/custom',
Expand Down
23 changes: 23 additions & 0 deletions src/Exceptions/NetworkException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace ChartMogul\Exceptions;

/**
* Network-related exceptions for ChartMogul API requests.
*
* Thrown when network requests fail, timeout, or return null responses.
*/
class NetworkException extends ChartMogulException
{
/**
* NetworkException constructor.
*
* @param string $message Error message describing the network failure
* @param \Psr\Http\Message\ResponseInterface|null $response HTTP response if available
* @param \Throwable|null $previous Previous exception for chaining
*/
public function __construct(string $message, ?\Psr\Http\Message\ResponseInterface $response = null, ?\Throwable $previous = null)
{
parent::__construct($message, $response, $previous);
}
}
1 change: 1 addition & 0 deletions src/Http/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ function () use ($request) {
return $this->client->sendRequest($request);
}
);

return $this->handleResponse($response);
}

Expand Down
21 changes: 19 additions & 2 deletions src/Http/Retry.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,28 @@ protected function shouldRetry($attempt, $maxAttempts, ?ResponseInterface $respo
public function retry($callback)
{
if ($this->retries === 0) {
return $callback();
$result = $callback();
// Ensure we never return null for HTTP responses
if ($result === null) {
throw new \ChartMogul\Exceptions\NetworkException(
'Request failed with no response'
);
}
return $result;
}

$backoff = new Backoff($this->retries, new ExponentialStrategy(), 60 * 1000, true);
$backoff->setDecider($this);
return $backoff->run($callback);
$result = $backoff->run($callback);

// Validate result is not null after all retries
if ($result === null) {
throw new \ChartMogul\Exceptions\NetworkException(
'All retry attempts failed with no response'
);
}

return $result;
}

public function __invoke($attempt, $maxAttempts, $response, $exception)
Expand Down
Loading
Loading