MessageValidator.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\MessageQueue;
  7. use Doctrine\Instantiator\Exception\InvalidArgumentException;
  8. use Magento\Framework\Exception\LocalizedException;
  9. use Magento\Framework\Phrase;
  10. use Magento\Framework\Communication\ConfigInterface as CommunicationConfig;
  11. /**
  12. * Class MessageValidator to validate message with topic schema
  13. *
  14. */
  15. class MessageValidator
  16. {
  17. /**
  18. * @var CommunicationConfig
  19. */
  20. private $communicationConfig;
  21. /**
  22. * Identify message data schema by topic.
  23. *
  24. * @param string $topic
  25. * @param bool $requestType
  26. * @return array
  27. * @throws LocalizedException
  28. */
  29. protected function getTopicSchema($topic, $requestType)
  30. {
  31. $topicConfig = $this->getCommunicationConfig()->getTopic($topic);
  32. if ($topicConfig === null) {
  33. throw new LocalizedException(new Phrase('Specified topic "%topic" is not declared.', ['topic' => $topic]));
  34. }
  35. if ($requestType) {
  36. return [
  37. 'schema_type' => $topicConfig[CommunicationConfig::TOPIC_REQUEST_TYPE],
  38. 'schema_value' => $topicConfig[CommunicationConfig::TOPIC_REQUEST]
  39. ];
  40. } else {
  41. return [
  42. 'schema_type' => isset($topicConfig[CommunicationConfig::TOPIC_RESPONSE])
  43. ? CommunicationConfig::TOPIC_REQUEST_TYPE_CLASS
  44. : null,
  45. 'schema_value' => $topicConfig[CommunicationConfig::TOPIC_RESPONSE]
  46. ];
  47. }
  48. }
  49. /**
  50. * Validate message according to the format associated with its topic
  51. *
  52. * @param string $topic
  53. * @param mixed $message
  54. * @param bool $requestType
  55. * @return void
  56. * @throws InvalidArgumentException
  57. */
  58. public function validate($topic, $message, $requestType = true)
  59. {
  60. $topicSchema = $this->getTopicSchema($topic, $requestType);
  61. if ($topicSchema['schema_type'] == CommunicationConfig::TOPIC_REQUEST_TYPE_CLASS) {
  62. $messageDataType = $topicSchema['schema_value'];
  63. $this->validateMessage($message, $messageDataType, $topic);
  64. } else {
  65. /** Validate message according to the method signature associated with the message topic */
  66. $message = (array)$message;
  67. $isIndexedArray = array_keys($message) === range(0, count($message) - 1);
  68. foreach ($topicSchema['schema_value'] as $methodParameterMeta) {
  69. $paramName = $methodParameterMeta[CommunicationConfig::SCHEMA_METHOD_PARAM_NAME];
  70. $paramType = $methodParameterMeta[CommunicationConfig::SCHEMA_METHOD_PARAM_TYPE];
  71. if ($isIndexedArray) {
  72. $paramPosition = $methodParameterMeta[CommunicationConfig::SCHEMA_METHOD_PARAM_POSITION];
  73. if (isset($message[$paramPosition])) {
  74. $this->validateMessage($message[$paramPosition], $paramType, $topic);
  75. }
  76. } else {
  77. if (isset($message[$paramName])) {
  78. if (isset($message[$paramName])) {
  79. $this->validateMessage($message[$paramName], $paramType, $topic);
  80. }
  81. }
  82. }
  83. }
  84. }
  85. }
  86. /**
  87. * @param string $message
  88. * @param string $messageType
  89. * @param string $topic
  90. * @return void
  91. */
  92. protected function validateMessage($message, $messageType, $topic)
  93. {
  94. if (preg_match_all("/\\\\/", $messageType)) {
  95. $this->validateClassType($message, $messageType, $topic);
  96. } else {
  97. $this->validatePrimitiveType($message, $messageType, $topic);
  98. }
  99. }
  100. /**
  101. * @param string $message
  102. * @param string $messageType
  103. * @param string $topic
  104. * @return void
  105. */
  106. protected function validatePrimitiveType($message, $messageType, $topic)
  107. {
  108. $compareType = $messageType;
  109. $realType = $this->getRealType($message);
  110. if ($realType == 'array' && count($message) == 0) {
  111. return;
  112. } elseif ($realType == 'array' && count($message) > 0) {
  113. $realType = $this->getRealType($message[0]);
  114. $compareType = preg_replace('/\[\]/', '', $messageType);
  115. }
  116. if ($realType !== $compareType) {
  117. throw new InvalidArgumentException(
  118. new Phrase(
  119. 'Data in topic "%topic" must be of type "%expectedType". '
  120. . '"%actualType" given.',
  121. [
  122. 'topic' => $topic,
  123. 'expectedType' => $messageType,
  124. 'actualType' => $this->getRealType($message)
  125. ]
  126. )
  127. );
  128. }
  129. }
  130. /**
  131. * @param string $message
  132. * @param string $messageType
  133. * @param string $topic
  134. * @return void
  135. */
  136. protected function validateClassType($message, $messageType, $topic)
  137. {
  138. $origMessage = $message;
  139. $compareType = $messageType;
  140. $realType = $this->getRealType($message);
  141. if ($realType == 'array' && count($message) == 0) {
  142. return;
  143. } elseif ($realType == 'array' && count($message) > 0) {
  144. $message = $message[0];
  145. $compareType = preg_replace('/\[\]/', '', $messageType);
  146. }
  147. if (!($message instanceof $compareType)) {
  148. throw new InvalidArgumentException(
  149. new Phrase(
  150. 'Data in topic "%topic" must be of type "%expectedType". '
  151. . '"%actualType" given.',
  152. [
  153. 'topic' => $topic,
  154. 'expectedType' => $messageType,
  155. 'actualType' => $this->getRealType($origMessage)
  156. ]
  157. )
  158. );
  159. }
  160. }
  161. /**
  162. * @param string $message
  163. * @return string
  164. */
  165. private function getRealType($message)
  166. {
  167. $type = is_object($message) ? get_class($message) : gettype($message);
  168. $type = $type == 'boolean' ? 'bool' : $type;
  169. $type = $type == 'double' ? 'float' : $type;
  170. return $type == "integer" ? "int" : $type;
  171. }
  172. /**
  173. * Get communication config.
  174. *
  175. * @return CommunicationConfig
  176. *
  177. * @deprecated 102.0.1
  178. */
  179. private function getCommunicationConfig()
  180. {
  181. if ($this->communicationConfig === null) {
  182. $this->communicationConfig = \Magento\Framework\App\ObjectManager::getInstance()->get(
  183. CommunicationConfig::class
  184. );
  185. }
  186. return $this->communicationConfig;
  187. }
  188. }