Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added method to update person data #84

Merged
merged 10 commits into from
Aug 20, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added
- [Added method to update person data](https://github.com/5pm-HDH/churchtools-api/pull/84)

### Changed
- [refactor CTClient:](https://github.com/5pm-HDH/churchtools-api/pull/83) transform inheritance from GuzzleClient to composition-relation
Expand Down
36 changes: 35 additions & 1 deletion docs/PersonAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,38 @@
$personA = PersonRequest::whoami();
$events = $personA->requestEvents()?->get();

```
```

## Update a person's data

Use the setters of the person model to modify its data and utilize the
`PersonRequest::update(...)` method to send the new data to ChurchTools.

Follow this example:

```php
use CTApi\Requests\PersonRequest;

$person->setEmail('new-mail@example.com');

PersonRequest::update($person);

```

This will send all data of the person to the API and persists them.

If you know that only a specific set of attributes is changed, you can limit the
data sent to the API, by adding a whitelist of attributes.

```php
use CTApi\Requests\PersonRequest;

$person->setEmail('new-mail@example.com');
$person->setJob('This should not be persisted!');

PersonRequest::update($person, ['email']);

```

Now, only the e-mail will be sent to the API. This may be used to reduce
unnecessary traffic if you are going to do some bulk updates.
21 changes: 20 additions & 1 deletion docs/src/ressources/PersonAPI.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# PersonAPI

{{ \Tests\Unit\Docs\PersonRequestTest.testExampleCode }}
{{ \Tests\Unit\Docs\PersonRequestTest.testExampleCode }}

## Update a person's data

Use the setters of the person model to modify its data and utilize the
`PersonRequest::update(...)` method to send the new data to ChurchTools.

Follow this example:

{{ \Tests\Unit\Docs\PersonRequestTest.testUpdatePerson }}

This will send all data of the person to the API and persists them.

If you know that only a specific set of attributes is changed, you can limit the
data sent to the API, by adding a whitelist of attributes.

{{ \Tests\Unit\Docs\PersonRequestTest.testUpdatePersonSingleAttrbute }}

Now, only the e-mail will be sent to the API. This may be used to reduce
unnecessary traffic if you are going to do some bulk updates.
10 changes: 10 additions & 0 deletions src/CTClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ public function post($uri, array $options = []): ResponseInterface
}
}

public function patch($uri, array $options = []): ResponseInterface
{
try {
CTLog::getLog()->debug('CTClient: PATCH-Request URI:' . $uri, ["options" => $options, "mergedOptions" => self::mergeOptions($options)]);
return $this->handleResponse($this->guzzleClient->patch($uri, self::mergeOptions($options)));
} catch (Exception $exception) {
return $this->handleException($exception);
}
}

private function handleResponse(ResponseInterface $response): ResponseInterface
{
switch ($response->getStatusCode()) {
Expand Down
42 changes: 41 additions & 1 deletion src/Exceptions/CTRequestException.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace CTApi\Exceptions;


use CTApi\CTLog;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use Throwable;

Expand All @@ -24,4 +24,44 @@ public static function ofModelNotFound(?string $modelName = null, ?Throwable $th
return new CTRequestException("Could not retrieve Model: " . $modelName, 0, $throwable);
}
}

public static function ofErrorResponse(ResponseInterface $response): self
{
$contents = \json_decode((string) $response->getBody(), true);

if (!isset($contents['message'])) {
return new self($response->getReasonPhrase() ?: 'Unknown API error.');
}

$msg = $contents['message'];

if (!empty($contents['errors'])) {
$errorDescriptions = [];

foreach ($contents['errors'] as $error) {
$wasValue = '';

if (array_key_exists('value', $error['args'])) {
$wasValue = 'Received value was %s.';

if (is_null($error['args']['value'])) {
$wasValue = sprintf($wasValue, 'null');
} else {
$wasValue = sprintf($wasValue, '"' . $error['args']['value'] . '"');
}
}

$errorDescriptions[] = sprintf(
'Field "%s": %s %s',
$error['fieldId'],
$error['message'],
$wasValue
);
}

$msg .= '. ' . implode(' ', $errorDescriptions);
}

return new self($msg);
}
}
27 changes: 26 additions & 1 deletion src/Models/Person.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
namespace CTApi\Models;


