39 Commits
1.0.2 ... main

Author SHA1 Message Date
491d4e3cb7 missing use statement 2024-09-13 04:47:07 +02:00
fe58a2e5b8 fixed wrong definition 2024-09-13 04:44:10 +02:00
0ac58211fb added config definition for cache enabler 2024-09-13 04:43:29 +02:00
3ff3ddfdc5 switch to enable/disable cache, added dedicated RedmineClientInterface 2024-09-13 04:40:28 +02:00
e9c4f4bca6 fixed config definition 2024-09-13 04:22:40 +02:00
876f356009 make url and apikey parameters 2024-09-13 04:21:26 +02:00
99417ef9ec made this thingi work \o/ 2024-09-13 04:01:21 +02:00
27fc4b9f99 added tests 2024-09-13 03:42:51 +02:00
84a8045e3e someday, it'll work 2024-09-13 03:30:36 +02:00
93d2fa6531 use recent file layout and make this stuff work 2024-09-13 03:22:19 +02:00
30b61b5064 made ttl configurable 2024-09-13 03:13:06 +02:00
a859a4a3ec define services in services.yaml instead of Extension class 2024-09-13 03:02:22 +02:00
0eaddfd7e1 throw arguments into closure 2024-09-13 02:55:59 +02:00
d22997f2f9 make sure arguments is an array 2024-09-13 02:54:00 +02:00
387ced5dce use call_user_func_array 2024-09-13 02:52:08 +02:00
da2e01d92b use absolute function call 2024-09-13 02:50:06 +02:00
3dd751f9a5 added missing arguments 2024-09-13 02:46:50 +02:00
22df616637 scope api and method to closure 2024-09-13 02:42:52 +02:00
094292a3ec missing use statement 2024-09-13 02:40:08 +02:00
b09a9990e3 using dedicated cache pool 2024-09-13 02:36:59 +02:00
0ae38ebd7a missing use statement 2024-09-13 02:32:07 +02:00
94165b3147 using service definition instead auf autowiring 2024-09-13 02:31:14 +02:00
6c608c5d00 trying to get that cache thingi work 2024-09-13 02:23:17 +02:00
b1452c4011 autowire by Attribute 2024-09-13 02:18:06 +02:00
dfe0bcfcc1 using explicit cache-contracts 2024-09-13 02:11:50 +02:00
225d8aadd3 removed invalid semicolon 2024-09-13 02:10:22 +02:00
461b074988 made calls cacheable 2024-09-13 02:09:15 +02:00
4102902b34 require guzzle 2024-09-13 01:19:40 +02:00
14569bd668 added type hints (oh, yeah - mixed for all) 2024-09-13 01:09:03 +02:00
72023d4591 use new Psr18Client 2024-09-13 01:04:38 +02:00
a698255f01 added wrapper for NativeCurlClient 2024-09-13 00:43:53 +02:00
c73f021d69 require curl extension 2024-09-13 00:43:20 +02:00
a9295b89f6 infotext changes 2024-09-13 00:43:01 +02:00
71e0377de9 downgraded PHPUnit to v10 2024-09-12 23:14:30 +02:00
8c879843b2 removed unused setting 2024-08-31 15:23:31 +02:00
cbd35ea507 force redmine-api version 2.7+ 2024-08-31 15:22:48 +02:00
478528db6e add composer.lock to repo 2024-07-13 05:49:21 +02:00
f6151266a5 package updates 2024-07-13 05:49:06 +02:00
656deb2a26 test are working 2023-04-29 09:13:37 +02:00
12 changed files with 4162 additions and 100 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
/.phpunit.result.cache
/.phpunit.cache
/composer.lock
/phpunit.xml
/vendor/
*~

View File

@@ -1,3 +0,0 @@
{
"nuxt.isNuxtApp": false
}

View File

