<?php
namespace App\Service;
use App\Entity\User;
use Doctrine\DBAL\LockMode;
use Psr\Log\LoggerInterface;
use App\Misc\AppMappingOperation;
use App\Misc\TransformDataHelper;
use Symfony\Component\Mime\Email;
use App\Repository\BaseRepository;
use App\Exception\CacheHitResponse;
use Symfony\Component\Mime\Address;
use App\Exception\ValidateException;
use App\Exception\BadRequestException;
use App\Annotation\TransformAnnotation;
use AutoMapperPlus\AutoMapperInterface;
use Doctrine\ORM\EntityManagerInterface;
use League\Flysystem\FilesystemOperator;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\HttpFoundation\UrlHelper;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Mime\Exception\RfcComplianceException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class BaseService
{
public function __construct(
\Symfony\Component\DependencyInjection\ContainerInterface $container,
CommonService $commonService,
AutoMapperInterface $autoMapper,
TransformDataHelper $transformDataHelper,
ValidatorInterface $validator,
TokenStorageInterface $tokenStorage,
ParameterBagInterface $params,
BaseRepository $baseRepository,
UrlHelper $urlHelper,
CacheInterface $appCache,
\Psr\Container\ContainerInterface $serviceLocator,
LoggerInterface $logger,
AppMappingOperation $appMappingOperation,
HttpClientInterface $httpClient,
UrlGeneratorInterface $urlGenerator,
MailerInterface $mailer,
FilesystemOperator $uploadsStorage
) {
$this->container = $container;
$this->commonService = $commonService;
$this->autoMapper = $autoMapper;
$this->autoMapper->getConfiguration()->getOptions()->setDefaultMappingOperation($appMappingOperation);
$this->transformDataHelper = $transformDataHelper;
$this->validator = $validator;
$this->tokenStorage = $tokenStorage;
$this->params = $params;
$this->entityManager = $baseRepository->getEntityManager();
$this->urlHelper = $urlHelper;
$this->cache = $appCache;
$this->serviceLocator = $serviceLocator;
$this->logger = $logger;
$this->httpClient = $httpClient;
$this->urlGenerator = $urlGenerator;
$this->mailer = $mailer;
$this->uploadsStorage = $uploadsStorage;
}
public function reflectFromParent($parent)
{
$this->container = $parent->container;
$this->commonService = $parent->commonService;
$this->autoMapper = $parent->autoMapper;
$this->transformDataHelper = $parent->transformDataHelper;
$this->validator = $parent->validator;
$this->tokenStorage = $parent->tokenStorage;
$this->params = $parent->params;
$this->entityManager = $parent->entityManager;
$this->cache = $parent->cache;
$this->urlHelper = $parent->urlHelper;
$this->serviceLocator = $parent->serviceLocator;
$this->logger = $parent->logger;
$this->httpClient = $parent->httpClient;
$this->urlGenerator = $parent->urlGenerator;
$this->mailer = $parent->mailer;
$this->uploadsStorage = $parent->uploadsStorage;
}
public function getProperty($propertyName)
{
return $this->$propertyName;
}
public function getContainer()
{
return $this->container;
}
/**
* @return EntityManagerInterface
*/
public function getEntityManager()
{
return $this->entityManager;
}
public function add(
$request,
array $inputs = null,
String $requestDTO = null,
String $responseDTO = null,
Bool $inTransaction = false,
array $checkExists = [],
Bool $includeRelations = false,
callable $beforeMappingCallback = null
) {
$serviceName = $this->commonService->getClassName($this, true);
if (empty($requestDTO)) {
$requestDTO = 'App\DTO\\' . $serviceName . '\Add' . $serviceName . 'Input';
}
$this->logger->info('serviceName in add', [$serviceName]);
$object = $this->inputResolver($request, $requestDTO, $inputs, $serviceName, $checkExists);
$this->logger->info('object', (array) $object);
$entity = $this->objectToEntity($object, $serviceName, null, $includeRelations);
$this->logger->info('entity', (array) $entity);
$this->repository->save($entity, $inTransaction);
if (empty($responseDTO)) {
$responseDTO = 'App\DTO\\' . $serviceName . '\\' . $serviceName . 'Output';
}
if (strtolower($responseDTO) == 'entity') {
return $entity;
}
if (!is_null($beforeMappingCallback)) {
call_user_func($beforeMappingCallback, $entity);
}
$objectDTO = $this->autoMapper->map($entity, $responseDTO);
$this->logger->info('after output mapping', (array) $objectDTO);
return $objectDTO;
}
public function update(
$entity = null,
$request = null,
array $inputs = null,
String $requestDTO = null,
String $responseDTO = null,
Bool $inTransaction = false,
Bool $includeRelations = false,
callable $beforeMappingCallback = null
) {
$serviceName = $this->commonService->getClassName($this, true);
if (empty($requestDTO)) {
$requestDTO = 'App\DTO\\' . $serviceName . '\Update' . $serviceName . 'Input';
if (!class_exists($requestDTO)) {
$requestDTO = 'App\DTO\\' . $serviceName . '\Add' . $serviceName . 'Input';
}
}
if (is_null($entity)) {
if (is_array($request) && isset($request['id'])) {
$id = $request['id'];
} else {
$id = array_merge(
$request->request->all(),
$request->query->all(),
json_decode($request->getContent(), 1) ?? []
)['id'];
}
$entity = $this->get($id);
}
$this->logger->info('serviceName in update', [$serviceName]);
$object = $this->inputResolver($request, $requestDTO, $inputs, $serviceName);
$this->logger->info('object', [$object]);
$this->objectToEntity($object, $serviceName, $entity, $includeRelations);
$this->repository->save($entity, $inTransaction);
$this->logger->info('entity', [$entity]);
if (empty($responseDTO)) {
$responseDTO = 'App\DTO\\' . $serviceName . '\\' . $serviceName . 'Output';
}
if ($responseDTO == 'ENTITY') {
return $entity;
}
if (!is_null($beforeMappingCallback)) {
call_user_func($beforeMappingCallback, $entity);
}
$objectDTO = $this->autoMapper->map($entity, $responseDTO);
$this->logger->info('objectDTO', [$objectDTO]);
return $objectDTO;
}
public function delete(
$entity = null,
$filters = [],
$strict = true,
$inTransaction = false
) {
if (is_int($entity)) {
$entity = $this->get($entity);
}
if (is_null($entity)) {
$entity = $this->repository->findOneBy($filters);
if (is_null($entity)) {
if ($strict) {
$serviceName = $this->commonService->getClassName($this, true);
throw new BadRequestException(
BadRequestException::NO_ENTITY_TO_DELETE,
null,
$serviceName
);
} else {
return false;
}
}
}
$this->repository->delete($entity, $inTransaction);
return true;
}
public function inputResolver(
$request,
$requestDTO,
$inputs = null,
$serviceName = null,
$checkExists = []
) {
$this->logger->info('serviceName in inputResolver', [$serviceName]);
if ($request instanceof Request) {
$inputArr = array_merge(
$request->request->all(),
$request->query->all(),
json_decode($request->getContent(), 1) ?? []
);
} else {
$inputArr = $request;
}
if (!empty($inputs)) {
$inputArr = array_merge($inputArr, $inputs);
}
if (count($checkExists) > 0) {
$checkExists = array_intersect_key(
$inputArr,
array_flip($checkExists)
);
$existedEntity = $this->repository->findOneBy($checkExists);
if (!is_null($existedEntity)) {
throw new BadRequestException(
BadRequestException::DUPLICATE_ENTITY,
null,
$serviceName
);
}
}
$inputDTO = $this->autoMapper->map($inputArr, $requestDTO);
if (isset($inputArr['id'])) {
$inputDTO->id = $inputArr['id'];
}
$properties = array_keys($inputArr);
$this->logger->info('inputArr', [$inputArr]);
if (
$request instanceof Request
&& $request->files->count()
) {
foreach ($request->files->all() as $fieldName => $file) {
$this->logger->info('fieldName', [$fieldName]);
$this->logger->info('file', [$file]);
if (is_array($file)) {
if (!is_array($inputDTO->{$fieldName})) {
$inputDTO->{$fieldName} = [];
}
foreach ($file as $index => $childFile) {
if (is_array($childFile)) {
$inputDTO->{$fieldName}[$index][array_keys($childFile)[0]] = array_values($childFile)[0];
} else {
if (!is_null($childFile)) {
$inputDTO->{$fieldName}[$index] = $childFile;
}
}
}
} else {
if (!is_null($file)) {
$inputDTO->{$fieldName} = $file;
$properties[] = $fieldName;
}
}
}
}
$this->logger->info('after inputDTO', [(array)$inputDTO]);
$this->transformDataHelper->transform($inputDTO, $properties);
$errors = $this->validator->validate($inputDTO);
if (count($errors) > 0) {
$messages = [];
/** @var ConstraintViolation $error */
foreach ($errors as $error) {
$messages[$error->getPropertyPath()] = $error->getMessage();
}
$this->logger->info('bad inputs', [$serviceName, $messages]);
throw new BadRequestException(
BadRequestException::WRONG_INPUT,
null,
$serviceName,
null,
$messages
);
}
return $inputDTO;
}
public function objectToEntity(
$object,
$entityName,
$entity = null,
$includeRelations = false
) {
if (is_null($entity)) {
$entityClass = 'App\\Entity\\' . $entityName;
$entity = new $entityClass();
}
$metadata = $this->getEntityManager()->getClassMetadata('App\\Entity\\' . $entityName);
$typeMap = [];
foreach ($metadata->fieldMappings as $fieldData) {
$typeMap[$fieldData['fieldName']] = $fieldData['type'];
}
if ($includeRelations) {
foreach ($metadata->associationMappings as $associationMapping) {
$typeMap[$associationMapping['fieldName']] = $associationMapping['type'];
}
}
$this->logger->info('object data', (array) $object);
$this->logger->info('typeMap', (array) $typeMap);
foreach ($object as $key => $value) {
if (is_null($value)) {
continue;
}
if (
isset($typeMap[$key])
&& ($typeMap[$key] == ClassMetadataInfo::MANY_TO_MANY
|| $typeMap[$key] == ClassMetadataInfo::ONE_TO_MANY
)
) {
$singularizedKey = null;
$words = preg_split('#([A-Z][^A-Z]*)#', ucwords($key), null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$lastUpperCaseWord = $words[count($words) - 1];
$lastUpperCaseWordSingle = $this->commonService->singularize($lastUpperCaseWord);
$singularizedKey = str_replace($lastUpperCaseWord, '', ucwords($key)) . $lastUpperCaseWordSingle;
if (!method_exists($entity, 'add' . $singularizedKey)) {
continue;
}
$oldRelations = $entity->{'get' . ucwords($key)}();
$newRelations = $value;
$currentRelationIdList = [];
foreach ($oldRelations as $oldRelation) {
foreach ($newRelations as $newRelation) {
if ($newRelation->getId() == $oldRelation->getId()) {
$currentRelationIdList[] = $oldRelation->getId();
continue 2;
}
}
$entity->{'remove' . $singularizedKey}($oldRelation);
}
foreach ($newRelations as $newRelation) {
if ($newRelation->getId()) {
foreach ($oldRelations as $oldRelation) {
if (in_array($newRelation->getId(), $currentRelationIdList)) {
continue 2;
}
}
}
$entity->{'add' . $singularizedKey}($newRelation);
}
continue;
}
if (!method_exists($entity, 'set' . ucwords($key))) {
continue;
}
if (
isset($typeMap[$key])
&& ($typeMap[$key] == 'integer'
|| $typeMap[$key] == 'float'
|| $typeMap[$key] == 'smallint'
)
&& $value === ''
) {
$entity->{'set' . ucwords($key)}(null);
continue;
}
// merge json update here
if (
isset($typeMap[$key])
&& $typeMap[$key] === 'json'
) {
if ($value === 'isNull') {
$entity->{'set' . ucwords($key)}(null);
continue;
}
$currentValue = $entity->{'get' . ucwords($key)}();
if ($value && is_array($value)) {
foreach ($value as $property => $v) {
$currentValue[$property] = $v;
}
$entity->{'set' . ucwords($key)}($currentValue);
}
continue;
}
if ($value === 'isNull') {
$entity->{'set' . ucwords($key)}(null);
continue;
}
$entity->{'set' . ucwords($key)}($value);
}
return $entity;
}
public function get($id, $lock = false)
{
$entity = $this->repository->find($id, $lock ? LockMode::OPTIMISTIC : null);
if (!$entity) {
$serviceName = $this->commonService->getClassName($this, true);
throw new BadRequestException(
BadRequestException::NO_ENTITY,
null,
$serviceName,
$id
);
}
return $entity;
}
public function __get($propertyName)
{
preg_match('/([a-zA-Z0-9]+)Service/i', $propertyName, $serviceMatches);
if (count($serviceMatches) > 0) {
return $this
->serviceLocator
->get('App\Service\\' . ucfirst($serviceMatches[1]) . 'Service');
}
preg_match('/([a-zA-Z0-9]+)Repo/i', $propertyName, $repositoryMatches);
if (count($repositoryMatches) > 0) {
return $this
->serviceLocator
->get('App\Service\\' . ucfirst($repositoryMatches[1]) . 'Service')
->repository;
}
$entityName = $this->commonService->getClassName($this, true);
$cacheKey = 'Const.' . $entityName . '.' . $propertyName;
$cachedConstantItem = $this->cache->getItem($cacheKey);
if ($cachedConstantItem->isHit()) {
return $cachedConstantItem->get();
}
$constant = null;
if(defined('App\Entity\\' . $entityName . '::' . $propertyName)) {
$constant = constant('App\Entity\\' . $entityName . '::' . $propertyName);
}
if (!is_null($constant)) {
$cachedConstantItem->set($constant);
$this->cache->save($cachedConstantItem);
return $constant;
}
$metas = $this->entityManager->getMetadataFactory()->getAllMetadata();
foreach ($metas as $meta) {
$classPath = $meta->getName();
$name = strtoupper($this->commonService->toSnakeCase(str_replace('App\Entity\\', '', $classPath)));
preg_match('/(' . $name . ')_(.+)/i', $propertyName, $matches);
if (count($matches) > 0) {
$prop = $matches[2];
if(defined($classPath . '::' . $prop)) {
$constant = @constant($classPath . '::' . $prop);
}
if (!is_null($constant)) {
$cachedConstantItem->set($constant);
$this->cache->save($cachedConstantItem);
return $constant;
}
}
}
trigger_error('Could not found property ' . $propertyName . ' in ' . $this->commonService->getClassName($this), E_USER_ERROR);
}
public function newEntity()
{
$entityName = $this->commonService->getClassName($this, true);
$entityClass = 'App\\Entity\\' . $entityName;
return new $entityClass();
}
public function getToken()
{
$token = $this->tokenStorage->getToken();
if (is_null($token) || $token->getUser() === 'anon.') return null;
return $token;
}
public function getUser()
{
$token = $this->getToken();
if (is_null($token)) return null;
return $token->getUser();
}
public function isLoggedIn()
{
return $this->getUser();
}
public function getClassProperties($className)
{
$reflectionClass = new \ReflectionClass($className);
$properties = [];
foreach ($reflectionClass->getProperties() as $property) {
$properties[] = $property->getName();
}
return $properties;
}
public function getClassData($className)
{
$reader = new AnnotationReader();
$reflectionClass = new \ReflectionClass($className);
$data = [
'properties' => [],
'transformAnnotations' => []
];
foreach ($reflectionClass->getProperties() as $property) {
$data['properties'][] = $property;
/** @var TransformAnnotation $transformAnnotation */
$transformAnnotation = $reader->getPropertyAnnotation($property, TransformAnnotation::class);
if (!$transformAnnotation) {
continue;
}
$data['transformAnnotations'][$property->getName()] = $transformAnnotation;
}
return $data;
}
public function getRootDir()
{
return $this->params->get('kernel.project_dir');
}
public function getCollectionProp($collection, $property = 'Id')
{
$propertyList = [];
foreach ($collection as $entity) {
$propertyList[] = $entity->{'get' . $property}();
}
return $propertyList;
}
/**
* Check user is a super admin
*
* @param $user
* @return bool
*/
public function isSuperAdmin($user)
{
foreach ($user->getSubRoles() as $role) {
if ($role->getId() == User::ROLE_ADMINISTRATOR_SUPER) {
return true;
}
}
return false;
}
/**
* Check user is a admin
*
* @param $user
* @return bool
*/
public function isAdmin($user)
{
foreach ($user->getSubRoles() as $role) {
if ($role->getId() == User::ROLE_USER) {
return false;
}
}
return true;
}
public function isClient($user)
{
foreach ($user->getSubRoles() as $role) {
if ($role->getId() == User::ROLE_CLIENT) {
return true;
}
}
return false;
}
public function sendMail(array|string $recipients = [], string $subject, string $content, string $from = null, string $type = 'html')
{
$email = (new Email())
->subject($subject)
->{$type}($content);
$email->from(
new Address(
!is_null($from) ? $from['address'] : $this->params->get('mail.no_reply.from_address'),
!is_null($from) ? $from['name'] : $this->params->get('mail.no_reply.from_name')
)
);
if(is_string($recipients)) {
$recipients = [$recipients];
}
foreach ($recipients as $recipient) {
try {
$email->to($recipient);
$this->mailer->send($email);
} catch(RfcComplianceException $ex) {
} catch(\Exception $ex) {
$this
->requestService
->saveLog(
500,
$email->getTo()[0]->getAddress() . $ex->getMessage(),
null,
true
);
}
}
return true;
}
public function flatening($entity)
{
$flatEntity = [];
$entityName = $this->commonService->getClassName($entity);
$metadata = $this->getEntityManager()->getClassMetadata('App\\Entity\\' . $entityName);
foreach ($metadata->fieldMappings as $fieldName => $fieldData) {
$value = $entity->{'get' . ucwords($fieldName)}();
if ($fieldData['type'] === 'datetime') {
if (!is_null($value)) {
$flatEntity[$fieldName] = $value->format('Y-m-d H:i:s');
} else {
$flatEntity[$fieldName] = null;
}
} else {
$flatEntity[$fieldName] = $value;
}
}
foreach ($metadata->associationMappings as $fieldName => $associationMapping) {
$value = $entity->{'get' . ucwords($fieldName)}();
if ($associationMapping['type'] === ClassMetadataInfo::MANY_TO_ONE) {
$flatEntity[$fieldName] = is_null($value) ? $value : $value->getId();
}
if (
$associationMapping['type'] === ClassMetadataInfo::MANY_TO_MANY
|| $associationMapping['type'] === ClassMetadataInfo::ONE_TO_MANY
) {
$idList = [];
foreach ($value as $associateEntity) {
$idList[] = $associateEntity->getId();
}
$flatEntity[$fieldName] = $idList;
}
}
return $flatEntity;
}
public function uploadPhotosContent($request)
{
$uploadPath = $this->container->getParameter('dir.photo_content');
$fileUpload = $request->files->get('upload');
if (!$fileUpload) {
throw new \Exception('File upload is required');
}
$newMedia = $this->mediaService->uploadFile($fileUpload, $uploadPath);
return [
'file_name' => $newMedia['name'],
'uploaded' => 1,
'url' => $this->uploadsStorage->publicUrl($newMedia['path'])
];
}
public function getYoutubeUrl($url)
{
$shortUrlRegex = '/youtu.be\/([a-zA-Z0-9_-]+)\??/i';
$longUrlRegex = '/youtube.com\/((?:embed)|(?:watch))((?:\?v\=)|(?:\/))([a-zA-Z0-9_-]+)/i';
$youtube_id = null;
if (preg_match($longUrlRegex, $url, $matches)) {
$youtube_id = $matches[count($matches) - 1];
}
if (preg_match($shortUrlRegex, $url, $matches)) {
$youtube_id = $matches[count($matches) - 1];
}
if ($youtube_id) {
return 'https://www.youtube.com/watch?v=' . $youtube_id;
}
return $youtube_id;
}
public function formatDate($datetime, $locale = null, $timezone = null)
{
if ($locale == 'fr' || $locale == null) {
$formatter = new \IntlDateFormatter('fr_FR', \IntlDateFormatter::FULL, \IntlDateFormatter::NONE, $timezone ? $timezone : 'Europe/Paris');
} elseif ($locale == 'en') {
$formatter = new \IntlDateFormatter('en_GB', \IntlDateFormatter::FULL, \IntlDateFormatter::NONE, $timezone ? $timezone : 'Europe/Paris');
}
return $formatter->format($datetime);
}
}