Skip to content

Commit 82698d6

Browse files
committed
Added caching and cleaned up
1 parent 695d2b5 commit 82698d6

8 files changed

Lines changed: 99 additions & 82 deletions

File tree

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"type": "library",
66
"require": {
77
"ext-simplexml": "*",
8+
"psr/cache": "^2.0 || ^3.0",
9+
"symfony/cache": "^6.4 || ^7.4",
810
"symfony/console": "^6.4 || ^7.4",
911
"symfony/http-client": "^6.4 || ^7.4",
1012
"symfony/http-foundation": "^6.4 || ^7.4",

src/Client/ApiClient.php

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
use ItkDev\F2ApiClient\Model\Atom;
1010
use ItkDev\F2ApiClient\Model\CaseFile;
1111
use ItkDev\F2ApiClient\Model\Matter;
12+
use Psr\Cache\CacheItemInterface;
13+
use Psr\Cache\CacheItemPoolInterface;
14+
use Symfony\Component\Cache\Adapter\ProxyAdapter;
1215
use Symfony\Component\HttpClient\HttpClient;
1316
use Symfony\Component\HttpFoundation\Request;
1417
use Symfony\Component\HttpFoundation\Response;
@@ -40,45 +43,42 @@ public function __construct(array $options)
4043
*/
4144
public function getServiceIndex(): array
4245
{
43-
$path = '/F2Rest/ServiceIndex';
44-
$response = $this->client()->request(Request::METHOD_GET, $path, [
45-
'headers' => [
46-
'Accept' => 'application/json',
47-
],
48-
]);
46+
$cache = new ProxyAdapter(pool: $this->options['cache_item_pool']);
47+
$cacheKey = sha1(__METHOD__);
48+
49+
$result = $cache->get($cacheKey, function (CacheItemInterface $item): array {
50+
$item->expiresAfter($this->options['cache_item_lifetime']);
51+
52+
$path = '/F2Rest/ServiceIndex';
53+
$response = $this->client()->request(Request::METHOD_GET, $path, [
54+
'headers' => [
55+
'Accept' => 'application/json',
56+
],
57+
]);
4958

50-
return $response->toArray();
59+
return $response->toArray();
60+
});
61+
62+
return $result;
5163
}
5264

5365
/**
54-
* @param array{q: string, count: int} $query
5566
* @return Atom[]
5667
*
5768
* @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
5869
* @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
5970
* @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
6071
* @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
6172
*/
62-
public function caseSearch(string $q, int $count = 10): array
73+
public function caseSearch(string $searchTerms, int $count = 10): array
6374
{
6475
$query = [
65-
'q' => $q,
76+
'searchTerms' => $searchTerms,
6677
'count' => $count,
6778
];
68-
$path = '/F2Rest/search/cases';
69-
// @todo /F2Rest/ServiceIndex.json reports
70-
//
71-
// "http://cbrain.com/casefile/rel/case-search": {
72-
// "href": "/F2Rest/search/cases",
73-
// "title": "Case file search"
74-
// },
75-
//
76-
// which refers to the actual search URL: /F2Rest/searches/cases
77-
$path = '/F2Rest/searches/cases';
78-
// $path = $this->getRequestUrl('http://cbrain.com/casefile/rel/case-search');
79-
$response = $this->request(Request::METHOD_GET, $path, [
80-
'query' => $query,
81-
]);
79+
80+
$url = $this->getRequestUrl('http://cbrain.com/casefile/rel/case-search', $query, isSearch: true);
81+
$response = $this->request(Request::METHOD_GET, $url);
8282

8383
$items = [];
8484
$sxe = new \SimpleXMLElement($response->getContent());
@@ -106,25 +106,12 @@ public function caseById(string $id): CaseFile
106106
public function matterSearch(string $searchTerms, int $count = 10): array
107107
{
108108
$query = [
109-
'searchTerms' => $searchTerms,
110-
'count' => $count,
109+
'searchTerms' => $searchTerms,
110+
'count' => $count,
111111
];
112-
// @todo /F2Rest/ServiceIndex.json reports
113-
//
114-
// "http://cbrain.com/casefile/rel/case-search": {
115-
// "href": "/F2Rest/search/cases",
116-
// "title": "Case file search"
117-
// },
118-
//
119-
// which refers to the actual search URL: /F2Rest/searches/cases
120-
$url = '/F2Rest/searches/matters';
121-
$url = '/F2Rest/searches/matters?q={searchTerms}&count={count}';
122-
$url = $this->replacePlaceholders($url, $query);
123-
// $url = $this->getRequestUrl('http://cbrain.com/casefile/rel/matter-search', [
124-
// ]);
125-
$response = $this->request(Request::METHOD_GET, $url, [
126-
'query' => $query,
127-
]);
112+
113+
$url = $this->getRequestUrl('http://cbrain.com/casefile/rel/matter-search', $query, isSearch: true);
114+
$response = $this->request(Request::METHOD_GET, $url);
128115

129116
$items = [];
130117
$sxe = new \SimpleXMLElement($response->getContent());
@@ -163,7 +150,6 @@ public function matterByMatterNumber(string $matterNumber): Matter
163150
return Matter::fromSimpleXMLElement(new \SimpleXMLElement($response->getContent()));
164151
}
165152

166-
167153
/**
168154
* @return array{access_token: string, token_type: string}
169155
*/
@@ -221,24 +207,51 @@ protected function client(): HttpClientInterface
221207

222208
protected function configureOptions(OptionsResolver $resolver): void
223209
{
224-
$resolver->setRequired([
225-
'api_uri',
226-
'api_username',
227-
'api_secret',
228-
'f2_username',
229-
]);
210+
$resolver
211+
->setRequired([
212+
'api_uri',
213+
'api_username',
214+
'api_secret',
215+
'f2_username',
216+
])
217+
->setDefault('cache_item_lifetime', 86400)
218+
->setAllowedTypes('cache_item_lifetime', 'int')
219+
->setRequired('cache_item_pool')
220+
->setAllowedTypes('cache_item_pool', CacheItemPoolInterface::class);
230221
}
231222

232-
protected function getRequestUrl(string $rel, array $values): string
223+
protected function getRequestUrl(string $rel, array $values, bool $isSearch = false): string
233224
{
234-
// @todo Cache this!
235225
$index = $this->getServiceIndex();
236226

237227
$url = $index[$rel]['href'] ?? null;
238228
if (null === $url) {
239229
throw new RuntimeException(sprintf('Cannot get rel %s', $rel));
240230
}
241231

232+
if ($isSearch) {
233+
try {
234+
$cache = new ProxyAdapter(pool: $this->options['cache_item_pool']);
235+
$cacheKey = sha1(__METHOD__ . '|||' . $rel);
236+
237+
$url = $cache->get($cacheKey, function (CacheItemInterface $item) use ($url) {
238+
$item->expiresAfter($this->options['cache_item_lifetime']);
239+
240+
$response = $this->request(Request::METHOD_GET, $url);
241+
$sxe = new \SimpleXMLElement($response->getContent());
242+
243+
$searchUrl = (string) $sxe->Url['template'];
244+
if (empty($searchUrl)) {
245+
throw new RuntimeException(sprintf('Cannot get search template URL for %s', $url));
246+
}
247+
248+
return $searchUrl;
249+
});
250+
} catch (\Exception $e) {
251+
throw new RuntimeException(sprintf('Cannot get search URL for rel %s', $rel), previous: $e);
252+
}
253+
}
254+
242255
return $this->replacePlaceholders($url, $values);
243256
}
244257

@@ -253,7 +266,7 @@ static function (array $matches) use ($url, $values): string {
253266
throw new RuntimeException(sprintf('Missing value %s for URL %s', $name, $url));
254267
}
255268

256-
return rawurlencode((string)$values[$name]);
269+
return rawurlencode((string) $values[$name]);
257270
},
258271
$url,
259272
);