@@ -1,18 +1,30 @@
{
"name": "magdev/redmine-bundle",
"description": "Symfony bundle to integrate Redmine",
"description": "Symfony bundle to integrate the Redmine-API",
"license": "MIT",
"type": "symfony-bundle",
"authors": [
{
"name": "Marco Grätsch",
"email": "magdev3.0@gmail.com"
}
],
"require": {
"kbsali/redmine-api": "^2.2"
"ext-curl": "*",
"kbsali/redmine-api": "^2.7",
"guzzlehttp/guzzle": "^7",
"symfony/cache-contracts": "^3.5",
"symfony/cache": "^7.1",
"symfony/yaml": "^7.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"symfony/http-kernel": "^6.2",
"symfony/dependency-injection": "^6.2",
"symfony/config": "^6.2",
"symfony/phpunit-bridge": "^6.0"
"phpunit/phpunit": "^9.5 || ^10",
"symfony/config": "^6 || ^7",
"symfony/dependency-injection": "^6 || ^7",
"symfony/http-kernel": "^6 || ^7",
"symfony/phpunit-bridge": "^6 || ^7"
},
"license": "MIT",
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"Magdev\\RedmineBundle\\": "src/"
@@ -22,12 +34,5 @@
"psr-4": {
"Magdev\\RedmineBundle\\Tests\\": "tests/"
}
},
"authors": [
{
"name": "Marco Grätsch",
"email": "magdev3.0@gmail.com"
}
],
"minimum-stability": "stable"
}
}

3954
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
config/services.yaml Normal file
View File

@@ -0,0 +1,10 @@
services:
Magdev\RedmineBundle\Client\RedmineClient:
arguments:
- '@redmine_api.cache'
- '%magdev_redmine.url%'
- '%magdev_redmine.apiKey%'
- '%magdev_redmine.cache.ttl%'
- '%magdev_redmine.cache.enabled%'
redmine.api: '@Magdev\RedmineBundle\Client\RedmineClient'

View File