use CTApi\Models\Traits\ExtractData;
use CTApi\Models\Traits\FillWithData;
use CTApi\Models\Traits\MetaAttribute;
use CTApi\Requests\PersonEventRequestBuilder;
use CTApi\Requests\PersonGroupRequestBuilder;

class Person
{
use FillWithData, MetaAttribute;
use FillWithData, ExtractData, MetaAttribute;

protected ?string $id = null;
protected ?string $guid = null;
Expand Down Expand Up @@ -64,6 +65,30 @@ protected function fillArrayType(string $key, array $data): void
}
}

public function getModifiableAttributes(): array
{
return [
'addressAddition',
'birthday',
'birthName',
'birthplace',
'city',
'country',
'email',
'fax',
'firstName',
'job',
'lastName',
'mobile',
'nickname',
'phonePrivate',
'phoneWork',
'sexId',
'street',
'zip',
];
}

private function processDomainAttributes(array $domainAttributes): void
{
if (array_key_exists('firstName', $domainAttributes)) {
Expand Down
13 changes: 13 additions & 0 deletions src/Models/Traits/ExtractData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace CTApi\Models\Traits;

trait ExtractData
{
public function extractData(): array
{
return get_object_vars($this);
}

public abstract function getModifiableAttributes(): array;
}
47 changes: 47 additions & 0 deletions src/Requests/AbstractRequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


use CTApi\CTClient;
use CTApi\Exceptions\CTModelException;
use CTApi\Exceptions\CTRequestException;
use CTApi\Models\AbstractModel;
use CTApi\Requests\Traits\OrderByCondition;
Expand Down Expand Up @@ -63,6 +64,52 @@ public function get(): array
return $this->getModelClass()::createModelsFromArray($data);
}

/**
* Update object's data in ChurchTools by the given object ID.
* @param string $objectId
* @param array $data Key-Value pair with attributes
*/
protected function updateData(string $objectId, array $data): void
{
$url = $this->getApiEndpoint() . '/' . $objectId;

$client = CTClient::getClient();
$response = $client->patch($url, ['json' => $data]);

if ($response->getStatusCode() !== 200) {
throw CTRequestException::ofErrorResponse($response);
}
}

/**
* Send Update-Request for given Model. Only update Attributes that are given with the updateAttributes-Parameter.
*
* @param $model Model
* @param string $modelId Id of Model
* @param array $updateAttributes Pass the attributes that should be updated as array. If nothing or
* an empty array is passed, all data of the person will be sent to the API.
* If an array is passed that looks like this:
* <code>['firstName', 'lastName', 'nickname']</code>
* only those attributes will be sent to the API.
*/
protected function updateDataForModel($model, string $modelId, array $updateAttributes): void
{
if (empty($updateAttributes)) {
$updateAttributes = $model->getModifiableAttributes();
} else {
$diff = array_diff($updateAttributes, $model->getModifiableAttributes());

if ($diff) {
throw new CTModelException('The attributes ' . implode(', ', $diff) . ' of Model ' . get_class($model) . ' are not modifiable.');
}
}

$allData = $model->extractData();
$updateAttributes = array_intersect_key($allData, array_flip($updateAttributes));

$this->updateData($modelId, $updateAttributes);
}

abstract protected function getApiEndpoint(): string;

abstract protected function getModelClass(): string;
Expand Down
14 changes: 14 additions & 0 deletions src/Requests/PersonRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,18 @@ public static function find(int $id): ?Person
return (new PersonRequestBuilder())->find($id);
}