src/Command/F2ApiClientCommand.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
namespace ItkDev\F2ApiClient\Command;
66

77
use ItkDev\F2ApiClient\Client\ApiClient;
8+
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
89
use Symfony\Component\Console\Attribute\Argument;
910
use Symfony\Component\Console\Attribute\AsCommand;
1011
use Symfony\Component\Console\Command\Command;
1112
use Symfony\Component\Console\Exception\InvalidArgumentException;
1213
use Symfony\Component\Console\Exception\RuntimeException;
1314
use Symfony\Component\Console\Style\SymfonyStyle;
15+
use Symfony\Component\Filesystem\Filesystem;
1416

1517
#[AsCommand(name: 'f2:api:client')]
1618
class F2ApiClientCommand
@@ -42,7 +44,10 @@ public function __invoke(SymfonyStyle $io, #[Argument] string $action, #[Argumen
4244
default => throw new InvalidArgumentException(sprintf('Invalid action: %s', $action)),
4345
};
4446

45-
$io->writeln((string) json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
47+
$io->writeln((string) json_encode(
48+
$response,
49+
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
50+
));
4651

4752
return Command::SUCCESS;
4853
}
@@ -63,6 +68,8 @@ private function createClient(): ApiClient
6368
'api_username' => $getEnv('F2_API_USERNAME'),
6469
'api_secret' => $getEnv('F2_API_SECRET'),
6570
'f2_username' => $getEnv('F2_F2_USERNAME'),
71+
72+
'cache_item_pool' => new FilesystemAdapter(),
6673
];
6774

