vendor/mark-gerarts/auto-mapper-plus/src/AutoMapper.php line 202

Open in your IDE?
  1. <?php
  2. namespace AutoMapperPlus;
  3. use AutoMapperPlus\Configuration\AutoMapperConfig;
  4. use AutoMapperPlus\Configuration\AutoMapperConfigInterface;
  5. use AutoMapperPlus\Configuration\MappingInterface;
  6. use AutoMapperPlus\Exception\AutoMapperPlusException;
  7. use AutoMapperPlus\Exception\InvalidArgumentException;
  8. use AutoMapperPlus\Exception\UnregisteredMappingException;
  9. use AutoMapperPlus\Exception\UnsupportedSourceTypeException;
  10. use AutoMapperPlus\MappingOperation\ContextAwareOperation;
  11. use AutoMapperPlus\MappingOperation\MapperAwareOperation;
  12. /**
  13.  * Class AutoMapper
  14.  *
  15.  * @package AutoMapperPlus
  16.  */
  17. class AutoMapper implements AutoMapperInterface
  18. {
  19.     public const SOURCE_STACK_CONTEXT '__source_stack';
  20.     public const DESTINATION_STACK_CONTEXT '__destination_stack';
  21.     public const PROPERTY_STACK_CONTEXT '__property_stack';
  22.     public const DESTINATION_CONTEXT '__destination';
  23.     public const DESTINATION_CLASS_CONTEXT '__destination_class';
  24.     /**
  25.      * @var AutoMapperConfigInterface
  26.      */
  27.     private $autoMapperConfig;
  28.     /**
  29.      * AutoMapper constructor.
  30.      *
  31.      * @param AutoMapperConfigInterface $autoMapperConfig
  32.      */
  33.     public function __construct(AutoMapperConfigInterface $autoMapperConfig null)
  34.     {
  35.         $this->autoMapperConfig $autoMapperConfig ?: new AutoMapperConfig();
  36.     }
  37.     /**
  38.      * @inheritdoc
  39.      */
  40.     public static function initialize(callable $configurator): AutoMapperInterface
  41.     {
  42.         $mapper = new static;
  43.         $configurator($mapper->autoMapperConfig);
  44.         return $mapper;
  45.     }
  46.     private function push($key$value, &$context)
  47.     {
  48.         if (!array_key_exists($key$context)) {
  49.             $stack = [];
  50.         } else {
  51.             $stack $context[$key];
  52.         }
  53.         $stack[] = $value;
  54.         $context[$key] = $stack;
  55.     }
  56.     private function pop($key, &$context)
  57.     {
  58.         array_pop($context[$key]);
  59.     }
  60.     /**
  61.      * @inheritdoc
  62.      */
  63.     public function map($sourcestring $destinationClass, array $context = [])
  64.     {
  65.         if ($source === null) {
  66.             return null;
  67.         }
  68.         if (\is_object($source)) {
  69.             $sourceClass \get_class($source);
  70.         } else {
  71.             $sourceClass \gettype($source);
  72.             if ($sourceClass !== DataType::ARRAY) {
  73.                 throw UnsupportedSourceTypeException::fromType($sourceClass);
  74.             }
  75.         }
  76.         $context[self::DESTINATION_CLASS_CONTEXT] = $destinationClass;
  77.         $mapping $this->getMapping($sourceClass$destinationClass);
  78.         if ($mapping->providesCustomMapper()) {
  79.             $customMapper $this->getCustomMapper($mapping);
  80.             return $customMapper->map($source$destinationClass$context);
  81.         }
  82.         if ($mapping->hasCustomConstructor()) {
  83.             $destinationObject $mapping->getCustomConstructor()(
  84.                 $source,
  85.                 $this,
  86.                 $context
  87.             );
  88.         } elseif (interface_exists($destinationClass)) {
  89.             // If we're mapping to an interface a valid custom constructor has
  90.             // to be provided. Otherwise we can't know what to do.
  91.             $message 'Mapping to an interface is not possible. Please '
  92.                 'provide a concrete class or use mapToObject instead.';
  93.             throw new AutoMapperPlusException($message);
  94.         } else {
  95.             $destinationObject = new $destinationClass;
  96.         }
  97.         $context[self::DESTINATION_CONTEXT] = $destinationObject;
  98.         $this->push(self::SOURCE_STACK_CONTEXT$source$context);
  99.         $this->push(self::DESTINATION_STACK_CONTEXT$destinationObject$context);
  100.         try {
  101.             return $this->doMap($source$destinationObject$mapping$context);
  102.         } finally {
  103.             $this->pop(self::DESTINATION_STACK_CONTEXT$context);
  104.             $this->pop(self::SOURCE_STACK_CONTEXT$context);
  105.         }
  106.     }
  107.     /**
  108.      * @inheritdoc
  109.      */
  110.     public function mapMultiple(
  111.         $sourceCollection,
  112.         string $destinationClass,
  113.         array $context = []
  114.     ): array
  115.     {
  116.         if (!is_iterable($sourceCollection)) {
  117.             throw new InvalidArgumentException(
  118.                 'The collection provided should be iterable.'
  119.             );
  120.         }
  121.         $mappedResults = [];
  122.         foreach ($sourceCollection as $source) {
  123.             $mappedResults[] = $this->map($source$destinationClass$context);
  124.         }
  125.         return $mappedResults;
  126.     }
  127.     /**
  128.      * @inheritdoc
  129.      */
  130.     public function mapToObject($source$destination, array $context = [])
  131.     {
  132.         if (\is_object($source)) {
  133.             $sourceClass \get_class($source);
  134.         } else {
  135.             $sourceClass \gettype($source);
  136.             if ($sourceClass !== DataType::ARRAY) {
  137.                 throw UnsupportedSourceTypeException::fromType($sourceClass);
  138.             }
  139.         }
  140.         $destinationClass \get_class($destination);
  141.         $context[self::DESTINATION_CONTEXT] = $destination;
  142.         $context[self::DESTINATION_CLASS_CONTEXT] = $destinationClass;
  143.         $this->push(self::SOURCE_STACK_CONTEXT$source$context);
  144.         $this->push(self::DESTINATION_STACK_CONTEXT$destination$context);
  145.         try {
  146.             $mapping $this->getMapping($sourceClass$destinationClass);
  147.             if ($mapping->providesCustomMapper()) {
  148.                 return $this->getCustomMapper($mapping)->mapToObject(
  149.                     $source,
  150.                     $destination,
  151.                     $context
  152.                 );
  153.             }
  154.             return $this->doMap(
  155.                 $source,
  156.                 $destination,
  157.                 $mapping,
  158.                 $context
  159.             );
  160.         } finally {
  161.             $this->pop(self::DESTINATION_STACK_CONTEXT$context);
  162.             $this->pop(self::SOURCE_STACK_CONTEXT$context);
  163.         }
  164.     }
  165.     /**
  166.      * Performs the actual transferring of properties.
  167.      *
  168.      * @param $source
  169.      * @param $destination
  170.      * @param MappingInterface $mapping
  171.      * @param array $context
  172.      * @return mixed
  173.      *   The destination object with mapped properties.
  174.      */
  175.     protected function doMap(
  176.         $source,
  177.         $destination,
  178.         MappingInterface $mapping,
  179.         array $context = []
  180.     )
  181.     {
  182.         $propertyNames $mapping->getTargetProperties($destination$source);
  183.         foreach ($propertyNames as $propertyName) {
  184.             $this->push(self::PROPERTY_STACK_CONTEXT$propertyName$context);
  185.             try {
  186.                 $mappingOperation $mapping->getMappingOperationFor($propertyName);
  187.                 if ($mappingOperation instanceof MapperAwareOperation) {
  188.                     $mappingOperation->setMapper($this);
  189.                 }
  190.                 if ($mappingOperation instanceof ContextAwareOperation) {
  191.                     $mappingOperation->setContext($context);
  192.                 }
  193.                 $mappingOperation->mapProperty(
  194.                     $propertyName,
  195.                     $source,
  196.                     $destination
  197.                 );
  198.             } finally {
  199.                 $this->pop(self::PROPERTY_STACK_CONTEXT$context);
  200.             }
  201.         }
  202.         return $destination;
  203.     }
  204.     /**
  205.      * @inheritdoc
  206.      */
  207.     public function getConfiguration(): AutoMapperConfigInterface
  208.     {
  209.         return $this->autoMapperConfig;
  210.     }
  211.     /**
  212.      * @param string $sourceClass
  213.      * @param string $destinationClass
  214.      * @return MappingInterface
  215.      * @throws UnregisteredMappingException
  216.      */
  217.     protected function getMapping
  218.     (
  219.         string $sourceClass,
  220.         string $destinationClass
  221.     ): MappingInterface
  222.     {
  223.         $mapping $this->autoMapperConfig->getMappingFor(
  224.             $sourceClass,
  225.             $destinationClass
  226.         );
  227.         if ($mapping) {
  228.             return $mapping;
  229.         }
  230.         throw UnregisteredMappingException::fromClasses(
  231.             $sourceClass,
  232.             $destinationClass
  233.         );
  234.     }
  235.     /**
  236.      * @param MappingInterface $mapping
  237.      *
  238.      * @return MapperInterface|null
  239.      */
  240.     private function getCustomMapper(MappingInterface $mapping): ?MapperInterface
  241.     {
  242.         $customMapper $mapping->getCustomMapper();
  243.         if ($customMapper instanceof MapperAwareOperation) {
  244.             $customMapper->setMapper($this);
  245.         }
  246.         return $customMapper;
  247.     }
  248. }