diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index da91338..c50161c 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -15,7 +15,7 @@ jobs: name: PHP ${{ matrix.php-versions }} Test on ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install PHP uses: shivammathur/setup-php@v2 with: @@ -27,7 +27,7 @@ jobs: # run: composer validate --strict - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: vendor key: ${{ runner.os }}-php-${{ matrix.php-versions }}-${{ hashFiles('**/composer.lock') }} diff --git a/README.md b/README.md index d54a765..b1442a2 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,11 @@ $msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|", ['SEGMENT_SEPARATOR' => // Segment with separator character (~) creates sub-arrays containing each sub-segment $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1"); // Creates [[3,0], [4,1]] -// To create a single array instead, pass 'true' as 5th argument. This may be used to retain behavior from previous releases +// To create a single array instead, pass 'true' as 6th argument. This may be used to retain behavior from previous releases // Notice: Since this leads to a non-standard behavior, it may be removed in future $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", null, false, false, true, true); // Creates ['3', '0~4', '1'] +// or +$message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", doNotSplitRepetition: true); // Creates ['3', '0~4', '1'] ``` ### Send messages to remote listeners diff --git a/src/HL7.php b/src/HL7.php index 559d4a6..f24feb3 100644 --- a/src/HL7.php +++ b/src/HL7.php @@ -4,6 +4,7 @@ namespace Aranyasen; +use Exception; use InvalidArgumentException; use Aranyasen\HL7\Message; use Aranyasen\HL7\Segments\MSH; @@ -18,10 +19,7 @@ */ class HL7 { - /** - * Holds all global HL7 settings. - */ - protected $hl7Globals; + protected array $hl7Globals; /** * Create a new instance of the HL7 factory, and set global @@ -43,8 +41,8 @@ public function __construct() * Create a new Message, using the global HL7 variables as defaults. * * @param string|null $msgStr Text representation of an HL7 message - * @throws \Exception - * @throws \InvalidArgumentException + * @throws Exception + * @throws InvalidArgumentException */ public function createMessage(string $msgStr = null): Message { @@ -53,7 +51,7 @@ public function createMessage(string $msgStr = null): Message /** * Create a new MSH segment, using the global HL7 variables as defaults. - * @throws \Exception + * @throws Exception */ public function createMSH(): MSH { @@ -65,11 +63,11 @@ public function createMSH(): MSH * * @param string $value Component separator char. * @return bool true if value has been set. - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setComponentSeparator(string $value): bool { - if (\strlen($value) !== 1) { + if (strlen($value) !== 1) { throw new InvalidArgumentException("Parameter should be of single character. Received: '$value'"); } @@ -82,11 +80,11 @@ public function setComponentSeparator(string $value): bool * * @param string $value Subcomponent separator char. * @return bool true if value has been set. - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setSubcomponentSeparator(string $value): bool { - if (\strlen($value) !== 1) { + if (strlen($value) !== 1) { throw new InvalidArgumentException("Parameter should be of single character. Received: '$value'"); } @@ -99,11 +97,11 @@ public function setSubcomponentSeparator(string $value): bool * * @param string $value Repetition separator char. * @return bool true if value has been set. - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setRepetitionSeparator(string $value): bool { - if (\strlen($value) !== 1) { + if (strlen($value) !== 1) { throw new InvalidArgumentException("Parameter should be of single character. Received: '$value'"); } @@ -116,11 +114,11 @@ public function setRepetitionSeparator(string $value): bool * * @param string $value Field separator char. * @return bool true if value has been set. - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setFieldSeparator(string $value): bool { - if (\strlen($value) !== 1) { + if (strlen($value) !== 1) { throw new InvalidArgumentException("Parameter should be of single character. Received: '$value'"); } @@ -133,11 +131,11 @@ public function setFieldSeparator(string $value): bool * * @param string $value separator char. * @return bool true if value has been set. - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setSegmentSeparator(string $value): bool { - if (\strlen($value) !== 1) { + if (strlen($value) !== 1) { throw new InvalidArgumentException("Parameter should be of single character. Received: '$value'"); } @@ -149,26 +147,20 @@ public function setSegmentSeparator(string $value): bool * * @param string $value Escape character. * @return bool true if value has been set. - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setEscapeCharacter(string $value): bool { - if (\strlen($value) !== 1) { + if (strlen($value) !== 1) { throw new InvalidArgumentException("Parameter should be of single character. Received: '$value'"); } return $this->setGlobal('ESCAPE_CHARACTER', $value); } - /** - * Set the HL7 version to be used by the factory. Default 2.3 - * - * @param string HL7 version character. - * @return bool true if value has been set. - */ - public function setHL7Version(string $value): bool + public function setHL7Version(string $hl7Version): bool { - return $this->setGlobal('HL7_VERSION', $value); + return $this->setGlobal('HL7_VERSION', $hl7Version); } /** diff --git a/src/HL7/Connection.php b/src/HL7/Connection.php index ae1a0be..e2b5f4b 100644 --- a/src/HL7/Connection.php +++ b/src/HL7/Connection.php @@ -7,7 +7,7 @@ use Aranyasen\Exceptions\HL7ConnectionException; use Aranyasen\Exceptions\HL7Exception; use Exception; -use ReflectionException; +use Socket; /** * Usage: @@ -37,10 +37,14 @@ */ class Connection { - protected $socket; - protected $timeout; - protected $MESSAGE_PREFIX; - protected $MESSAGE_SUFFIX; + protected Socket $socket; + protected int $timeout; + + /** # Octal 13 (Hex: 0B): Vertical Tab */ + protected string $MESSAGE_PREFIX = "\013"; + + /** # 34 (Hex: 1C): file separator character, 15 (Hex: 0D): Carriage return */ + protected string $MESSAGE_SUFFIX = "\034\015"; /** * Creates a connection to a HL7 server, or throws exception when a connection could not be established. @@ -56,8 +60,6 @@ public function __construct(string $host, int $port, int $timeout = 10) throw new HL7ConnectionException('Please install ext-sockets to run Connection'); } $this->setSocket($host, $port, $timeout); - $this->MESSAGE_PREFIX = "\013"; # Octal 13 (Hex: 0B): Vertical Tab - $this->MESSAGE_SUFFIX = "\034\015"; # 34 (Hex: 1C): file separator character, 15 (Hex: 0D): Carriage return $this->timeout = $timeout; } @@ -161,9 +163,9 @@ public function send(Message $msg, string $responseCharEncoding = 'UTF-8', bool } /* - * Return the socket opened/used by this class + * Return the raw socket opened/used by this class */ - public function getSocket() + public function getSocket(): Socket { return $this->socket; } diff --git a/src/HL7/Message.php b/src/HL7/Message.php index 90cc146..9232ebb 100644 --- a/src/HL7/Message.php +++ b/src/HL7/Message.php @@ -15,34 +15,24 @@ * ```php $msg->getSegmentByIndex(0) ``` * * The segment separator defaults to \015. To change this, set the global variable $SEGMENT_SEPARATOR. - * - * @author Aranya Sen */ class Message { use MessageHelpersTrait; - - /** @var array Array holding all segments of this message */ protected array $segments = []; - /** - * local value for segment separator - */ - protected $segmentSeparator; - /** - * @var bool Is the bar (|) at the end of each segment required? Default: Yes. - */ - protected $segmentEndingBar; - protected $fieldSeparator; - protected $componentSeparator; - protected $subcomponentSeparator; - protected $repetitionSeparator; - protected $escapeChar; - protected $hl7Version; + protected string $segmentSeparator; + protected bool $segmentEndingBar; # true, if '|' at end of each segment is needed + protected string $fieldSeparator; + protected string $componentSeparator; + protected string $subcomponentSeparator; + protected string $repetitionSeparator; + protected string $escapeChar; + protected string $hl7Version; - /** @var bool|null $doNotSplitRepetition */ - protected $doNotSplitRepetition; + // Split (or not) repeated subfields joined by ~. E.g. if true, parses 3^0~4^1 to [3, '0~4', 1] + protected bool $doNotSplitRepetition; /** * Constructor for Message. Consider using the HL7 factory to obtain a message instead. @@ -77,7 +67,7 @@ public function __construct( ) { // Control characters and other HL7 properties $this->segmentSeparator = $hl7Globals['SEGMENT_SEPARATOR'] ?? '\n'; - $this->segmentEndingBar = $hl7Globals['SEGMENT_ENDING_BAR'] ?? true; // '|' at end of each segment + $this->segmentEndingBar = $hl7Globals['SEGMENT_ENDING_BAR'] ?? true; $this->fieldSeparator = $hl7Globals['FIELD_SEPARATOR'] ?? '|'; $this->componentSeparator = $hl7Globals['COMPONENT_SEPARATOR'] ?? '^'; $this->subcomponentSeparator = $hl7Globals['SUBCOMPONENT_SEPARATOR'] ?? '&'; @@ -85,7 +75,7 @@ public function __construct( $this->escapeChar = $hl7Globals['ESCAPE_CHAR'] ?? '\\'; $this->hl7Version = $hl7Globals['HL7_VERSION'] ?? '2.3'; - $this->doNotSplitRepetition = $doNotSplitRepetition; + $this->doNotSplitRepetition = (bool) $doNotSplitRepetition; if ($resetIndices) { $this->resetSegmentIndices(); @@ -245,7 +235,7 @@ public function removeSegmentsByName(string $segmentName): int * control characters and hl7 version, based on MSH(1), MSH(2) and MSH(12). * * @param int $index Index where segment is set - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function setSegment(Segment $segment, int $index): bool { @@ -272,9 +262,9 @@ protected function resetCtrl(Segment $segment): bool } if (preg_match('/(.)(.)(.)(.)/', (string) $segment->getField(2), $matches)) { - $this->componentSeparator = $matches[1]; - $this->repetitionSeparator = $matches[2]; - $this->escapeChar = $matches[3]; + $this->componentSeparator = $matches[1]; + $this->repetitionSeparator = $matches[2]; + $this->escapeChar = $matches[3]; $this->subcomponentSeparator = $matches[4]; } @@ -377,10 +367,7 @@ public function resetSegmentIndices(): void } } - /** - * @return array|string - */ - private function extractComponentsFromField(string $field, bool $keepEmptySubFields) + private function extractComponentsFromField(string $field, bool $keepEmptySubFields): array|string { $pregFlags = $keepEmptySubFields ? 0 diff --git a/src/HL7/Messages/ACK.php b/src/HL7/Messages/ACK.php index 6f8ed06..93f98cd 100644 --- a/src/HL7/Messages/ACK.php +++ b/src/HL7/Messages/ACK.php @@ -7,10 +7,12 @@ use Aranyasen\HL7\Message; use Aranyasen\HL7\Segments\MSA; use Aranyasen\HL7\Segments\MSH; +use Exception; +use InvalidArgumentException; class ACK extends Message { - protected $ACK_TYPE; + protected string $ACK_TYPE; /** * Usage: @@ -24,8 +26,8 @@ class ACK extends Message * @param Message|null $req * @param MSH|null $reqMsh * @param array|null $hl7Globals Set control characters or HL7 properties. e.g., ['HL7_VERSION' => '2.5'] - * @throws \Exception - * @throws \InvalidArgumentException + * @throws Exception + * @throws InvalidArgumentException */ public function __construct(Message $req = null, MSH $reqMsh = null, array $hl7Globals = null) { diff --git a/src/HL7/Segment.php b/src/HL7/Segment.php index 4d353c3..ab02ad7 100644 --- a/src/HL7/Segment.php +++ b/src/HL7/Segment.php @@ -30,18 +30,18 @@ class Segment * @author Aranya Sen * @param string $name Name of the segment * @param array|null $fields Fields for segment - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function __construct(string $name, array $fields = null) { // Is the name 3 upper case characters? - if ((!$name) || (\strlen($name) !== 3) || (strtoupper($name) !== $name)) { + if ((!$name) || (strlen($name) !== 3) || (strtoupper($name) !== $name)) { throw new InvalidArgumentException("Segment name '$name' should be 3 characters and in uppercase"); } $this->fields[0] = $name; - if (\is_array($fields)) { + if (is_array($fields)) { foreach ($fields as $i => $value) { $this->setField($i + 1, $value); } @@ -66,9 +66,8 @@ public function __construct(string $name, array $fields = null) * If values are not provided at all, the method will just return. * * @param int $index Index to set - * @param string|array $value Value for field */ - public function setField(int $index, $value = ''): bool + public function setField(int $index, string|int|array $value = ''): bool { if ($index === 0) { // Do not allow changing 0th index, which is the name of the segment return false; @@ -79,7 +78,7 @@ public function setField(int $index, $value = ''): bool } // Fill in the blanks... - for ($i = \count($this->fields); $i < $index; $i++) { + for ($i = count($this->fields); $i < $index; $i++) { $this->fields[$i] = ''; } @@ -119,11 +118,8 @@ public function clearField(int $index): void * ```php * $field = $seg->getField(9); // Returns a string/null/array depending on what the 9th field is. * ``` - * - * @param int $index Index of field - * @return null|string|array The value of the field */ - public function getField(int $index) + public function getField(int $index): array|string|int|null { return $this->fields[$index] ?? null; } @@ -135,7 +131,7 @@ public function getField(int $index) */ public function size(): int { - return \count($this->fields) - 1; + return count($this->fields) - 1; } /** @@ -151,9 +147,9 @@ public function size(): int public function getFields(int $from = 0, int $to = null): array { if (!$to) { - $to = \count($this->fields); + $to = count($this->fields); } - return \array_slice($this->fields, $from, $to - $from + 1); + return array_slice($this->fields, $from, $to - $from + 1); } /** diff --git a/src/HL7/Segments/MSH.php b/src/HL7/Segments/MSH.php index 66a3f22..a84c69c 100644 --- a/src/HL7/Segments/MSH.php +++ b/src/HL7/Segments/MSH.php @@ -5,6 +5,8 @@ namespace Aranyasen\HL7\Segments; use Aranyasen\HL7\Segment; +use Exception; +use InvalidArgumentException; /** * MSH (message header) segment class @@ -36,8 +38,7 @@ class MSH extends Segment * * @param null|array $fields * @param null|array $hl7Globals - * @throws \InvalidArgumentException - * @throws \Exception + * @throws InvalidArgumentException|Exception */ public function __construct(array $fields = null, array $hl7Globals = null) { @@ -63,7 +64,7 @@ public function __construct(array $fields = null, array $hl7Globals = null) $this->setField(2, '^~\\&'); $this->setVersionId('2.3'); } - $this->setDateTimeOfMessage(date('YmdHis', time())); + $this->setDateTimeOfMessage(date('YmdHis')); $this->setMessageControlId($this->getDateTimeOfMessage() . random_int(10000, 99999)); } @@ -279,7 +280,7 @@ public function getTriggerEvent(int $position = 9): string return false; } - public function getMessageControlId(int $position = 10) + public function getMessageControlId(int $position = 10): string { return $this->getField($position); } @@ -291,9 +292,8 @@ public function getProcessingId(int $position = 11) /** * Get HL7 version, e.g. 2.1, 2.3, 3.0 etc. - * @return array|null|string */ - public function getVersionId(int $position = 12) + public function getVersionId(int $position = 12): string { return $this->getField($position); } diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 5303a03..4fc648e 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -14,7 +14,7 @@ class ConnectionTest extends TestCase { use Hl7ListenerTrait; - protected $port = 12011; + protected int $port = 12011; protected function tearDown(): void { @@ -26,10 +26,12 @@ protected function tearDown(): void * @test * @throws HL7ConnectionException * @throws HL7Exception - * @throws \ReflectionException */ public function a_message_can_be_sent_to_a_hl7_server(): void { + if (!extension_loaded('pcntl')) { + self::markTestSkipped("Extension pcntl_fork is not loaded"); + } $pid = pcntl_fork(); if ($pid === -1) { throw new RuntimeException('Could not fork'); @@ -57,10 +59,12 @@ public function a_message_can_be_sent_to_a_hl7_server(): void * @test * @throws HL7ConnectionException * @throws HL7Exception - * @throws \ReflectionException */ public function do_not_wait_for_ack_after_sending_if_corresponding_parameter_is_set(): void { + if (!extension_loaded('pcntl')) { + self::markTestSkipped("Extension pcntl_fork is not loaded"); + } $pid = pcntl_fork(); if ($pid === -1) { throw new RuntimeException('Could not fork'); diff --git a/tests/Hl7ListenerTrait.php b/tests/Hl7ListenerTrait.php index 04a4760..3d382ad 100644 --- a/tests/Hl7ListenerTrait.php +++ b/tests/Hl7ListenerTrait.php @@ -1,11 +1,13 @@ MESSAGE_SUFFIX . '$/', '', $hl7); $msg = new Message(trim($hl7), null, true, true); - $ack = new ACK($msg); - return $ack->toString(); + return (new ACK($msg))->toString(); } /** * Clean up temporary pipe file generated for testing */ - private function deletePipe() + private function deletePipe(): void { if (file_exists($this->pipeName)) { unlink($this->pipeName); diff --git a/tests/MessageTest.php b/tests/MessageTest.php index 946c58d..0f56491 100644 --- a/tests/MessageTest.php +++ b/tests/MessageTest.php @@ -459,9 +459,9 @@ public function message_can_be_verified_as_empty(): void } /** @test */ - public function message_with_repetition_separator(): void + public function field_with_repetition_separator_splits_into_array_by_default(): void { - $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1"); + $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1"); // Repetition separator is ~ $pid = $message->getSegmentByIndex(1); $patientIdentifierList = $pid->getField(3); self::assertIsArray($patientIdentifierList); @@ -472,9 +472,22 @@ public function message_with_repetition_separator(): void } /** @test */ - public function separation_character_can_be_ignored(): void + public function field_with_repetition_separator_can_be_split_into_array(): void { - $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", null, false, false, true, true); + $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", doNotSplitRepetition: false); + $pid = $message->getSegmentByIndex(1); + $patientIdentifierList = $pid->getField(3); + self::assertIsArray($patientIdentifierList); + self::assertSame('3', $patientIdentifierList[0][0]); + self::assertSame('0', $patientIdentifierList[0][1]); + self::assertSame('4', $patientIdentifierList[1][0]); + self::assertSame('1', $patientIdentifierList[1][1]); + } + + /** @test */ + public function repetition_separation_character_can_be_ignored(): void + { + $message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", doNotSplitRepetition: true); $pid = $message->getSegmentByIndex(1); $patientIdentifierList = $pid->getField(3); self::assertIsArray($patientIdentifierList);