@@ -0,0 +1,125 @@
<?php
namespace Magdev\RedmineBundle\Client;
use Redmine\Client\Psr18Client;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Psr7\HttpFactory as GuzzleHttpFactory;
use Redmine\Api;
use Redmine\Http\HttpClient;
use Redmine\Http\Request;
use Redmine\Http\Response;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
final class RedmineClient implements RedmineClientInterface, HttpClient
{
private ?Psr18Client $client = null;
public function __construct(
private TagAwareCacheInterface $cache,
private string $url,
private string $apiKey,
private int $ttl,
private bool $cacheEnabled
) {}
public function call(string $api, string $method, array $arguments = []): mixed
{
if (!$this->cacheEnabled) {
$client = $this->getClient()->getApi($api);
return \call_user_func_array(
[$client, $method],
$arguments
);
}
$cacheKey = sprintf('%s_%s_%s', $api, $method, sha1(serialize($arguments)));
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($api, $method, $arguments): array {
$item->expiresAfter($this->ttl);
$item->tag([$api, $method]);
$client = $this->getClient()->getApi($api);
$value = \call_user_func_array(
[$client, $method],
$arguments
);
return $value;
});
}
public function getClient(): ?Psr18Client
{
if (!$this->client) {
$guzzle = new GuzzleClient();
$factory = new GuzzleHttpFactory();
$this->client = new Psr18Client(
$guzzle,
$factory,
$factory,
$this->url,
$this->apiKey
);
}
return $this->client;
}
public function request(Request $request): Response
{
return $this->getClient()->request($request);
}
public function getApi(string $name): Api
{
return $this->getClient()->getApi($name);
}
public function startImpersonateUser(string $username): void
{
$this->getClient()->startImpersonateUser($username);
}
public function stopImpersonateUser(): void
{
$this->getClient()->stopImpersonateUser();
}
public function requestGet(string $path): bool
{
return $this->getClient()->requestGet($path);
}
public function requestPost(string $path, string $body): bool
{
return $this->getClient()->requestPost($path, $body);
}
public function requestPut(string $path, string $body): bool
{
return $this->getClient()->requestPut($path, $body);
}
public function requestDelete(string $path): bool
{
return $this->getClient()->requestDelete($path);
}
public function getLastResponseStatusCode(): int
{
return $this->getClient()->getLastResponseStatusCode();
}
public function getLastResponseContentType(): string
{
return $this->getClient()->getLastResponseContentType();
}
public function getLastResponseBody(): string
{
return $this->getClient()->getLastResponseBody();
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Magdev\RedmineBundle\Client;
use Redmine\Client\Client;
use Redmine\Client\Psr18Client;
interface RedmineClientInterface extends Client
{
public function call(string $api, string $method, array $arguments = []): mixed;
public function getClient(): ?Psr18Client;
}

View File

@@ -14,26 +14,36 @@ final class Configuration implements ConfigurationInterface
{
$treeBuilder = new TreeBuilder('magdev_redmine');
$rootNode = $treeBuilder->getRootNode();
$rootNode->fixXmlConfig('connection')
->children()
->arrayNode('connections')
->useAttributeAsKey('name')
->normalizeKeys(false)
->arrayPrototype()
$rootNode->children()
->scalarNode('url')
->info('URL of the Redmine instance')
->example('http://your-redmine.com')
->cannotBeEmpty()
->isRequired()
->end()
->scalarNode('apiKey')
->info('API-Key to access Redmine')
->example('abcdefghijklmnopqrstuvwxyz')
->cannotBeEmpty()
->isRequired()
->end()
->arrayNode('cache')
->children()
->scalarNode('url')
->booleanNode('enabled')
->info('Enable/disable cache')
->defaultTrue()
->isRequired()
->info('The URL of your Redmine instance')
->end()
->scalarNode('apikey')
->scalarNode('ttl')
->info('TTL for cached API calls')
->example([1800, 'P30M'])
->defaultValue(1800)
->cannotBeEmpty()
->isRequired()
->info('Your Redmine API-Key')
->end()
->end()
->end()
->end()
->scalarNode('default_connection')->end()
;
->end();
return $treeBuilder;
}

View File

@@ -2,9 +2,11 @@
namespace Magdev\RedmineBundle\DependencyInjection;
use Redmine\Client\NativeCurlClient;
use Magdev\RedmineBundle\Client\RedmineClient;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* @author Marco Grätsch <magdev3.0@gmail.com>
@@ -15,19 +17,12 @@ final class MagdevRedmineExtension extends Extension
{
$config = $this->processConfiguration(new Configuration(), $configs);
if (!isset($config['default_connection'])) {
$config['default_connection'] = array_key_first($config['connections']);
}
$container->setParameter('magdev_redmine.cache.enabled', $config['cache']['enabled']);
$container->setParameter('magdev_redmine.cache.ttl', $config['cache']['ttl']);
$container->setParameter('magdev_redmine.url', $config['url']);
$container->setParameter('magdev_redmine.apiKey', $config['apiKey']);
foreach ($config['connections'] as $name => $connection) {
$id = sprintf('magdev.redmine.%s', $name);
$container->register($id, NativeCurlClient::class)
->setArguments([$connection['url'], $connection['apikey']]);
$container->registerAliasForArgument($id, NativeCurlClient::class, "{$name}Client");
if ($name === $config['default_connection']) {
$container->setAlias(NativeCurlClient::class, $id);
}
}
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/../config'));
$loader->load('services.yaml');
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace Magdev\RedmineBundle\Enum;
enum ApiName
{
case attachment;
case group;
case custom_fields;
case issue;
case issue_category;
case issue_priority;
case issue_relation;
case issue_status;
case membership;
case news;
case project;
case query;
case role;
case time_entry;
case time_entry_activity;
case tracker;
case user;
case version;
case wiki;
case search;
}

View File

@@ -17,23 +17,15 @@ class ConfigurationTest extends TestCase
public function testConfig(array $configs): void
{
$config = (new Processor())->processConfiguration(new Configuration(), $configs);
$this->assertSame('foo', $config['connections']['default']['apikey']);
$this->assertSame('bar', $config['connections']['default']['url']);
$this->assertSame(1800, $config['ttl']);
}
public function configsProvider(): iterable
{
yield [
[
'magdev_redmine' => [
'connections' => [
'default' => [
'apikey' => 'foo',
'url' => 'bar'
],
],
],
yield [[
'magdev_redmine' => [
'ttl' => 1800
],
];
]];
}
}

View File

@@ -2,6 +2,7 @@
namespace Magdev\RedmineBundle\Tests\DependencyInjection;
use Magdev\RedmineBundle\Client\RedmineClient;
use Magdev\RedmineBundle\DependencyInjection\MagdevRedmineExtension;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -17,21 +18,10 @@ class MagdevRedmineExtensionTest extends TestCase
(new MagdevRedmineExtension())->load([
'magdev_redmine' => [
'connections' => [
'primary' => ['url' => 'http://foo1', 'apikey' => 'bar1'],
'secondary' => ['url' => 'http://foo2', 'apikey' => 'bar2'],
],
'ttl' => 1800,
],
], $container);
$this->assertTrue($container->has('magdev.redmine.primary'));
$this->assertTrue($container->has('magdev.redmine.secondary'));
$this->assertTrue($container->hasAlias(Client::class));
$this->assertTrue($container->hasAlias(Client::class.' $primaryClient'));
$this->assertTrue($container->hasAlias(Client::class.' $secondaryClient'));
$this->assertInstanceOf(Client::class, $container->get('magdev.redmine.primary'));
$this->assertInstanceOf(Client::class, $container->get('magdev.redmine.secondary'));
#$this->assertInstanceOf(RedmineClient::class, $container->get('redmine.api'));
}
}