diff --git a/Api/Factory/NewsApiDtoFactory.php b/Api/Factory/NewsApiDtoFactory.php new file mode 100644 index 0000000..503d40d --- /dev/null +++ b/Api/Factory/NewsApiDtoFactory.php @@ -0,0 +1,32 @@ +getId(), + title: $entity->getTitle(), + teaser: $entity->getTeaser(), + content: $entity->getContent(), + enabled: $entity->isEnabled(), + publishedAt: $entity->getPublishedAt(), + route: $entity->getRoute()?->getPath(), + tags: $entity->getTagNameArray(), + header: [ + 'id' => $entity->getHeader()?->getId(), + ], + authored: $entity->getCreated(), + created: $entity->getCreated(), + changed: $entity->getChanged(), + author: $entity->getCreator()?->getId(), + ext: $entity->getSeo(), + locale: $locale + ); + } +} diff --git a/Api/News.php b/Api/News.php index 14c602f..aad6a1c 100644 --- a/Api/News.php +++ b/Api/News.php @@ -13,211 +13,27 @@ namespace TheCadien\Bundle\SuluNewsBundle\Api; -use JMS\Serializer\Annotation\ExclusionPolicy; -use JMS\Serializer\Annotation\Groups; -use JMS\Serializer\Annotation\SerializedName; -use JMS\Serializer\Annotation\VirtualProperty; -use Sulu\Component\Rest\ApiWrapper; -use TheCadien\Bundle\SuluNewsBundle\Entity\News as NewsEntity; +use Symfony\Component\Validator\Constraints as Assert; -/** - * The News class which will be exported to the API. - * - * @ExclusionPolicy("all") - */ -class News extends ApiWrapper +final class News { - public function __construct(NewsEntity $contact, $locale) - { - // @var NewsEntity entity - $this->entity = $contact; - $this->locale = $locale; - } - - /** - * Get id. - * - * @VirtualProperty - * - * @SerializedName("id") - * @Groups({"fullNews"}) - */ - public function getId(): ?int - { - return $this->entity->getId(); - } - - /** - * @VirtualProperty - * - * @SerializedName("title") - * @Groups({"fullNews"}) - */ - public function getTitle(): ?string - { - return $this->entity?->getTitle(); - } - - /** - * @VirtualProperty - * - * @SerializedName("teaser") - * @Groups({"fullNews"}) - */ - public function getTeaser(): ?string - { - return $this->entity->getTeaser(); - } - - /** - * @VirtualProperty - * - * @SerializedName("content") - * @Groups({"fullNews"}) - */ - public function getContent(): array - { - if (!$this->entity->getContent()) { - return []; - } - - return $this->entity->getContent(); - } - - /** - * @VirtualProperty - * - * @SerializedName("enabled") - * @Groups({"fullNews"}) - */ - public function isEnabled(): bool - { - return $this->entity?->isEnabled(); - } - - /** - * @VirtualProperty - * - * @SerializedName("publishedAt") - * @Groups({"fullNews"}) - */ - public function getPublishedAt(): ?\DateTime - { - return $this->entity?->getPublishedAt(); - } - - /** - * @VirtualProperty - * - * @SerializedName("route") - * @Groups({"fullNews"}) - */ - public function getRoutePath(): ?string - { - if ($this->entity?->getRoute()) { - return $this->entity->getRoute()?->getPath(); - } - - return ''; - } - - /** - * Get tags. - * - * @VirtualProperty - * - * @SerializedName("tags") - * @Groups({"fullNews"}) - */ - public function getTags(): array - { - return $this->entity->getTagNameArray(); - } - - /** - * Get the contacts avatar and return the array of different formats. - * - * @VirtualProperty - * - * @SerializedName("header") - * @Groups({"fullNews"}) - */ - public function getHeader(): array - { - if ($this->entity->getHeader()) { - return [ - 'id' => $this->entity->getHeader()->getId(), - ]; - } - - return []; - } - - /** - * Get tags. - * - * @VirtualProperty - * - * @SerializedName("authored") - * @Groups({"fullNews"}) - */ - public function getAuthored(): \DateTime - { - return $this->entity->getCreated(); - } - - /** - * Get tags. - * - * @VirtualProperty - * - * @SerializedName("created") - * @Groups({"fullNews"}) - */ - public function getCreated(): \DateTime - { - return $this->entity->getCreated(); - } - - /** - * Get tags. - * - * @VirtualProperty - * - * @SerializedName("changed") - * @Groups({"fullNews"}) - */ - public function getChanged(): \DateTime - { - return $this->entity->getChanged(); - } - - /** - * Get tags. - * - * @VirtualProperty - * - * @SerializedName("author") - * @Groups({"fullNews"}) - */ - public function getAuthor(): ?int - { - return $this->entity?->getCreator()?->getId(); - } - - /** - * Get tags. - * - * @VirtualProperty - * - * @SerializedName("ext") - * @Groups({"fullNews"}) - */ - public function getSeo(): array - { - $seo = ['seo']; - $seo['seo'] = $this->getEntity()->getSeo(); - - return $seo; + public function __construct( + #[Assert\NotBlank(groups: ['edit'])] + public ?int $id, + public string $title, + public ?string $teaser, + public ?array $content, + public ?bool $enabled, + public ?\DateTime $publishedAt = null, + public ?string $route, + public ?array $tags, + public ?array $header, + public ?\DateTime $authored = null, + public ?\DateTime $created = null, + public ?\DateTime $changed = null, + public ?int $author, + public ?string $ext, + public ?string $locale = null + ) { } } diff --git a/Admin/DoctrineListRepresentationFactory.php b/Common/DoctrineListRepresentationFactory.php similarity index 93% rename from Admin/DoctrineListRepresentationFactory.php rename to Common/DoctrineListRepresentationFactory.php index 9fdb1e0..84ed605 100644 --- a/Admin/DoctrineListRepresentationFactory.php +++ b/Common/DoctrineListRepresentationFactory.php @@ -11,7 +11,7 @@ * with this source code in the file LICENSE. */ -namespace TheCadien\Bundle\SuluNewsBundle\Admin; +namespace TheCadien\Bundle\SuluNewsBundle\Common; use Sulu\Bundle\MediaBundle\Media\Manager\MediaManagerInterface; use Sulu\Component\Rest\ListBuilder\Doctrine\DoctrineListBuilderFactoryInterface; @@ -67,10 +67,6 @@ public function createDoctrineListRepresentation( ); } - /** - * Takes an array of contacts and resets the avatar containing the media id with - * the actual urls to the avatars thumbnail. - */ private function addHeader(array $news, string $locale): array { $ids = \array_filter(\array_column($news, 'header')); diff --git a/Content/NewsDataProvider.php b/Content/NewsDataProvider.php index 7f43556..04e0d07 100644 --- a/Content/NewsDataProvider.php +++ b/Content/NewsDataProvider.php @@ -15,13 +15,14 @@ use JMS\Serializer\Context; use JMS\Serializer\SerializationContext; +use Sulu\Component\SmartContent\Configuration\ProviderConfigurationInterface; use Sulu\Component\SmartContent\Orm\BaseDataProvider; class NewsDataProvider extends BaseDataProvider { public function getConfiguration() { - if (null === $this->configuration) { + if (!$this->configuration instanceof ProviderConfigurationInterface) { $this->configuration = self::createConfigurationBuilder() ->enableLimit() ->enablePagination() diff --git a/Controller/Admin/NewsController.php b/Controller/Admin/NewsController.php index d49dd08..c6c5772 100644 --- a/Controller/Admin/NewsController.php +++ b/Controller/Admin/NewsController.php @@ -13,153 +13,92 @@ namespace TheCadien\Bundle\SuluNewsBundle\Controller\Admin; -use Doctrine\ORM\OptimisticLockException; -use Doctrine\ORM\ORMException; -use FOS\RestBundle\Context\Context; -use FOS\RestBundle\Controller\Annotations as Rest; -use FOS\RestBundle\Routing\ClassResourceInterface; -use FOS\RestBundle\View\View; -use FOS\RestBundle\View\ViewHandlerInterface; -use Sulu\Component\Rest\AbstractRestController; -use Sulu\Component\Rest\Exception\EntityNotFoundException; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use TheCadien\Bundle\SuluNewsBundle\Admin\DoctrineListRepresentationFactory; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\Routing\Annotation\Route; +use TheCadien\Bundle\SuluNewsBundle\Api\Factory\NewsApiDtoFactory; use TheCadien\Bundle\SuluNewsBundle\Api\News as NewsApi; +use TheCadien\Bundle\SuluNewsBundle\Common\DoctrineListRepresentationFactory; use TheCadien\Bundle\SuluNewsBundle\Entity\News; -use TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository; +use TheCadien\Bundle\SuluNewsBundle\Exception\NewsException; use TheCadien\Bundle\SuluNewsBundle\Service\News\NewsService; -class NewsController extends AbstractRestController implements ClassResourceInterface +#[AsController] +final class NewsController extends AbstractController { - // serialization groups for contact - protected static $oneNewsSerializationGroups = [ - 'partialMedia', - 'fullNews', - ]; - - /** - * NewsController constructor. - */ public function __construct( - ViewHandlerInterface $viewHandler, - TokenStorageInterface $tokenStorage, - private readonly NewsRepository $repository, private readonly NewsService $newsService, private readonly DoctrineListRepresentationFactory $doctrineListRepresentationFactory, + private readonly NewsApiDtoFactory $apiDtoFactory ) { - parent::__construct($viewHandler, $tokenStorage); } - public function cgetAction(Request $request): Response + #[Route('/news', name: 'app.cget_news', methods: ['GET'])] + public function cget(Request $request): Response { - $locale = $request->query->get('locale'); $listRepresentation = $this->doctrineListRepresentationFactory->createDoctrineListRepresentation( News::RESOURCE_KEY, [], - ['locale' => $locale] + ['locale' => $request->query->get('locale')] ); - return $this->handleView($this->view($listRepresentation)); + return $this->json($listRepresentation->toArray()); } - public function getAction(int $id, Request $request): Response + #[Route('/news/{id}', name: 'app.get_news', methods: ['GET'])] + public function get(News $news, Request $request): Response { - if (($entity = $this->repository->findById($id)) === null) { - throw new NotFoundHttpException(); - } - - $apiEntity = $this->generateApiNewsEntity($entity, $this->getLocale($request)); - - $view = $this->generateViewContent($apiEntity); - - return $this->handleView($view); - } - - /** - * @throws ORMException - * @throws OptimisticLockException - */ - public function postAction(Request $request): Response - { - $news = $this->newsService->saveNewNews($request->request->all(), $this->getLocale($request)); - - $apiEntity = $this->generateApiNewsEntity($news, $this->getLocale($request)); - - $view = $this->generateViewContent($apiEntity); - - return $this->handleView($view); + return $this->json($this->apiDtoFactory->generate($news, $request->query->get('locale'))); } - /** - * @Rest\Post("/news/{id}") - */ - public function postTriggerAction(int $id, Request $request): Response + #[Route('/news', name: 'app.post_news', methods: ['POST'])] + public function post(#[MapRequestPayload(acceptFormat: 'json')] NewsApi $newsApi, Request $request): Response { - $news = $this->repository->findById($id); - if (!$news instanceof News) { - throw new NotFoundHttpException(); + try { + $news = $this->newsService->saveNewNews($newsApi, $request->query->get('locale')); + } catch (NewsException) { + throw new NewsException(); } - $news = $this->newsService->updateNewsPublish($news, $request->query->all()); - - $apiEntity = $this->generateApiNewsEntity($news, $this->getLocale($request)); - $view = $this->generateViewContent($apiEntity); - - return $this->handleView($view); + return $this->json($this->apiDtoFactory->generate($news, $request->query->get('locale')), 200, [], ['fullNews']); } - /** - * @throws ORMException - * @throws OptimisticLockException - */ - public function putAction(int $id, Request $request): Response + #[Route('/news/{id}', name: 'app.post_news_trigger', methods: ['POST'])] + public function postTrigger(News $news, Request $request): Response { - $entity = $this->repository->findById($id); - if (!$entity instanceof News) { - throw new NotFoundHttpException(); + try { + $news = $this->newsService->updateNewsPublish($news, $request->query->all()); + } catch (NewsException) { + throw new NewsException(); } - $updatedEntity = $this->newsService->updateNews($request->request->all(), $entity, $this->getLocale($request)); - $apiEntity = $this->generateApiNewsEntity($updatedEntity, $this->getLocale($request)); - $view = $this->generateViewContent($apiEntity); - - return $this->handleView($view); + return $this->json($this->apiDtoFactory->generate($news, $request->query->get('locale'))); } - /** - * @throws ORMException - * @throws OptimisticLockException - */ - public function deleteAction(int $id): Response + #[Route('/news/{id}', name: 'app.put_news', methods: ['PUT'])] + public function put(News $news, #[MapRequestPayload(acceptFormat: 'json')] NewsApi $newsApi, Request $request): Response { try { - $this->newsService->removeNews($id); - } catch (\Exception) { - throw new EntityNotFoundException(self::$entityName, $id); + $updatedEntity = $this->newsService->updateNews($newsApi, $news, $request->query->get('locale')); + } catch (NewsException) { + throw new NewsException(); } - return $this->handleView($this->view()); - } - - public static function getPriority(): int - { - return 0; + return $this->json($this->apiDtoFactory->generate($updatedEntity, $request->query->get('locale'))); } - protected function generateApiNewsEntity(News $entity, string $locale): NewsApi + #[Route('/news/{id}', name: 'app.delete_news', methods: ['DELETE'])] + public function delete(News $news): Response { - return new NewsApi($entity, $locale); - } - - protected function generateViewContent(NewsApi $entity): View - { - $view = $this->view($entity); - $context = new Context(); - $context->setGroups(static::$oneNewsSerializationGroups); + try { + $this->newsService->removeNews($news->getId()); + } catch (NewsException) { + throw new NewsException(); + } - return $view->setContext($context); + return $this->json(null, 204); } } diff --git a/Controller/NewsWebsiteController.php b/Controller/Website/NewsController.php similarity index 79% rename from Controller/NewsWebsiteController.php rename to Controller/Website/NewsController.php index b207c0f..b78a69b 100644 --- a/Controller/NewsWebsiteController.php +++ b/Controller/Website/NewsController.php @@ -11,27 +11,26 @@ * with this source code in the file LICENSE. */ -namespace TheCadien\Bundle\SuluNewsBundle\Controller; +namespace TheCadien\Bundle\SuluNewsBundle\Controller\Website; use Sulu\Bundle\PreviewBundle\Preview\Preview; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use TheCadien\Bundle\SuluNewsBundle\Entity\News; -/** - * Class NewsWebsiteController. - */ -class NewsWebsiteController extends AbstractController +#[AsController] +final class NewsController extends AbstractController { - public function indexAction(News $news, $attributes = [], $preview = false, $partial = false): Response + public function index(News $news, $attributes = [], $preview = false, $partial = false): Response { if (!$news) { throw new NotFoundHttpException(); } if ($partial) { - $content = $this->renderBlock( + $content = $this->renderBlockView( 'news/index.html.twig', 'content', ['news' => $news] @@ -62,7 +61,7 @@ protected function renderPreview(string $view, array $parameters = []): string /** * Returns rendered part of template specified by block. */ - protected function renderBlock(mixed $template, mixed $block, mixed $attributes = []) + protected function renderBlockView(mixed $template, mixed $block, mixed $attributes = []): string { $twig = $this->container->get('twig'); $attributes = $twig->mergeGlobals($attributes); @@ -73,7 +72,7 @@ protected function renderBlock(mixed $template, mixed $block, mixed $attributes \ob_start(); try { - $rendered = $template->renderBlock($block, $attributes); + $rendered = $template->renderBlockView($block, $attributes); \ob_end_clean(); return $rendered; diff --git a/DependencyInjection/NewsExtension.php b/DependencyInjection/NewsExtension.php index aae4765..a6065d1 100644 --- a/DependencyInjection/NewsExtension.php +++ b/DependencyInjection/NewsExtension.php @@ -17,7 +17,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use TheCadien\Bundle\SuluNewsBundle\Admin\NewsAdmin; use TheCadien\Bundle\SuluNewsBundle\Entity\News; @@ -90,7 +90,7 @@ public function prepend(ContainerBuilder $container): void 'resources' => [ 'news' => [ 'routes' => [ - 'list' => 'app.get_news', + 'list' => 'app.cget_news', 'detail' => 'app.get_news', ], ], @@ -144,9 +144,8 @@ public function load(array $configs, ContainerBuilder $container): void $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); - $loader->load('services.xml'); - $loader->load('controller.xml'); + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('services.yaml'); $this->configurePersistence($config['objects'], $container); } diff --git a/Entity/Factory/AbstractFactory.php b/Entity/Factory/AbstractFactory.php deleted file mode 100644 index 576cb96..0000000 --- a/Entity/Factory/AbstractFactory.php +++ /dev/null @@ -1,33 +0,0 @@ -getProperty($data, 'title')) { - $news->setTitle($this->getProperty($data, 'title')); - } - - if ($this->getProperty($data, 'teaser')) { - $news->setTeaser($this->getProperty($data, 'teaser')); - } - - if ($this->getProperty($data, 'header')) { - $news->setHeader($this->mediaFactory->generateMedia($data['header'])); - } - - if ($this->getProperty($data, 'publishedAt')) { - $news->setPublishedAt(new \DateTime($this->getProperty($data, 'publishedAt'))); - } - - if ($this->getProperty($data, 'content')) { - $news->setContent($this->getProperty($data, 'content')); - } - - if ($this->getProperty($data, 'ext')) { - $news->setSeo($this->getProperty($data['ext'], 'seo')); - } - - if ($tags = $this->getProperty($data, 'tags')) { - $this->tagFactory->processTags($news, $tags); + $news->setTitle($data->title); + $news->setTeaser($data->teaser); + $news->setHeader($data->header); + $news->setPublishedAt($data->publishedAt); + $news->setContent($data->content); + $news->setSeo($data->ext); + + if ($data->tags) { + $this->tagFactory->processTags($news, $data->tags); } if (!$news->getId()) { @@ -75,13 +59,12 @@ public function generateNewsFromRequest(News $news, array $data, string $locale $news->setEnabled($state); } - if ($authored = $this->getProperty($data, 'authored')) { - $news->setCreated(new \DateTime($authored)); + if ($data->authored) { + $news->setCreated($data->authored); } - if ($author = $this->getProperty($data, 'author')) { - // @var Contact $contact - $news->setCreator($this->contactRepository->find($author)); + if ($data->author) { + $news->setCreator($this->contactRepository->find($data->author)); } return $news; diff --git a/Entity/Factory/NewsFactoryInterface.php b/Entity/Factory/NewsFactoryInterface.php index c9ad720..33d2294 100644 --- a/Entity/Factory/NewsFactoryInterface.php +++ b/Entity/Factory/NewsFactoryInterface.php @@ -13,9 +13,10 @@ namespace TheCadien\Bundle\SuluNewsBundle\Entity\Factory; +use TheCadien\Bundle\SuluNewsBundle\Api\News as NewsApi; use TheCadien\Bundle\SuluNewsBundle\Entity\News; interface NewsFactoryInterface { - public function generateNewsFromRequest(News $news, array $data, string $locale): News; + public function generateNewsFromRequest(News $news, NewsApi $data, string $locale): News; } diff --git a/Entity/Factory/NewsRouteFactory.php b/Entity/Factory/NewsRouteFactory.php index c0c937d..d1fcec4 100644 --- a/Entity/Factory/NewsRouteFactory.php +++ b/Entity/Factory/NewsRouteFactory.php @@ -13,16 +13,13 @@ namespace TheCadien\Bundle\SuluNewsBundle\Entity\Factory; -use Sulu\Bundle\RouteBundle\Manager\RouteManager; +use Sulu\Bundle\RouteBundle\Manager\RouteManagerInterface; use Sulu\Bundle\RouteBundle\Model\RouteInterface; use TheCadien\Bundle\SuluNewsBundle\Entity\News; class NewsRouteFactory implements NewsRouteFactoryInterface { - /** - * NewsFactory constructor. - */ - public function __construct(private readonly RouteManager $routeManager) + public function __construct(private readonly RouteManagerInterface $routeManager) { } diff --git a/Entity/Factory/TagFactory.php b/Entity/Factory/TagFactory.php index 30dc22f..e848049 100644 --- a/Entity/Factory/TagFactory.php +++ b/Entity/Factory/TagFactory.php @@ -17,7 +17,7 @@ use Sulu\Component\Persistence\RelationTrait; use TheCadien\Bundle\SuluNewsBundle\Entity\News; -class TagFactory extends AbstractFactory implements TagFactoryInterface +class TagFactory implements TagFactoryInterface { use RelationTrait; @@ -35,17 +35,25 @@ public function __construct(private readonly TagManagerInterface $tagManager) */ public function processTags(News $news, $tags) { - $get = fn ($tag) => $tag->getId(); + $get = function ($tag) { + return $tag->getId(); + }; - $delete = fn ($tag) => $news->removeTag($tag); + $delete = function ($tag) use ($news) { + return $news->removeTag($tag); + }; - $update = fn () => true; + $update = function () { + return true; + }; - $add = fn ($tag) => $this->addTag($news, $tag); + $add = function ($tag) use ($news) { + return $this->addTag($news, $tag); + }; $entities = $news->getTags(); - return $this->processSubEntities( + $result = $this->processSubEntities( $entities, $tags, $get, @@ -53,6 +61,10 @@ public function processTags(News $news, $tags) $update, $delete ); + + $this->resetIndexOfSubentites($entities); + + return $result; } /** diff --git a/Entity/News.php b/Entity/News.php index 7b38386..4db2c5c 100644 --- a/Entity/News.php +++ b/Entity/News.php @@ -13,7 +13,6 @@ namespace TheCadien\Bundle\SuluNewsBundle\Entity; -use DateTime; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use JMS\Serializer\Annotation\Accessor; @@ -103,9 +102,6 @@ public function setEnabled(bool $enabled): void $this->enabled = $enabled; } - /** - * @return string - */ public function getTitle(): ?string { return $this->title; @@ -121,33 +117,27 @@ public function getContent() return $this->content ?: []; } - public function setContent($content): void + public function setContent(mixed $content): void { $this->content = $content; } - /** - * @return DateTime - */ - public function getCreated(): ?DateTime + public function getCreated(): ?\DateTime { return $this->created; } - public function setCreated(DateTime $created): void + public function setCreated(\DateTime $created): void { $this->created = $created; } - /** - * @return DateTime - */ - public function getPublishedAt(): ?DateTime + public function getPublishedAt(): ?\DateTime { return $this->publishedAt; } - public function setPublishedAt(DateTime $publishedAt): void + public function setPublishedAt(\DateTime $publishedAt): void { $this->publishedAt = $publishedAt; } @@ -157,7 +147,7 @@ public function getTeaser(): ?string return $this->teaser; } - public function setTeaser(string $teaser): void + public function setTeaser(?string $teaser): void { $this->teaser = $teaser; } @@ -196,7 +186,7 @@ public function getTagNameArray(): array { $tags = []; - if (null !== $this->getTags()) { + if ($this->getTags() instanceof Collection) { foreach ($this->getTags() as $tag) { $tags[] = $tag->getName(); } @@ -231,12 +221,12 @@ public function setchanger(mixed $changer): void $this->changer = $changer; } - public function getChanged(): DateTime + public function getChanged(): ?\DateTime { return $this->changed; } - public function setChanged(DateTime $changed): void + public function setChanged(\DateTime $changed): void { $this->changed = $changed; } diff --git a/Exception/NewsException.php b/Exception/NewsException.php new file mode 100644 index 0000000..803b31f --- /dev/null +++ b/Exception/NewsException.php @@ -0,0 +1,9 @@ +select('n') ->from('NewsBundle:News', 'n') ->where('n.enabled = 1') - ->andWhere('n.publishedAt <= :created') - ->setParameter('created', \date('Y-m-d H:i:s')) + ->andWhere('n.publishedAt <= :published') + ->setParameter('published', \date('Y-m-d H:i:s')) ->orderBy('n.publishedAt', 'DESC'); - $news = $qb->getQuery()->getResult(); - - if (!$news) { - return []; - } - - return $news; + return $qb->getQuery()->getResult(); } public function findById(int $id): ?News @@ -62,8 +64,8 @@ public function findById(int $id): ?News } /** - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException + * @throws ORMException + * @throws OptimisticLockException */ public function remove(int $id): void { diff --git a/Resources/config/controller.xml b/Resources/config/controller.xml deleted file mode 100644 index cfec162..0000000 --- a/Resources/config/controller.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Resources/config/forms/news_details_add.xml b/Resources/config/forms/news_details_add.xml index 864b89b..b874a07 100644 --- a/Resources/config/forms/news_details_add.xml +++ b/Resources/config/forms/news_details_add.xml @@ -1,7 +1,6 @@ -
news_details_add diff --git a/Resources/config/forms/news_details_edit.xml b/Resources/config/forms/news_details_edit.xml index 9993a7e..f0659ee 100644 --- a/Resources/config/forms/news_details_edit.xml +++ b/Resources/config/forms/news_details_edit.xml @@ -1,6 +1,6 @@ - news_details_edit diff --git a/Resources/config/forms/news_settings.xml b/Resources/config/forms/news_settings.xml index 3eef1a3..f9faf04 100644 --- a/Resources/config/forms/news_settings.xml +++ b/Resources/config/forms/news_settings.xml @@ -1,7 +1,7 @@ news_settings diff --git a/Resources/config/routes_admin.yaml b/Resources/config/routes_admin.yaml new file mode 100644 index 0000000..d5a9b8f --- /dev/null +++ b/Resources/config/routes_admin.yaml @@ -0,0 +1,3 @@ +controllers_admin: + resource: '../../Controller/Admin' + type: annotation \ No newline at end of file diff --git a/Resources/config/services.xml b/Resources/config/services.xml deleted file mode 100644 index 306912d..0000000 --- a/Resources/config/services.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Resources/config/services.yaml b/Resources/config/services.yaml new file mode 100644 index 0000000..b5de973 --- /dev/null +++ b/Resources/config/services.yaml @@ -0,0 +1,88 @@ +services: + + TheCadien\Bundle\SuluNewsBundle\Admin\NewsAdmin: + arguments: + - '@sulu_admin.view_builder_factory' + - '@sulu_core.webspace.webspace_manager' + - '@sulu_security.security_checker' + - '@sulu_activity.activity_list_view_builder_factory' + tags: [ 'sulu.admin', { name: 'sulu.context', context: 'admin' } ] + + TheCadien\Bundle\SuluNewsBundle\Controller\Admin\NewsController: + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Service\News\NewsService' + - '@TheCadien\Bundle\SuluNewsBundle\Common\DoctrineListRepresentationFactory' + - '@TheCadien\Bundle\SuluNewsBundle\Api\Factory\NewsApiDtoFactory' + autoconfigure: true; + autowire: true; + tags: [ 'controller.service_arguments', { name: 'sulu.context', context: 'admin' } ] + + TheCadien\Bundle\SuluNewsBundle\Controller\Website\NewsController: + tags: [ 'controller.service_arguments', { name: 'sulu.context', context: 'website' } ] + autoconfigure: true; + autowire: true; + + + TheCadien\Bundle\SuluNewsBundle\Twig\NewsTwigExtension: + tags: [ 'twig.extension' ] + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository' + + TheCadien\Bundle\SuluNewsBundle\Content\NewsSelectionContentType: + tags: [ { name: 'sulu.content.type', alias: 'news_selection' } ] + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository' + + TheCadien\Bundle\SuluNewsBundle\Routing\NewsRouteDefaultProvider: + tags: [ 'sulu_route.defaults_provider' ] + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository' + + TheCadien\Bundle\SuluNewsBundle\Preview\NewsObjectProvider: + tags: [ { name: 'sulu.context', context: 'admin' }, { name: 'sulu_preview.object_provider', provider-key: 'news' } ] + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository' + - + TheCadien\Bundle\SuluNewsBundle\Content\NewsDataProvider: + tags: [ { name: 'sulu_website.data_provider', alias: 'news' } ] + arguments: + + TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository: + tags: [ 'doctrine.repository_service' ] + arguments: + - '@Doctrine\Persistence\ManagerRegistry' + + TheCadien\Bundle\SuluNewsBundle\Service\News\NewsService: + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository' + - '@TheCadien\Bundle\SuluNewsBundle\Entity\Factory\NewsFactory' + - '@TheCadien\Bundle\SuluNewsBundle\Entity\Factory\NewsRouteFactory' + - '@security.untracked_token_storage' + - '@sulu_activity.domain_event_collector' + + TheCadien\Bundle\SuluNewsBundle\Common\DoctrineListRepresentationFactory: + arguments: + - '@sulu_core.doctrine_rest_helper' + - '@sulu_core.doctrine_list_builder_factory' + - '@sulu_core.list_builder.field_descriptor_factory' + - '@sulu_media.media_manager' + + TheCadien\Bundle\SuluNewsBundle\Entity\Factory\NewsFactory: + arguments: + - '@TheCadien\Bundle\SuluNewsBundle\Entity\Factory\MediaFactory' + - '@TheCadien\Bundle\SuluNewsBundle\Entity\Factory\TagFactory' + - '@sulu.repository.contact' + + TheCadien\Bundle\SuluNewsBundle\Entity\Factory\NewsRouteFactory: + arguments: + - '@sulu_route.manager.route_manager' + + TheCadien\Bundle\SuluNewsBundle\Entity\Factory\TagFactory: + arguments: + - '@sulu_tag.tag_manager' + + TheCadien\Bundle\SuluNewsBundle\Entity\Factory\MediaFactory: + arguments: + - '@sulu.repository.media' + + TheCadien\Bundle\SuluNewsBundle\Api\Factory\NewsApiDtoFactory: \ No newline at end of file diff --git a/Resources/docs/installation.md b/Resources/docs/installation.md index 706711c..505ab09 100644 --- a/Resources/docs/installation.md +++ b/Resources/docs/installation.md @@ -32,10 +32,9 @@ bin/console doctrine:schema:update --force Define the Admin Api Route in `routes_admin.yaml` ```yaml sulu_news.admin: + resource: "@NewsBundle/Resources/config/routes_admin.yaml" type: rest - resource: sulu_news.rest.controller prefix: /admin/api - name_prefix: app. ``` ## Role Permissions diff --git a/Resources/docs/upgrade.md b/Resources/docs/upgrade.md new file mode 100644 index 0000000..caae575 --- /dev/null +++ b/Resources/docs/upgrade.md @@ -0,0 +1,13 @@ + + +# NewsBundle 2.0.0 + +### routes_admin.yaml + +Change the following routing Config in your `routes_admin.yaml` +```diff +sulu_news.admin: +- resource: sulu_news.rest.controller ++ resource: "@NewsBundle/Resources/config/routes_admin.yaml" + prefix: /admin/api +``` diff --git a/Routing/NewsRouteDefaultProvider.php b/Routing/NewsRouteDefaultProvider.php index 3608f92..814cf29 100644 --- a/Routing/NewsRouteDefaultProvider.php +++ b/Routing/NewsRouteDefaultProvider.php @@ -26,7 +26,7 @@ public function __construct(private readonly NewsRepository $newsRepository) public function getByEntity($entityClass, $id, $locale, $object = null) { return [ - '_controller' => 'sulu_news.controller::indexAction', + '_controller' => 'TheCadien\Bundle\SuluNewsBundle\Controller\Website\NewsController::indexAction', 'news' => $object ?: $this->newsRepository->findById((int) $id), ]; } diff --git a/Service/News/NewsService.php b/Service/News/NewsService.php index 3381151..6936e34 100644 --- a/Service/News/NewsService.php +++ b/Service/News/NewsService.php @@ -17,7 +17,9 @@ use Doctrine\ORM\ORMException; use Sulu\Bundle\ActivityBundle\Application\Collector\DomainEventCollectorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; +use TheCadien\Bundle\SuluNewsBundle\Api\News as NewsApi; use TheCadien\Bundle\SuluNewsBundle\Entity\Factory\NewsFactory; use TheCadien\Bundle\SuluNewsBundle\Entity\Factory\NewsRouteFactory; use TheCadien\Bundle\SuluNewsBundle\Entity\News; @@ -43,7 +45,7 @@ public function __construct( TokenStorageInterface $tokenStorage, private readonly DomainEventCollectorInterface $domainEventCollector ) { - if (null !== $tokenStorage->getToken()) { + if ($tokenStorage->getToken() instanceof TokenInterface) { $this->loginUser = $tokenStorage->getToken()->getUser(); } } @@ -52,7 +54,7 @@ public function __construct( * @throws ORMException * @throws OptimisticLockException */ - public function saveNewNews(array $data, string $locale): News + public function saveNewNews(NewsApi $data, string $locale): News { $news = null; try { @@ -82,7 +84,7 @@ public function saveNewNews(array $data, string $locale): News * @throws ORMException * @throws OptimisticLockException */ - public function updateNews($data, News $news, string $locale): News + public function updateNews(NewsApi $data, News $news, string $locale): News { try { $news = $this->newsFactory->generateNewsFromRequest($news, $data, $locale); @@ -91,8 +93,8 @@ public function updateNews($data, News $news, string $locale): News $news->setchanger($this->loginUser->getContact()); - if ($news->getRoute()->getPath() !== $data['route']) { - $route = $this->routeFactory->updateNewsRoute($news, $data['route']); + if ($news->getRoute()->getPath() !== $data->route && $data->route) { + $route = $this->routeFactory->updateNewsRoute($news, $data->route); $news->setRoute($route); } diff --git a/Service/News/NewsServiceInterface.php b/Service/News/NewsServiceInterface.php index 3b56014..e8aa1e8 100644 --- a/Service/News/NewsServiceInterface.php +++ b/Service/News/NewsServiceInterface.php @@ -13,11 +13,12 @@ namespace TheCadien\Bundle\SuluNewsBundle\Service\News; +use TheCadien\Bundle\SuluNewsBundle\Api\News as NewsApi; use TheCadien\Bundle\SuluNewsBundle\Entity\News; interface NewsServiceInterface { - public function saveNewNews(array $data, string $locale): News; + public function saveNewNews(NewsApi $data, string $locale): News; - public function updateNews($data, News $article, string $locale): News; + public function updateNews(NewsApi $data, News $article, string $locale): News; } diff --git a/Tests/Application/bin/adminconsole b/Tests/Application/bin/adminconsole new file mode 100644 index 0000000..e73f45a --- /dev/null +++ b/Tests/Application/bin/adminconsole @@ -0,0 +1,5 @@ +#!/usr/bin/env php +getParameterOption(array('--env', '-e'), getenv('APP_ENV') ?: 'dev'); -$debug = getenv('APP_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod'; +$env = $input->getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev'); +$debug = '0' !== getenv('SYMFONY_DEBUG') && !$input->hasParameterOption(['--no-debug', '']) && 'prod' !== $env; if ($debug) { - Debug::enable(); + // Clean up when sf 4.3 support is removed + if (class_exists(Debug::class)) { + Debug::enable(); + } else { + \Symfony\Component\Debug\Debug::enable(); + } } -$kernel = new Kernel($env, $debug); +$kernel = new Kernel($env, $debug, $suluContext); $application = new Application($kernel); $application->run($input); diff --git a/Tests/Application/bin/websiteconsole b/Tests/Application/bin/websiteconsole new file mode 100644 index 0000000..f8bf04c --- /dev/null +++ b/Tests/Application/bin/websiteconsole @@ -0,0 +1,5 @@ +#!/usr/bin/env php +=1.2) +if (\is_array($env = @include \dirname(__DIR__) . '/.env.local.php')) { + $_SERVER += $env; + $_ENV += $env; +} elseif (!\class_exists(Dotenv::class)) { + throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); +} else { + $path = \dirname(__DIR__) . '/.env'; + $dotenv = new Dotenv(); + $dotenv->loadEnv($path); +} + +$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; +$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; +$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || \filter_var($_SERVER['APP_DEBUG'], \FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/Tests/Application/config/config.yml b/Tests/Application/config/config.yml index 682c6b8..af8cf11 100644 --- a/Tests/Application/config/config.yml +++ b/Tests/Application/config/config.yml @@ -1 +1,4 @@ +framework: + router: { resource: "%kernel.project_dir%/config/routing.yml" } + doctrine: \ No newline at end of file diff --git a/Tests/Application/config/routing.yml b/Tests/Application/config/routing.yml index 92d0b98..8877276 100644 --- a/Tests/Application/config/routing.yml +++ b/Tests/Application/config/routing.yml @@ -1,9 +1,7 @@ sulu_admin: resource: "@SuluAdminBundle/Resources/config/routing.yml" prefix: /admin - -app_news: +sulu_news.admin: + resource: "@NewsBundle/Resources/config/routes_admin.yaml" type: rest - resource: sulu_news.rest.controller - prefix: /admin/api - name_prefix: news. \ No newline at end of file + prefix: /admin/api \ No newline at end of file diff --git a/Tests/Application/config/templates/pages/overview.html.twig b/Tests/Application/config/templates/pages/overview.html.twig new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Application/config/webspaces/newsbundle.xml b/Tests/Application/config/webspaces/newsbundle.xml new file mode 100644 index 0000000..7675bdc --- /dev/null +++ b/Tests/Application/config/webspaces/newsbundle.xml @@ -0,0 +1,54 @@ + + + + TheCadien NewsBundle + newsbundle + + + + + + default + + + overview + overview + + + + + + + Mainnavigation + + + + + + + + TheCadien NewsBundle + newsbundle.de + + + + + {host} + + + + + {host} + + + + + {host} + + + + + + \ No newline at end of file diff --git a/Tests/Functional/Admin/AdminBackendTest.php b/Tests/Functional/Admin/AdminBackendTest.php index e807cf2..1f0a3d8 100644 --- a/Tests/Functional/Admin/AdminBackendTest.php +++ b/Tests/Functional/Admin/AdminBackendTest.php @@ -24,11 +24,10 @@ final class AdminBackendTest extends SuluTestCase { public function testListMetadataAction(): void { - $client = $this->createAuthenticatedClient(); + $client = self::createAuthenticatedClient(); - $client->request('GET', '/admin/metadata/list/news'); - - $this->assertHttpStatusCode(200, $client->getResponse()); + $client->jsonRequest('GET', 'admin/metadata/list/news'); + self::assertHttpStatusCode(200, $client->getResponse()); $response = \json_decode($client->getResponse()->getContent(), null, 512, \JSON_THROW_ON_ERROR); static::assertObjectHasAttribute('title', $response); @@ -45,7 +44,7 @@ public function testListMetadataAction(): void public function testFormMetadataAction(): void { - $client = $this->createAuthenticatedClient(); + $client = self::createAuthenticatedClient(); $client->request('GET', '/admin/metadata/form/news_details_add'); diff --git a/Tests/Functional/Controller/Admin/NewsControllerTest.php b/Tests/Functional/Controller/Admin/NewsControllerTest.php new file mode 100644 index 0000000..ea5b8e1 --- /dev/null +++ b/Tests/Functional/Controller/Admin/NewsControllerTest.php @@ -0,0 +1,132 @@ +generateNews(); + + $client->getContainer()->get('TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository')->save($news); + + $client->request('GET', '/admin/api/news/' . $news->getId() . '?locale=en'); + + $response = \json_decode($client->getResponse()->getContent()); + + self::assertSame('Test Teaser', $response->teaser); + self::assertSame('title', $response->content[0]->type); + self::assertSame('Test', $response->content[0]->title); + self::assertSame('/test-1', $response->route); + self::assertTrue($response->enabled); + self::assertSame([], $response->tags); // todo Test with tags! + self::assertNull($response->author); // todo ! Test Author! + self::assertNull($response->ext); // todo ! Test ext! + } + + public function testAppGetNewsWithoutData() + { + $client = self::createAuthenticatedClient(); + + self::purgeDatabase(); + + $client->request('GET', '/admin/api/news/1000?locale=en'); + self::assertResponseStatusCodeSame(404); + } + + public function testPostValidNewsWithOutContent() + { + $client = self::createAuthenticatedClient(); + + self::purgeDatabase(); + + $client->jsonRequest( + 'POST', + '/admin/api/news?locale=en', + [ + 'title' => 'test', + 'teaser' => 'Test Teaser', + 'publishedAt' => '2023-12-23T18:22:54', + ] + ); + + $newsResult = $client->getContainer()->get('TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository')->findOneBy(['title' => 'test']); + + self::assertSame('test', $newsResult->getTitle()); + self::assertSame('Test Teaser', $newsResult->getTeaser()); + self::assertSame('/news/' . $newsResult->getId(), $newsResult->getRoute()->getPath()); + } + + public function testNewsPostWithInvalidRequest() + { + $client = self::createAuthenticatedClient(); + + self::purgeDatabase(); + + $client->jsonRequest( + 'POST', + '/admin/api/news?locale=en', + [ + 'title' => '', + 'teaser' => '', + 'publishedAt' => '', + ] + ); + + /* Symfony MapRequestPayload 422 status code */ + self::assertResponseStatusCodeSame(422); + } + + public function testNewsPutWithValidRequest() + { + $client = self::createAuthenticatedClient(); + + self::purgeDatabase(); + $news = $this->generateNews(); + + $client->getContainer()->get('TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository')->save($news); + + $client->jsonRequest( + 'PUT', + '/admin/api/news/' . $news->getId() . '?locale=en', + [ + 'title' => 'New Title', + 'teaser' => 'New Teaser', + 'publishedAt' => '2023-12-23T18:22:54', + 'route' => '/news/fancy-new-route', + 'content' => [ + [ + 'type' => 'editor', + 'text' => '

Test Editor

', + ], + [ + 'type' => 'title', + 'title' => 'Test', + ], + ], + 'tags' => [ + 'Test', + 'Sulu', + 'News', + ], + ] + ); + + $updatetNews = $client->getContainer()->get('TheCadien\Bundle\SuluNewsBundle\Repository\NewsRepository')->findById($news->getId()); + self::assertSame('New Title', $updatetNews->getTitle()); + self::assertSame('New Teaser', $updatetNews->getTeaser()); + self::assertSame('/news/fancy-new-route', $updatetNews->getRoute()->getPath()); + self::assertSame('Max', $updatetNews->getChanger()->getFirstName()); + self::assertSame('Mustermann', $updatetNews->getChanger()->getLastName()); + self::assertSame('title', $updatetNews->getContent()[1]['type']); + self::assertSame('Test', $updatetNews->getContent()[1]['title']); + } +} diff --git a/Tests/Unit/Api/NewsApiTest.php b/Tests/Unit/Api/NewsApiTest.php index ad893c1..6e090e7 100644 --- a/Tests/Unit/Api/NewsApiTest.php +++ b/Tests/Unit/Api/NewsApiTest.php @@ -14,7 +14,6 @@ namespace TheCadien\Bundle\SuluNewsBundle\Tests\Unit\Api; use PHPUnit\Framework\TestCase; -use TheCadien\Bundle\SuluNewsBundle\Entity\News; use TheCadien\Bundle\SuluNewsBundle\Tests\Unit\Traits\Api\NewsTrait; /** @@ -26,31 +25,13 @@ final class NewsApiTest extends TestCase { use NewsTrait; - public function testEmptyApiDto(): void + public function testApiDto(): void { - $apiDto = $this->generateEmptyApiNews(); + $apiDto = $this->generateApiNews(); - static::assertInstanceOf(News::class, $apiDto->getEntity()); - - static::assertNull($apiDto->getId()); - static::assertNull($apiDto->getTitle()); - static::assertNull($apiDto->getTeaser()); - static::assertSame([], $apiDto->getHeader()); - static::assertSame([], $apiDto->getContent()); - static::assertNull($apiDto->getPublishedAt()); - static::assertSame([], $apiDto->getTags()); - static::assertSame('', $apiDto->getRoutePath()); - } - - public function testApiDtoWithContent(): void - { - $apiDto = $this->generateApiNewsWithContent(); - - static::assertInstanceOf(News::class, $apiDto->getEntity()); - - static::assertSame(1, $apiDto->getId()); - static::assertSame('Test Title', $apiDto->getTitle()); - static::assertSame('Test Teaser', $apiDto->getTeaser()); + static::assertSame(1, $apiDto->id); + static::assertSame('Test Title', $apiDto->title); + static::assertSame('Test Teaser', $apiDto->teaser); static::assertSame( [ [ @@ -62,9 +43,9 @@ public function testApiDtoWithContent(): void 'text' => '

Test Editor

', ], ], - $apiDto->getContent() + $apiDto->content ); - static::assertSame('2017-08-31 00:00:00', $apiDto->getPublishedAt()->format('Y-m-d H:i:s')); - static::assertSame('/test-1', $apiDto->getRoutePath()); + static::assertSame('2031-08-31 00:00:00', $apiDto->publishedAt->format('Y-m-d H:i:s')); + static::assertSame('/test-1', $apiDto->route); } } diff --git a/Tests/Unit/Entity/Factory/NewsFactoryTest.php b/Tests/Unit/Entity/Factory/NewsFactoryTest.php deleted file mode 100644 index bb3f1f4..0000000 --- a/Tests/Unit/Entity/Factory/NewsFactoryTest.php +++ /dev/null @@ -1,82 +0,0 @@ -prophesize(MediaFactoryInterface::class); - $mediaFactory->generateMedia()->willReturn([]); - - $tagFactory = $this->prophesize(TagFactoryInterface::class); - $tagFactory->processTags()->willReturn([]); - - $contactRepository = $this->prophesize(ContactRepositoryInterface::class); - $contactRepository->find()->willReturn(new Contact()); - - $this->factory = new NewsFactory($mediaFactory->reveal(), $tagFactory->reveal(), $contactRepository->reveal()); - } - - public function testNewNewsFactory(): void - { - $news = $this->factory->generateNewsFromRequest(new news(), $this->generateNewsContentArray()); - static::assertSame('Test Title', $news->getTitle()); - static::assertSame([ - [ - 'type' => 'title', - 'title' => 'Test', - ], - [ - 'type' => 'editor', - 'text' => '

Test Editor

', - ], - ], $news->getContent()); - static::assertSame('Test Teaser', $news->getTeaser()); - // static::assertInstanceOf(Contact::class, $news->getCreator()); - static::assertSame('2017-08-31 00:00:00', $news->getPublishedAt()->format('Y-m-d H:i:s')); - } - - public function testNewNewsFactoryWithEmptyContent(): void - { - $news = $this->factory->generateNewsFromRequest(new news(), $this->generateNewsContentArrayWithoutContent()); - static::assertSame('Test', $news->getTitle()); - static::assertSame([], $news->getContent()); - static::assertSame('Test', $news->getTeaser()); - static::assertSame('2017-08-31 00:00:00', $news->getPublishedAt()->format('Y-m-d H:i:s')); - } -} diff --git a/Tests/Unit/Entity/NewsTest.php b/Tests/Unit/Entity/NewsTest.php index d81381c..a5ddf25 100644 --- a/Tests/Unit/Entity/NewsTest.php +++ b/Tests/Unit/Entity/NewsTest.php @@ -27,13 +27,21 @@ final class NewsTest extends TestCase public function testEmptyApiDto(): void { - $news = $this->generateEmptyNews(); + $news = $this->generateNews(); - static::assertNull($news->getId()); - static::assertNull($news->getTitle()); - static::assertNull($news->getTeaser()); - static::assertNull($news->getHeader()); - static::assertSame([], $news->getContent()); - static::assertNull($news->getPublishedAt()); + static::assertSame($news->getId(), 1); + static::assertSame($news->getTitle(), 'Test Title'); + static::assertSame($news->getTeaser(), 'Test Teaser'); + static::assertSame([ + [ + 'type' => 'title', + 'title' => 'Test', + ], + [ + 'type' => 'editor', + 'text' => '

Test Editor

', + ], + ], $news->getContent()); + static::assertSame('2031-08-31 00:00:00', $news->getPublishedAt()->format('Y-m-d H:i:s')); } } diff --git a/Tests/Unit/Repository/NewsRepositoryTest.php b/Tests/Unit/Repository/NewsRepositoryTest.php index 0ed0e8d..d21fbf2 100644 --- a/Tests/Unit/Repository/NewsRepositoryTest.php +++ b/Tests/Unit/Repository/NewsRepositoryTest.php @@ -13,8 +13,9 @@ namespace TheCadien\Bundle\SuluNewsBundle\Tests\Unit\Repository; -use DateTime; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; use Sulu\Bundle\TestBundle\Testing\PurgeDatabaseTrait; use Sulu\Bundle\TestBundle\Testing\SuluTestCase; use TheCadien\Bundle\SuluNewsBundle\Entity\News; @@ -34,12 +35,12 @@ final class NewsRepositoryTest extends SuluTestCase /** * @var EntityManager */ - private \Doctrine\ORM\EntityManagerInterface $em; + private EntityManagerInterface $em; /** * @var NewsRepository */ - private \Doctrine\ORM\EntityRepository $newsRepository; + private EntityRepository $newsRepository; protected function setUp(): void { @@ -50,7 +51,7 @@ protected function setUp(): void public function testSave(): void { - $newsTestData = $this->generateNewsWithContent(); + $newsTestData = $this->generateNews(); $this->newsRepository->save($newsTestData); $newsResult = $this->newsRepository->findOneBy(['title' => $newsTestData->getTitle()]); @@ -60,15 +61,14 @@ public function testSave(): void public function testGetPublishedNewsWithResult(): void { - $newsTestData = $this->generateNewsWithContent(); + $newsTestData = $this->generateNews(); $this->newsRepository->save($newsTestData); $secondNewsTestData = $this->generateSecondNewsWithContent(); $this->newsRepository->save($secondNewsTestData); $result = $this->newsRepository->getPublishedNews(); - static::assertSame($newsTestData->getTitle(), $result[0]->getTitle()); - static::assertSame($secondNewsTestData->getTitle(), $result[1]->getTitle()); + self::assertCount(1, $result); } public function testGetPublishedNewsWithEmptyDatabase(): void @@ -81,12 +81,12 @@ public function testGetPublishedNewsWithEmptyDatabase(): void public function testGetPublishedNewsWithoutPublishedResult(): void { /** not enabled example in */ - $newsTestData = $this->generateNewsWithContent(); + $newsTestData = $this->generateNews(); $newsTestData->setEnabled(false); $this->newsRepository->save($newsTestData); /** not published example in */ $secondNewsTestData = $this->generateSecondNewsWithContent(); - $secondNewsTestData->setPublishedAt(new DateTime('tomorrow')); + $secondNewsTestData->setPublishedAt(new \DateTime('tomorrow')); $this->newsRepository->save($secondNewsTestData); $result = $this->newsRepository->getPublishedNews(); diff --git a/Tests/Unit/Traits/Api/NewsTrait.php b/Tests/Unit/Traits/Api/NewsTrait.php index f7e700d..4e261d0 100644 --- a/Tests/Unit/Traits/Api/NewsTrait.php +++ b/Tests/Unit/Traits/Api/NewsTrait.php @@ -22,13 +22,25 @@ trait NewsTrait { use \TheCadien\Bundle\SuluNewsBundle\Tests\Unit\Traits\Entity\NewsTrait; - public function generateEmptyApiNews(): ApiNews + public function generateApiNews(): ApiNews { - return new ApiNews($this->generateEmptyNews(), 'de'); - } - - public function generateApiNewsWithContent(): ApiNews - { - return new ApiNews($this->generateNewsWithContent(), 'de'); + return new ApiNews( + $this->generateNews()->getId(), + $this->generateNews()->getTitle(), + $this->generateNews()->getTeaser(), + $this->generateNews()->getContent(), + $this->generateNews()->isEnabled(), + $this->generateNews()->getPublishedAt(), + $this->generateNews()->getRoute()?->getPath(), + $this->generateNews()->getTagNameArray(), + [ + 'id' => $this->generateNews()->getHeader()?->getId(), + ], + $this->generateNews()->getCreated(), + $this->generateNews()->getCreated(), + $this->generateNews()->getChanged(), + $this->generateNews()->getCreator()?->getId(), + $this->generateNews()->getSeo() + ); } } diff --git a/Tests/Unit/Traits/Entity/NewsTrait.php b/Tests/Unit/Traits/Entity/NewsTrait.php index 2f3a221..e94758d 100644 --- a/Tests/Unit/Traits/Entity/NewsTrait.php +++ b/Tests/Unit/Traits/Entity/NewsTrait.php @@ -13,7 +13,6 @@ namespace TheCadien\Bundle\SuluNewsBundle\Tests\Unit\Traits\Entity; -use DateTime; use Sulu\Bundle\RouteBundle\Entity\Route; use TheCadien\Bundle\SuluNewsBundle\Entity\News; @@ -22,12 +21,7 @@ */ trait NewsTrait { - public function generateEmptyNews(): News - { - return new News(); - } - - public function generateNewsWithContent(): News + public function generateNews(): News { $news = new News(); $contentArray = $this->generateNewsContentArray(); @@ -39,7 +33,7 @@ public function generateNewsWithContent(): News $news->setEnabled($contentArray['enable']); $news->setLocale($contentArray['locale']); $news->setRoute($contentArray['route']); - $news->setPublishedAt(DateTime::createFromFormat('Y-m-d H:i:s', $contentArray['publishedAt'])); + $news->setPublishedAt(\DateTime::createFromFormat('Y-m-d H:i:s', $contentArray['publishedAt'])); return $news; } @@ -63,7 +57,7 @@ public function generateNewsContentArray(): array 'locale' => 'en', 'route' => new Route('/test-1', 1, News::class, 'en'), 'enable' => true, - 'publishedAt' => '2017-08-31 00:00:00', + 'publishedAt' => '2031-08-31 00:00:00', ]; } @@ -79,7 +73,7 @@ public function generateSecondNewsWithContent(): News $news->setEnabled($contentArray['enable']); $news->setLocale($contentArray['locale']); $news->setRoute($contentArray['route']); - $news->setPublishedAt(DateTime::createFromFormat('Y-m-d H:i:s', $contentArray['publishedAt'])); + $news->setPublishedAt(\DateTime::createFromFormat('Y-m-d H:i:s', $contentArray['publishedAt'])); return $news; } diff --git a/Tests/Unit/Twig/NewsTwigExtensionTest.php b/Tests/Unit/Twig/NewsTwigExtensionTest.php index 8d641dc..3bd8e3e 100644 --- a/Tests/Unit/Twig/NewsTwigExtensionTest.php +++ b/Tests/Unit/Twig/NewsTwigExtensionTest.php @@ -14,7 +14,7 @@ class NewsTwigExtensionTest extends TestCase public function testResolveNewsFunction() { $repoMock = $this->createMock(NewsRepository::class); - $repoMock->method('find')->willReturn($this->generateNewsWithContent()); + $repoMock->method('find')->willReturn($this->generateNews()); $newsTwigExtension = new NewsTwigExtension($repoMock); $news = $newsTwigExtension->resolveNewsFunction(1); diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 9e16efc..e0dda66 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -37,7 +37,7 @@ } else { // fallback code in case your Dotenv component is not 4.2 or higher (when loadEnv() was added) - if (\file_exists($path) || !\file_exists($p = "{$path}.dist")) { + if (\file_exists($path) || !\file_exists($p = "$path.dist")) { $dotenv->load($path); } else { $dotenv->load($p); @@ -47,16 +47,16 @@ $dotenv->populate(['APP_ENV' => $env = 'dev']); } - if ('test' !== $env && \file_exists($p = "{$path}.local")) { + if ('test' !== $env && \file_exists($p = "$path.local")) { $dotenv->load($p); $env = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env; } - if (\file_exists($p = "{$path}.{$env}")) { + if (\file_exists($p = "$path.$env")) { $dotenv->load($p); } - if (\file_exists($p = "{$path}.{$env}.local")) { + if (\file_exists($p = "$path.$env.local")) { $dotenv->load($p); } } diff --git a/composer.json b/composer.json index ec91acd..49b9212 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,7 @@ ], "require": { "php": "^8.1", - "laminas/laminas-zendframework-bridge": "^1.4", - "rector/rector": "^0.15.7", - "sulu/sulu": "2.5.*", - "sulu/sulu-rector": "^0.1.3", + "sulu/sulu": "^2.4 || ^2.5@dev", "symfony/config": "^4.4 || ^5.0 || ^6.0", "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", @@ -28,24 +25,23 @@ }, "require-dev": { "coduo/php-matcher": "^5.0 || ^6.0", - "friendsofphp/php-cs-fixer": "^3.1", + "friendsofphp/php-cs-fixer": "^3.22", "handcraftedinthealps/code-coverage-checker": "^0.2.1", "handcraftedinthealps/zendsearch": "^2.0", "jackalope/jackalope-doctrine-dbal": "^1.3.4", - "jangregor/phpstan-prophecy": "^1.0", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-doctrine": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-symfony": "^1.1", - "phpstan/phpstan-webmozart-assert": "^1.0", - "phpunit/phpunit": "^8.4", + "jangregor/phpstan-prophecy": "^0.8", + "phpspec/prophecy": "^1.8", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-doctrine": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-symfony": "^0.12", + "phpunit/phpunit": "^9.5", "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", "symfony/console": "^4.4 || ^5.0 || ^6.0", "symfony/dotenv": "^4.4 || ^5.0 || ^6.0", "symfony/error-handler": "^4.4 || ^5.0 || ^6.0", "symfony/monolog-bundle": "^3.1", - "thecodingmachine/phpstan-strict-rules": "^1.0" + "thecodingmachine/phpstan-strict-rules": "^0.12" }, "scripts": { "php-cs-fixer": [ @@ -56,8 +52,9 @@ ], "phpunit": "@php vendor/bin/phpunit --testdox", "bootstrap-test-environment": [ - "@php Tests/Application/bin/console doctrine:database:create --if-not-exists", - "@php Tests/Application/bin/console doctrine:schema:update --force" + "@php Tests/Application/bin/adminconsole doctrine:database:drop --if-exists --force --env test", + "@php Tests/Application/bin/adminconsole doctrine:database:create --env test", + "@php Tests/Application/bin/adminconsole doctrine:schema:update --force --env test" ] }, "autoload": { @@ -66,6 +63,9 @@ } }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..3ea5e84 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,516 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method Sulu\\\\Bundle\\\\AdminBundle\\\\Admin\\\\View\\\\PreviewFormViewBuilderInterface\\:\\:disablePreviewWebspaceChooser\\(\\)\\.$#" + count: 1 + path: Admin/NewsAdmin.php + + - + message: "#^Cannot call method getId\\(\\) on mixed\\.$#" + count: 2 + path: Api/Factory/NewsApiDtoFactory.php + + - + message: "#^Parameter \\#1 \\$id of class TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Api\\\\News constructor expects int, int\\|null given\\.$#" + count: 1 + path: Api/Factory/NewsApiDtoFactory.php + + - + message: "#^Using nullsafe method call on non\\-nullable type Sulu\\\\Bundle\\\\RouteBundle\\\\Model\\\\RouteInterface\\. Use \\-\\> instead\\.$#" + count: 1 + path: Api/Factory/NewsApiDtoFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Api\\\\News\\:\\:__construct\\(\\) has parameter \\$content with no value type specified in iterable type array\\.$#" + count: 1 + path: Api/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Api\\\\News\\:\\:__construct\\(\\) has parameter \\$header with no value type specified in iterable type array\\.$#" + count: 1 + path: Api/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Api\\\\News\\:\\:__construct\\(\\) has parameter \\$tags with no value type specified in iterable type array\\.$#" + count: 1 + path: Api/News.php + + - + message: "#^Call to an undefined method Sulu\\\\Component\\\\Rest\\\\ListBuilder\\\\ListBuilderInterface\\:\\:setParameter\\(\\)\\.$#" + count: 1 + path: Common/DoctrineListRepresentationFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Common\\\\DoctrineListRepresentationFactory\\:\\:addHeader\\(\\) has parameter \\$news with no value type specified in iterable type array\\.$#" + count: 1 + path: Common/DoctrineListRepresentationFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Common\\\\DoctrineListRepresentationFactory\\:\\:addHeader\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: Common/DoctrineListRepresentationFactory.php + + - + message: "#^Parameter \\#2 \\$locale of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Common\\\\DoctrineListRepresentationFactory\\:\\:addHeader\\(\\) expects string, mixed given\\.$#" + count: 1 + path: Common/DoctrineListRepresentationFactory.php + + - + message: "#^Parameter \\#2 \\$value of method Sulu\\\\Component\\\\Rest\\\\ListBuilder\\\\ListBuilderInterface\\:\\:where\\(\\) expects string, mixed given\\.$#" + count: 1 + path: Common/DoctrineListRepresentationFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Content\\\\NewsDataItem\\:\\:getId\\(\\) should return string but returns int\\|null\\.$#" + count: 1 + path: Content/NewsDataItem.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Content\\\\NewsDataItem\\:\\:getImage\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: Content/NewsDataItem.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Content\\\\NewsDataItem\\:\\:getTitle\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: Content/NewsDataItem.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Content\\\\NewsDataProvider\\:\\:decorateDataItems\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Content/NewsDataProvider.php + + - + message: "#^Parameter \\#2 \\$locale of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Controller\\\\Admin\\\\NewsController\\:\\:generateApiNewsEntity\\(\\) expects string, string\\|null given\\.$#" + count: 4 + path: Controller/Admin/NewsController.php + + - + message: "#^Parameter \\#2 \\$locale of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:saveNewNews\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: Controller/Admin/NewsController.php + + - + message: "#^Parameter \\#3 \\$locale of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:updateNews\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: Controller/Admin/NewsController.php + + - + message: "#^Cannot call method load\\(\\) on mixed\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Cannot call method mergeGlobals\\(\\) on mixed\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Controller\\\\Website\\\\NewsController\\:\\:indexAction\\(\\) has parameter \\$attributes with no type specified\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Controller\\\\Website\\\\NewsController\\:\\:indexAction\\(\\) has parameter \\$partial with no type specified\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Controller\\\\Website\\\\NewsController\\:\\:indexAction\\(\\) has parameter \\$preview with no type specified\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Controller\\\\Website\\\\NewsController\\:\\:renderBlock\\(\\) has no return type specified\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Controller\\\\Website\\\\NewsController\\:\\:renderPreview\\(\\) has parameter \\$parameters with no value type specified in iterable type array\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: Controller/Website/NewsController.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\AbstractFactory\\:\\:getProperty\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Entity/Factory/AbstractFactory.php + + - + message: "#^Parameter \\#1 \\$id of method Sulu\\\\Bundle\\\\MediaBundle\\\\Entity\\\\MediaRepositoryInterface\\:\\:findMediaById\\(\\) expects int, mixed given\\.$#" + count: 1 + path: Entity/Factory/MediaFactory.php + + - + message: "#^Parameter \\#2 \\$id of class Sulu\\\\Component\\\\Rest\\\\Exception\\\\EntityNotFoundException constructor expects int\\|string, mixed given\\.$#" + count: 1 + path: Entity/Factory/MediaFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\MediaFactoryInterface\\:\\:generateMedia\\(\\) has parameter \\$header with no type specified\\.$#" + count: 1 + path: Entity/Factory/MediaFactoryInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\NewsFactory\\:\\:generateNewsFromRequest\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Entity/Factory/NewsFactory.php + + - + message: "#^Parameter \\#1 \\$datetime of class DateTime constructor expects string, mixed given\\.$#" + count: 2 + path: Entity/Factory/NewsFactory.php + + - + message: "#^Parameter \\#1 \\$enabled of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:setEnabled\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: Entity/Factory/NewsFactory.php + + - + message: "#^Parameter \\#1 \\$teaser of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:setTeaser\\(\\) expects string, mixed given\\.$#" + count: 1 + path: Entity/Factory/NewsFactory.php + + - + message: "#^Parameter \\#1 \\$title of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:setTitle\\(\\) expects string, mixed given\\.$#" + count: 1 + path: Entity/Factory/NewsFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\NewsFactoryInterface\\:\\:generateNewsFromRequest\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Entity/Factory/NewsFactoryInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\NewsRouteFactory\\:\\:updateNewsRoute\\(\\) should return Sulu\\\\Bundle\\\\RouteBundle\\\\Model\\\\RouteInterface but returns Sulu\\\\Bundle\\\\RouteBundle\\\\Model\\\\RouteInterface\\|null\\.$#" + count: 1 + path: Entity/Factory/NewsRouteFactory.php + + - + message: "#^Parameter \\#1 \\$name of method Sulu\\\\Bundle\\\\TagBundle\\\\Tag\\\\TagManagerInterface\\:\\:findOrCreateByName\\(\\) expects string, mixed given\\.$#" + count: 1 + path: Entity/Factory/TagFactory.php + + - + message: "#^Parameter \\#2 \\$requestEntities of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\TagFactory\\:\\:processSubEntities\\(\\) expects array, mixed given\\.$#" + count: 1 + path: Entity/Factory/TagFactory.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\TagFactoryInterface\\:\\:processTags\\(\\) has no return type specified\\.$#" + count: 1 + path: Entity/Factory/TagFactoryInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\TagFactoryInterface\\:\\:processTags\\(\\) has parameter \\$tags with no type specified\\.$#" + count: 1 + path: Entity/Factory/TagFactoryInterface.php + + - + message: "#^Cannot call method getName\\(\\) on mixed\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:addTag\\(\\) has no return type specified\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:getContent\\(\\) has no return type specified\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:getLocale\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:getRoute\\(\\) should return Sulu\\\\Bundle\\\\RouteBundle\\\\Model\\\\RouteInterface but returns Sulu\\\\Bundle\\\\RouteBundle\\\\Model\\\\RouteInterface\\|null\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:getSeo\\(\\) has no return type specified\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:getTagNameArray\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:getTags\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:setContent\\(\\) has parameter \\$content with no type specified\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:setSeo\\(\\) has parameter \\$seo with no type specified\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$changed type mapping mismatch\\: property can contain DateTime\\|null but database expects DateTimeInterface\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$changer \\(TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface\\) does not accept mixed\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$changer has unknown class TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface as its type\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$changer type mapping mismatch\\: database can contain Sulu\\\\Bundle\\\\ContactBundle\\\\Entity\\\\Contact\\|null but property expects TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$changer type mapping mismatch\\: property can contain TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface but database expects Sulu\\\\Bundle\\\\ContactBundle\\\\Entity\\\\Contact\\|null\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$created type mapping mismatch\\: property can contain DateTime\\|null but database expects DateTimeInterface\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$creator \\(TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface\\) does not accept mixed\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$creator has unknown class TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface as its type\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$creator type mapping mismatch\\: database can contain Sulu\\\\Bundle\\\\ContactBundle\\\\Entity\\\\Contact\\|null but property expects TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$creator type mapping mismatch\\: property can contain TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\ContactInterface but database expects Sulu\\\\Bundle\\\\ContactBundle\\\\Entity\\\\Contact\\|null\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$header \\(Sulu\\\\Bundle\\\\MediaBundle\\\\Entity\\\\MediaInterface\\) does not accept mixed\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$header type mapping mismatch\\: database can contain Sulu\\\\Bundle\\\\MediaBundle\\\\Entity\\\\Media\\|null but property expects Sulu\\\\Bundle\\\\MediaBundle\\\\Entity\\\\MediaInterface\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$header type mapping mismatch\\: property can contain Sulu\\\\Bundle\\\\MediaBundle\\\\Entity\\\\MediaInterface but database expects Sulu\\\\Bundle\\\\MediaBundle\\\\Entity\\\\Media\\|null\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$locale type mapping mismatch\\: property can contain string\\|null but database expects string\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$publishedAt type mapping mismatch\\: property can contain DateTime\\|null but database expects DateTimeInterface\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$route type mapping mismatch\\: property can contain Sulu\\\\Bundle\\\\RouteBundle\\\\Model\\\\RouteInterface\\|null but database expects Sulu\\\\Bundle\\\\RouteBundle\\\\Entity\\\\Route\\|null\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$tags has no type specified\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\:\\:\\$title type mapping mismatch\\: property can contain string\\|null but database expects string\\.$#" + count: 1 + path: Entity/News.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\NewsInterface\\:\\:getContent\\(\\) has no return type specified\\.$#" + count: 1 + path: Entity/NewsInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\NewsInterface\\:\\:getCreator\\(\\) has no return type specified\\.$#" + count: 1 + path: Entity/NewsInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Event\\\\NewsCreatedActivityEvent\\:\\:__construct\\(\\) has parameter \\$payload with no value type specified in iterable type array\\.$#" + count: 1 + path: Event/NewsCreatedActivityEvent.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Event\\\\NewsModifiedActivityEvent\\:\\:__construct\\(\\) has parameter \\$payload with no value type specified in iterable type array\\.$#" + count: 1 + path: Event/NewsModifiedActivityEvent.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Event\\\\NewsRemovedActivityEvent\\:\\:__construct\\(\\) has parameter \\$payload with no value type specified in iterable type array\\.$#" + count: 1 + path: Event/NewsRemovedActivityEvent.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:deserialize\\(\\) should return TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News but returns mixed\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:getId\\(\\) has parameter \\$object with no type specified\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:serialize\\(\\) has parameter \\$object with no type specified\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:setContext\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:setContext\\(\\) has parameter \\$object with no type specified\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:setValues\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Preview\\\\NewsObjectProvider\\:\\:setValues\\(\\) has parameter \\$object with no type specified\\.$#" + count: 1 + path: Preview/NewsObjectProvider.php + + - + message: "#^Class TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Repository\\\\NewsRepository extends generic class Doctrine\\\\Bundle\\\\DoctrineBundle\\\\Repository\\\\ServiceEntityRepository but does not specify its types\\: TEntityClass$#" + count: 1 + path: Repository/NewsRepository.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Repository\\\\NewsRepository\\:\\:findByFilters\\(\\) has parameter \\$filters with no value type specified in iterable type array\\.$#" + count: 1 + path: Repository/NewsRepository.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Repository\\\\NewsRepository\\:\\:findById\\(\\) should return TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\|null but returns object\\.$#" + count: 1 + path: Repository/NewsRepository.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Repository\\\\NewsRepository\\:\\:getPublishedNews\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: Repository/NewsRepository.php + + - + message: "#^Parameter \\#1 \\$object of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:remove\\(\\) expects object, object\\|null given\\.$#" + count: 1 + path: Repository/NewsRepository.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Routing\\\\NewsRouteDefaultProvider\\:\\:getByEntity\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: Routing/NewsRouteDefaultProvider.php + + - + message: "#^Cannot access offset 'route' on mixed\\.$#" + count: 2 + path: Service/News/NewsService.php + + - + message: "#^Cannot call method getContact\\(\\) on Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null\\.$#" + count: 3 + path: Service/News/NewsService.php + + - + message: "#^Do not throw the \\\\Exception base class\\. Instead, extend the \\\\Exception base class\\. More info\\: http\\://bit\\.ly/subtypeexception$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Empty catch block\\. If you are sure this is meant to be empty, please add a \"// @ignoreException\" comment in the catch block\\.$#" + count: 2 + path: Service/News/NewsService.php + + - + message: "#^In method \"TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:saveNewNews\", caught \"Exception\" must be rethrown\\. Either catch a more specific exception or add a \"throw\" clause in the \"catch\" block to propagate the exception\\. More info\\: http\\://bit\\.ly/failloud$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^In method \"TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:updateNews\", caught \"Exception\" must be rethrown\\. Either catch a more specific exception or add a \"throw\" clause in the \"catch\" block to propagate the exception\\. More info\\: http\\://bit\\.ly/failloud$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:saveNewNews\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:updateNewsPublish\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^PHPDoc tag @var for property TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsService\\:\\:\\$loginUser with type object\\|string is not subtype of native type Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null\\.$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Parameter \\#1 \\$message of class Exception constructor expects string, int given\\.$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Parameter \\#2 \\$data of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\NewsFactory\\:\\:generateNewsFromRequest\\(\\) expects array, mixed given\\.$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Parameter \\#2 \\$routePath of method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\Factory\\\\NewsRouteFactory\\:\\:updateNewsRoute\\(\\) expects string, mixed given\\.$#" + count: 1 + path: Service/News/NewsService.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsServiceInterface\\:\\:saveNewNews\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: Service/News/NewsServiceInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Service\\\\News\\\\NewsServiceInterface\\:\\:updateNews\\(\\) has parameter \\$data with no type specified\\.$#" + count: 1 + path: Service/News/NewsServiceInterface.php + + - + message: "#^Method TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Twig\\\\NewsTwigExtension\\:\\:resolveNewsFunction\\(\\) should return TheCadien\\\\Bundle\\\\SuluNewsBundle\\\\Entity\\\\News\\|null but returns object\\|null\\.$#" + count: 1 + path: Twig/NewsTwigExtension.php diff --git a/phpstan.neon b/phpstan.neon index 72bce4a..da7e0bc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,6 +6,7 @@ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon + - phpstan-baseline.neon parameters: paths: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9e4552b..8cec0b3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,22 @@ - - - - Tests - - - - - . - - vendor - - - - - - - - \ No newline at end of file + + + + . + + + Resources/ + Tests/ + vendor/ + + + + + Tests + + + + + + + diff --git a/phpunit.xml.dist.bak b/phpunit.xml.dist.bak new file mode 100644 index 0000000..4c6a8ba --- /dev/null +++ b/phpunit.xml.dist.bak @@ -0,0 +1,22 @@ + + + + + Tests + + + + + . + + Resources/ + Tests/ + vendor/ + + + + + + + + \ No newline at end of file diff --git a/processTags/doctrine/phpcr-bundle/tests/Fixtures/App/var/.gitempty b/processTags/doctrine/phpcr-bundle/tests/Fixtures/App/var/.gitempty new file mode 100644 index 0000000..e69de29 diff --git a/readme.md b/readme.md index edeca19..0c435c1 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ project: ```bash -composer require thecadien/sulu-news-bundle --with-all-dependencies +composer require thecadien/sulu-news-bundle ``` Afterwards, visit the [bundle documentation](Resources/docs) to find out how to set up and configure the SuluNewsBundle to your specific needs. \ No newline at end of file