Passed
Push — master ( 6bd0b8...84c84f )
by Sébastien
03:47
created

PjbProxyClient::getLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Soluble Japha / PhpJavaBridge.
7
 *
8
 * @author Vanvelthem Sébastien
9
 * @license MIT
10
 */
11
12
namespace Soluble\Japha\Bridge\Driver\Pjb62;
13
14
use Monolog\Logger;
15
use Soluble\Japha\Bridge\Exception;
16
use Soluble\Japha\Interfaces;
17
use Soluble\Japha\Bridge\Driver\ClientInterface;
18
use ArrayObject;
19
use Soluble\Japha\Bridge\Driver\Pjb62\Exception\IllegalArgumentException;
20
use Psr\Log\LoggerInterface;
21
use Psr\Log\NullLogger;
22
23
class PjbProxyClient implements ClientInterface
24
{
25
    /**
26
     * @var PjbProxyClient|null
27
     */
28
    protected static $instance;
29
30
    protected static $unregistering = false;
31
32
    /**
33
     * @var array
34
     */
35
    protected $defaultOptions = [
36
        'java_disable_autoload' => true,
37
        'java_log_level' => null,
38
        'java_send_size' => 8192,
39
        'java_recv_size' => 8192,
40
        // java_prefer_values=true is the default working mode
41
        // of the soluble-japha client... It may be less efficient,
42
        // because it casts java String, Boolean, Integer... objects
43
        // automatically into (string, bool, integer...), and thus
44
        // not require additional writing like ($ba->values($myInt)) in
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
45
        // order to use a remote object. But prevent to work on the proxy instead,
46
        // so the value is always transferred for those types. If you put
47
        // at false you'll have to rework on the code.
48
        'java_prefer_values' => true,
49
        // use SimpleParser (pure PHP code) even if NativeParser (based on xml_* php functions) may be used
50
        // should only be used to workaround bugs or limitations regarding the xml extension
51
        'force_simple_xml_parser' => false
52
    ];
53
54
    /**
55
     * @var Client|null
56
     */
57
    protected static $client;
58
59
    /**
60
     * Internal cache for already loaded Java classes.
61
     *
62
     * @var array
63
     */
64
    protected $classMapCache = [];
65
66
    /**
67
     * @var string
68
     */
69
    protected $compatibilityOption;
70
71
    /**
72
     * @var ArrayObject
73
     */
74
    protected $options;
75
76
    /**
77
     * @var LoggerInterface
78
     */
79
    protected $logger;
80
81
    /**
82
     * @var string|null
83
     */
84
    protected static $instanceOptionsKey;
85
86
    /**
87
     * Private contructor.
88
     *
89
     * $options requires :
90
     *  'servlet_address' => 'http://127.0.0.1:8080/javabridge-bundle/java/servlet.phpjavabridge'
91
     *
92
     *  Optionally :
93
     *  'java_log_level' => null
94
     *  'java_send_size' => 8192,
95
     *  'java_recv_size' => 8192
96
     *
97
     *
98
     * @throws Exception\InvalidArgumentException
99
     * @throws Exception\ConnectionException
100
     *
101
     * @see PjbProxyClient::getInstance()
102
     *
103 25
     * @param array           $options
104
     * @param LoggerInterface $logger
105 25
     */
106 25
    protected function __construct(array $options, LoggerInterface $logger)
107
    {
108 25
        $this->options = new ArrayObject(array_merge($this->defaultOptions, $options));
109 25
        self::$instanceOptionsKey = serialize((array) $this->options);
110 21
111
        $this->logger = $logger;
112
        $this->loadClient();
113
    }
114
115
    /**
116
     * Return a unique instance of the phpjavabridge client
117
     * $options is an associative array and requires :.
118
     *
119
     *  'servlet_address' => 'http://127.0.0.1:8080/javabridge-bundle/java/servlet.phpjavabridge'
120
     *
121
     *  $options can be :
122
     *  "java_send_size" => 8192,
123
     *  "java_recv_size" => 8192,
124
     *  "java_log_level' => null,
125
     *  "java_prefer_values" => true (see note)
126
     *
127
     * <code>
128
     *    $options = [
129
     *      'servlet_address' => 'http://127.0.0.1:8080/javabridge-bundle/servlet.phpjavabridge'
130
     *      "java_send_size" => 8192,
131
     *      "java_recv_size" => 8192,
132
     *      "internal_encoding" => 'UTF-8'
133
     *    ];
134
     *    $pjb = PjbProxyClient::getInstance($options, $logger);
135
     * </code>
136
     *
137
     * Note: java_prefer_values=true is the default working mode
138
     * of the soluble-japha client...
139
     *
140
     * Disadvantage: Not good for performance !!!
141
     *  > From mailinglist: Please note that the option JAVA_PREFER_VALUES kills performance as it
142
     *  > checks for an exception after each call (I.e. each java call generates a full network round-trip).
143
     *  Note that in simple_benchmarks.php no difference have been measured (localhost), need more
144
     *  taylor made tests to see.
145
     *
146
     * Advantage: More readable / writable
147
     *  > it casts java String, Boolean, Integer... objects
148
     *  > automatically into (string, bool, integer...), and thus
149
     *  > not require additional writing like ($ba->values($myInt)) in
150
     *  > order to get the value. (proxy)
151
     *
152
     * If you put at false you'll have to rework on the code.
153
     * Check what's best for yourself
154
     *
155
     * @throws Exception\InvalidArgumentException
156
     * @throws Exception\ConnectionException
157
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
158
     *
159
     * @param array|null           $options
160
     * @param LoggerInterface|null $logger  any psr3 logger
161 150
     *
162
     * @return PjbProxyClient
163 150
     */
164 26
    public static function getInstance(?array $options = null, ?LoggerInterface $logger = null): PjbProxyClient
165 3
    {
166
        if (self::$instance === null) {
167 3
            if ($options === null) {
168
                throw new Exception\InvalidUsageException(
169
                    'Cannot instanciate PjbProxyClient without "$options" the first time, '.
170 25
                    'or the instance have been unregistered since'
171 11
                );
172
            }
173 25
            if ($logger === null) {
174
                $logger = new NullLogger();
175
            }
176 147
            self::$instance = new self($options, $logger);
177
        }
178
179
        return self::$instance;
180
    }
181
182 3
    /**
183
     * @return bool
184 3
     */
185
    public static function isInitialized(): bool
186
    {
187
        return self::$instance !== null;
188
    }
189
190
    /**
191
     * Load pjb client with options.
192
     *
193 25
     * @throws Exception\InvalidArgumentException
194
     * @throws Exception\ConnectionException
195 25
     */
196 25
    protected function loadClient(): void
197
    {
198 25
        if (self::$client === null) {
199 1
            $options = $this->options;
200
201
            if (!isset($options['servlet_address'])) {
202 24
                throw new Exception\InvalidArgumentException(__METHOD__.' Missing required parameter servlet_address');
203
            }
204 23
205 23
            $connection = static::parseServletUrl($options['servlet_address']);
206 23
207 23
            $params = new ArrayObject([
208 23
                Client::PARAM_JAVA_HOSTS => $connection['servlet_host'],
209 23
                Client::PARAM_JAVA_SERVLET => $connection['servlet_uri'],
210 23
                Client::PARAM_JAVA_AUTH_USER => $connection['auth_user'],
211 23
                Client::PARAM_JAVA_AUTH_PASSWORD => $connection['auth_password'],
212 23
                Client::PARAM_JAVA_DISABLE_AUTOLOAD => $options['java_disable_autoload'],
213 23
                Client::PARAM_JAVA_PREFER_VALUES => $options['java_prefer_values'],
214 23
                Client::PARAM_JAVA_SEND_SIZE => $options['java_send_size'],
215
                Client::PARAM_JAVA_RECV_SIZE => $options['java_recv_size'],
216
                Client::PARAM_JAVA_LOG_LEVEL => $options['java_log_level'],
217 23
                Client::PARAM_XML_PARSER_FORCE_SIMPLE_PARSER => $options['force_simple_xml_parser'],
218
            ]);
219
220 21
            self::$client = new Client($params, $this->logger);
221
222 21
            // Added in order to work with custom exceptions
223
            self::getClient()->throwExceptionProxyFactory = new Proxy\DefaultThrowExceptionProxyFactory(self::$client, $this->logger);
224 21
225
            $this->bootstrap();
226
        }
227
    }
228
229
    /**
230
     * Return Pjb62 internal client.
231 135
     *
232
     * @return Client
233 135
     *
234 5
     * @throws Exception\BrokenConnectionException
235
     */
236
    public static function getClient(): Client
237 135
    {
238
        if (self::$client === null) {
239
            throw new Exception\BrokenConnectionException('Client is not registered');
240
        }
241
242
        return self::$client;
243
    }
244
245
    /**
246
     * Return a Java class.
247
     *
248
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
249 56
     *
250
     * @param string $name Name of the java class
251 56
     *
252 25
     * @return JavaClass
253
     */
254
    public function getJavaClass($name): Interfaces\JavaClass
255 50
    {
256
        if (!array_key_exists($name, $this->classMapCache)) {
257
            $this->classMapCache[$name] = new JavaClass($name);
258
        }
259
260
        return $this->classMapCache[$name];
261
    }
262
263
    /**
264
     * Invoke a method dynamically.
265
     *
266
     * Example:
267
     * <code>
268
     * $bigint1 = new Java('java.math.BigInteger', 10);
269
     * $bigint2 = new Java('java.math.BigInteger', 20);
270
     * $bigint3 = PjbProxyClient::invokeMethod($bigint, "add", [$bigint2])
271
     * </code>
272
     *
273
     * <br> Any declared exception can be caught by PHP code. <br>
274
     * Exceptions derived from java.lang.RuntimeException or Error should
275
     * not be caught unless declared in the methods throws clause -- OutOfMemoryErrors cannot be caught at all,
276
     * even if declared.
277
     *
278
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
279
     *
280
     * @param Interfaces\JavaType|null $object a java object or type
281 5
     * @param string                   $method A method string
282
     * @param mixed                    $args   Arguments to send to method
283 5
     *
284
     * @return mixed
285 5
     */
286
    public function invokeMethod(?Interfaces\JavaType $object = null, string $method, array $args = [])
287
    {
288
        $id = ($object === null) ? 0 : $object->__getJavaInternalObjectId();
289
290
        return self::getClient()->invokeMethod($id, $method, $args);
291
    }
292
293
    /**
294
     * Inspect the java object | type.
295
     *
296
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
297
     *
298
     * @param Interfaces\JavaType $object
299 12
     *
300
     * @return string
301
     *
302
     * @throws IllegalArgumentException
303 12
     */
304
    public function inspect(Interfaces\JavaType $object): string
305
    {
306
        return self::getClient()->invokeMethod(0, 'inspect', [$object]);
307
    }
308
309
    /**
310
     * Test whether an object is an instance of java class or interface.
311
     *
312
     * @throws Exception\InvalidArgumentException
313
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
314
     *
315
     * @param Interfaces\JavaObject                                             $object
316
     * @param JavaType|string|Interfaces\JavaClass|Interfaces\JavaObject|string $class
317 8
     *
318
     * @return bool
319 8
     */
320
    public function isInstanceOf(Interfaces\JavaObject $object, $class): bool
321 6
    {
322
        if (is_string($class)) {
323 6
            // Attempt to initiate a class
324 3
            $name = $class;
325 2
            // Will eventually throws ClassNotFoundException
326
            $class = $this->getJavaClass($name);
327
        } elseif (!$class instanceof Interfaces\JavaObject) {
328 4
            throw new Exception\InvalidArgumentException(__METHOD__.'Class $class parameter must be of Interfaces\JavaClass, Interfaces\JavaObject or string');
329
        }
330
331
        return self::getClient()->invokeMethod(0, 'instanceOf', [$object, $class]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getClient()...array($object, $class)) returns the type string|Soluble\Japha\Bridge\Driver\Pjb62\JavaType which is incompatible with the type-hinted return boolean.
Loading history...
332
    }
333
334
    /**
335
     * Evaluate a Java object.
336
     *
337
     * Evaluate a object and fetch its content, if possible. Use java_values() to convert a Java object into an equivalent PHP value.
338
     *
339
     * A java array, Map or Collection object is returned
340
     * as a php array.
341
     * An array, Map or Collection proxy is returned as a java array, Map or
342
     * Collection object, and a null proxy is returned as null.
343
     * All values of java types for which a primitive php type exists are
344
     * returned as php values.
345
     * Everything else is returned unevaluated.
346
     * Please make sure that the values do not not exceed
347
     * php's memory limit. Example:
348
     *
349
     *
350
     * <code>
351
     * $str = new java("java.lang.String", "hello");
352
     * echo java_values($str);
353
     * => hello
354
     * $chr = $str->toCharArray();
355
     * echo $chr;
356
     * => [o(array_of-C):"[C@1b10d42"]
357
     * $ar = java_values($chr);
358
     * print $ar;
359
     * => Array
360
     * print $ar[0];
361
     * => [o(Character):"h"]
362
     * print java_values($ar[0]);
363
     * => h
364
     * </code>
365
     *
366
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
367
     *
368
     * @param Interfaces\JavaObject $object
369 12
     *
370
     * @return mixed
371 12
     */
372
    public function getValues(Interfaces\JavaObject $object)
373
    {
374
        return self::getClient()->invokeMethod(0, 'getValues', [$object]);
375
    }
376
377
    /**
378
     * Return latest exception.
379
     *
380
     * @deprecated
381
     *
382
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
383 1
     *
384
     * @return \Soluble\Japha\Bridge\Driver\Pjb62\Exception\JavaException
385 1
     */
386
    public function getLastException()
387
    {
388
        return self::getClient()->invokeMethod(0, 'getLastException', []);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getClient()...astException', array()) returns the type string which is incompatible with the documented return type Soluble\Japha\Bridge\Dri...Exception\JavaException.
Loading history...
389
    }
390
391
    /**
392
     * Clear last exception.
393
     *
394
     * @deprecated
395 1
     *
396
     * @throws \Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException
397 1
     */
398 1
    public function clearLastException(): void
399
    {
400
        self::getClient()->invokeMethod(0, 'clearLastException', []);
401
    }
402
403
    /**
404
     * @param Client $client
405 21
     *
406
     * @return string
407 21
     */
408 21
    public function getCompatibilityOption(Client $client = null): string
409 1
    {
410
        if ($this->compatibilityOption === null) {
411
            if ($client === null) {
412 21
                $client = $client = self::getClient();
0 ignored issues
show
Unused Code introduced by
The assignment to $client is dead and can be removed.
Loading history...
413 21
            }
414 21
415 21
            $java_prefer_values = $this->getOption('java_prefer_values');
416
            $java_log_level = $this->getOption('java_log_level');
417
            $compatibility = ($client->RUNTIME['PARSER'] === 'NATIVE') ? (0103 - $java_prefer_values) : (0100 + $java_prefer_values);
418 21
            if (is_int($java_log_level)) {
419
                $compatibility |= 128 | (7 & $java_log_level) << 2;
420
            }
421 21
            $this->compatibilityOption = chr($compatibility);
422
        }
423
424
        return $this->compatibilityOption;
425
    }
426
427
    /**
428
     * Utility class to parse servlet_address,
429
     * i.e 'http://localhost:8080/javabridge-bundle/java/servlet.phpjavabridge'.
430
     *
431
     * @throws Exception\InvalidArgumentException
432
     *
433
     * @param string $servlet_address
434 27
     *
435
     * @return array associative array with 'servlet_host' and 'servlet_uri'
436 27
     */
437
    public static function parseServletUrl(string $servlet_address): array
438 27
    {
439 1
        $url = parse_url($servlet_address);
440
441
        if ($url === false || !isset($url['host'])) {
442 26
            throw new Exception\InvalidArgumentException(__METHOD__." Cannot parse url '$servlet_address'");
443 26
        }
444 26
445
        $scheme = '';
446 26
        if (isset($url['scheme'])) {
447 26
            $scheme = $url['scheme'] === 'https' ? 'ssl://' : $scheme;
448 26
        }
449
        $host = $url['host'];
450
        $port = $url['port'];
451 26
        $path = $url['path'] ?? '';
452 26
453 26
        $infos = [
454 26
            'servlet_host' => "{$scheme}{$host}:{$port}",
455
            'servlet_uri' => $path,
456
            'auth_user' => $url['user'] ?? null,
457 26
            'auth_password' => $url['pass'] ?? null,
458
        ];
459
460
        return $infos;
461
    }
462
463 21
    /**
464
     * For compatibility usage all constants have been kept.
465 21
     */
466 21
    protected function bootstrap($options = []): void
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

466
    protected function bootstrap(/** @scrutinizer ignore-unused */ $options = []): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
467
    {
468
        register_shutdown_function(['Soluble\Japha\Bridge\Driver\Pjb62\PjbProxyClient', 'unregisterInstance']);
469
    }
470
471
    /**
472
     * Return options.
473 2
     *
474
     * @return ArrayObject
475 2
     */
476
    public function getOptions(): ArrayObject
477
    {
478
        return $this->options;
479
    }
480
481
    /**
482
     * Return specific option.
483
     *
484
     * @return mixed
485 21
     */
486
    public function getOption(string $name)
487 21
    {
488 1
        if (!array_key_exists($name, $this->options)) {
0 ignored issues
show
Bug introduced by
$this->options of type ArrayObject is incompatible with the type array expected by parameter $search of array_key_exists(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

488
        if (!array_key_exists($name, /** @scrutinizer ignore-type */ $this->options)) {
Loading history...
489
            throw new Exception\InvalidArgumentException("Option '$name' does not exists'");
490
        }
491 21
492
        return $this->options[$name];
493
    }
494
495
    public function getLogger(): LoggerInterface
496
    {
497 20
        return $this->logger;
498
    }
499 20
500
    /**
501
     * @throws Exception\BrokenConnectionException|Exception\AuthenticationException
502
     */
503
    public static function unregisterAndThrowBrokenConnectionException(string $message = null, int $code = null): void
504
    {
505 20
        if (self::$instance !== null) {
506
            $message = $message ?? 'undefined messsage';
507
508
            switch ($code) {
509
                case 401:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
510 20
                    $exception = new Exception\AuthenticationException(sprintf(
511 20
                        'Java bridge authentication failure: code: %s',
512 14
                        $code
513
                    ));
514
                    break;
515
                default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
516
                    $exception = new Exception\BrokenConnectionException(sprintf(
517
                        'Java bridge broken connection: "%s" (code: %s)',
518
                        $message,
519 20
                        $code
520 20
                    ));
521
            }
522 20
            try {
523 14
                self::$instance->getLogger()->critical(sprintf(
524
                    '[soluble-japha] BrokenConnectionException to "%s": "%s" (code: "%s")',
525
                    self::$instance->getOption('servlet_address'),
526
                    $message,
527
                    $code ?? '?'
528 20
                ));
529 20
            } catch (\Throwable $e) {
530 20
                // discard logger errors
531
            }
532 20
533
            self::unregisterInstance();
534
            throw $exception;
535
        }
536
    }
537
538
    /**
539
     * Clean up PjbProxyClient instance.
540
     */
541
    public static function unregisterInstance(): void
542
    {
543
        if (!self::$unregistering && self::$client !== null) {
544
            self::$unregistering = true;
545
546
            if (self::$client->preparedToSendBuffer) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::client->preparedToSendBuffer of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
547
                self::$client->sendBuffer .= self::$client->preparedToSendBuffer;
548
            }
549
550
            try {
551
                self::$client->sendBuffer .= self::$client->protocol->getKeepAlive();
552
                self::$client->protocol->flush();
553
            } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
554
            }
555
556
            // TODO MUST TEST, IT WAS REMOVED FROM FUNCTION
557
            // BECAUSE IT SIMPLY LOOKS LIKE THE LINES BEFORE
558
            // ADDED AN IF TO CHECK THE CHANNEL In CASE OF
559
            //
560
            if (isset(self::$client->protocol->handler->channel) &&
561
                false === strpos(get_class(self::getClient()->protocol->handler->channel), '/EmptyChannel/')) {
562
                try {
563
                    self::$client->protocol->keepAlive();
564
                } catch (\Throwable $e) {
565
                    // silently discard exceptions when unregistering
566
                }
567
            }
568
569
            self::$client = null;
570
            self::$instance = null;
571
            self::$instanceOptionsKey = null;
572
            self::$unregistering = false;
573
        }
574
    }
575
}
576