6875
return new ApiClient($config);

src/Model/AbstractItem.php

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@
66

77
abstract class AbstractItem implements \JsonSerializable, \Stringable
88
{
9-
// abstract static function fromResponse(ResponseInterface $response);
10-
abstract static function fromSimpleXMLElement(\SimpleXMLElement $sxe);
9+
abstract static function fromSimpleXMLElement(\SimpleXMLElement $sxe);
1110

12-
protected static function listOf(string $class, \SimpleXMLElement $sxe): array
13-
{
14-
$items = [];
15-
foreach ($sxe as $child) {
16-
$items[] = $child::class::fromSimpleXMLElement($child);
17-
}
18-
19-
return $items;
20-
}
11+
protected static function listOf(string $class, \SimpleXMLElement $sxe): array
12+
{
13+
$items = [];
14+
foreach ($sxe as $child) {
15+
$items[] = $child::class::fromSimpleXMLElement($child);
16+
}
2117

18+
return $items;
19+
}
2220
}

src/Model/Atom.php

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@
77
final class Atom extends AbstractItem
88
{
99
public function __construct(
10-
protected string $id,
11-
protected string $title,
12-
protected \DateTimeImmutable $published,
13-
protected \DateTimeImmutable $updated,
14-
) {}
10+
protected string $id,
11+
protected string $title,
12+
protected \DateTimeImmutable $published,
13+
protected \DateTimeImmutable $updated,
14+
)
15+
{
16+
}
1517

1618
public static function fromSimpleXMLElement(\SimpleXMLElement $sxe)
1719
{
1820
return new self(
19-
id: (string)$sxe->id,
20-
title: (string)$sxe->title,
21-
published: new \DateTimeImmutable((string)$sxe->published),
22-
updated: new \DateTimeImmutable((string)$sxe->updated),
21+
id: (string) $sxe->id,
22+
title: (string) $sxe->title,
23+
published: new \DateTimeImmutable((string) $sxe->published),
24+
updated: new \DateTimeImmutable((string) $sxe->updated),
2325
);
2426
}
2527

src/Model/CaseFile.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,16 @@ public function __construct(
4444
// PartyItem
4545
// The party who is responsible for the case.
4646
public readonly PartyItem $responsible,
47-
4847
// Link
4948
// List of Link (read-only)
5049
// One or more links to other related resources.
51-
5250
// Matters
5351
// List of Matter
5452
// List of all accessible matters on the case.
5553
// /** @var Matter[] */
5654
// public readonly array $matters,
57-
) {
55+
)
56+
{
5857
}
5958

6059
public static function fromSimpleXMLElement(\SimpleXMLElement $sxe)

src/Model/Matter.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ public function __construct(
1616
public readonly PartyItem $responsible,
1717
)
1818
{
19-
2019
}
2120

2221
#[\Override]
2322
static function fromSimpleXMLElement(\SimpleXMLElement $sxe)
2423
{
2524
return new self(
26-
id: (int)$sxe->Id,
27-
matterNumber: (string)$sxe->MatterNumber,
25+
id: (int) $sxe->Id,
26+
matterNumber: (string) $sxe->MatterNumber,
2827
title: (string) $sxe->Title,
2928
createdDate: new \DateTimeImmutable((string) $sxe->CreatedDate),
3029
modifiedDate: new \DateTimeImmutable((string) $sxe->ModifiedDate),

src/Model/PartyItem.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,13 @@ public function __construct(
3636
// int
3737
// Internal identifier of party - displayed in F2 party details window.
3838
public readonly int $partyNumber,
39-
4039
// SynchronizationKey
4140
// string
4241
// Identifier for synchronizing parties with external repositories such as Windows
4342
// Active Directory.
44-
4543
// CPRCVR
4644
// string
4745
// Personal security number or VAT number.
48-
4946
// CVR_P
5047
// string
5148
// Danish P-number extension to CVR (VAT) number.

0 commit comments

Comments
 (0)