/**
* Update the person's data on churchtools.
*
* @param array $attributesToUpdate
* Pass the attributes that should be updated as array. If nothing or
* an empty array is passed, all data of the person will be sent to the API.
* If an array is passed that looks like this:
* <code>['firstName', 'lastName', 'nickname']</code>
* only those attributes will be sent to the API.
*/
public static function update(Person $person, array $attributesToUpdate = []): void
{
(new PersonRequestBuilder())->update($person, $attributesToUpdate);
}
}
21 changes: 21 additions & 0 deletions src/Requests/PersonRequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace CTApi\Requests;

use CTApi\CTClient;
use CTApi\Exceptions\CTModelException;
use CTApi\Exceptions\CTRequestException;
use CTApi\Models\Person;
use CTApi\Utils\CTResponseUtil;
Expand Down Expand Up @@ -42,6 +43,26 @@ public function get(): array
return Person::createModelsFromArray($data);
}

/**
* Update the person's data on churchtools.
*
* @param array $attributesToUpdate
* Pass the attributes that should be updated as array. If nothing or
* an empty array is passed, all data of the person will be sent to the API.
* If an array is passed that looks like this:
* <code>['firstName', 'lastName', 'nickname']</code>
* only those attributes will be sent to the API.
*/
public function update(Person $person, array $attributesToUpdate = []): void
{
$id = $person->getId();
if (is_null($id)) {
throw new CTModelException("ID of Person cannot be null.");
} else {
$this->updateDataForModel($person, $id, $attributesToUpdate);
}
}

protected function getApiEndpoint(): string
{
return '/api/persons';
Expand Down
56 changes: 56 additions & 0 deletions tests/integration/Requests/PersonUpdateRequestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php


namespace Tests\Integration\Requests;


use CTApi\Requests\PersonRequest;
use Tests\Integration\TestCaseAuthenticated;
use Tests\Integration\TestData;

class PersonUpdateRequestTest extends TestCaseAuthenticated
{
protected string $birthNameA = "Smith";
protected string $birthNameB = "Doe";

protected function setUp(): void
{
if (!TestData::getValue("UPDATE_PERSON_SHOULD_TEST") == "YES") {
$this->markTestSkipped("Test suite is disabled in testdata.ini");
}
}

public function testUpdatePersonWholeObject()
{
$me = PersonRequest::whoami();

// select the birthname that is not the current
$newBirthName = $me->getBirthName() == $this->birthNameA ? $this->birthNameB : $this->birthNameA;

// Update Birthname
$me->setBirthName($newBirthName);
PersonRequest::update($me);

// Reload Person-Object
$meReloaded = PersonRequest::whoami();

$this->assertEquals($meReloaded->getBirthName(), $newBirthName);
}

public function testUpdatePersonReducedAttributes()
{
$me = PersonRequest::whoami();

// select the birthname that is not the current
$newBirthName = $me->getBirthName() == $this->birthNameA ? $this->birthNameB : $this->birthNameA;

// Update Birthname
$me->setBirthName($newBirthName);
PersonRequest::update($me, ["birthName"]);

// Reload Person-Object
$meReloaded = PersonRequest::whoami();

$this->assertEquals($meReloaded->getBirthName(), $newBirthName);
}
}
5 changes: 4 additions & 1 deletion tests/integration/testdata.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,7 @@ RESOURCE_BOOKINGS_SHOULD_TEST = YES
RESOURCE_BOOKINGS_RESOURCE_ID_1 = 921
RESOURCE_BOOKINGS_RESOURCE_ID_2 = 922
RESOURCE_BOOKINGS_FROM_DATE = 2021-01-01
RESOURCE_BOOKINGS_TO_DATE = 2021-02-01
RESOURCE_BOOKINGS_TO_DATE = 2021-02-01

# enable UPDATE_PERSON testsuite with YES and disable with NO
UPDATE_PERSON_SHOULD_TEST = YES
Loading