Symfony Api Platform Normalization Context Groups datetime property issue - symfony

I'm struggle with strange issue when using normalizer with groups. It look like there is a problem with datetime properties.
Issue occurs when using normalizationContext on ApiResource and/or normalization_context on operation.
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ApiResource(
* collectionOperations={
* "get"={
* "normalization_context"={
* "datetime_format"="Y-m-d\TH:i:sP",
* "groups"={"bookmarkdata:collection:read"}
* }
* }
* },
* itemOperations={
* "get"
* },
* normalizationContext={"groups"={"bookmarkdata:read"}}
* )
* #ORM\Entity(repositoryClass="App\Repository\BookmarkDataRepository")
*/
class BookmarkData
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"bookmarkdata:read", "bookmarkdata:collection:read"})
*/
private $id;
/**
* #ORM\Column(type="string", length=2048, nullable=true)
* #Groups({"bookmarkdata:read", "bookmarkdata:collection:read"})
*/
private $screenshot;
/**
* #ORM\Column(type="string", length=2048)
* #Groups({"bookmarkdata:read", "bookmarkdata:collection:read"})
*/
private $dump;
/**
* #ORM\Column(type="datetime")
* #Groups({"bookmarkdata:read", "bookmarkdata:collection:read"})
*/
private $created_at;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updated_at;
public function getId(): ?int
{
return $this->id;
}
public function getScreenshot(): ?string
{
return $this->screenshot;
}
public function setScreenshot(?string $screenshot): self
{
$this->screenshot = $screenshot;
return $this;
}
public function getDump(): ?string
{
return $this->dump;
}
public function setDump(string $dump): self
{
$this->dump = $dump;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeInterface $created_at): self
{
$this->created_at = $created_at;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(?\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
}
In result property created_at ignored.
{
"#context": "/api/contexts/BookmarkData",
"#id": "/api/bookmark_datas",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/bookmark_datas/1",
"#type": "BookmarkData",
"id": 1,
"screenshot": "This is screen",
"dump": "This is dump"
}
],
"hydra:totalItems": 1
}
I'm using Docker.
My dependencies.
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/api-pack": "^1.2",
"doctrine/doctrine-migrations-bundle": "^2.1",
"easycorp/easyadmin-bundle": "^2.3",
"lexik/jwt-authentication-bundle": "^2.6",
"nesbot/carbon": "^2.32",
"symfony/console": "4.4.*",
"symfony/dotenv": "4.4.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "4.4.*",
"symfony/security-bundle": "4.4.*",
"symfony/yaml": "4.4.*"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"fzaninotto/faker": "^1.9",
"symfony/maker-bundle": "^1.15",
"symfony/profiler-pack": "^1.0"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "4.4.*"
}
}
}

Related

Aggregate values on API Platform response

Similar to this question, I am using API Platform with Doctrine entities - I have an Entity which contains a value:
/**
* #ApiResource()
*/
class Credit
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $value;
}
I would like to retrieve the sum of this value and return it in the top level element of the response when querying for a collection:
{
"#context": "/api/contexts/Credit",
"#id": "/api/credits",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/credits/1",
"#type": "Credit",
"id": 1,
"value": 200,
"createdAt": "2019-03"
},
{
"#id": "/api/credits/2",
"#type": "Credit",
"id": 2,
"value": 200,
"createdAt": "2019-04"
}
],
"hydra:totalItems": 2,
"totalValues": 400
}
However, I would like to achieve this using a copy of the query instead of summing the values after the execution to maintain the same totalValues amount when pagination is applied - much the same way that hydra:totalItems will always return the total number of items.
What would be the best way to achieve this?
You can create a CreditCollectionNormalizer:
<?php
namespace App\Serializer\Normalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
class CreditCollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
public const RESOURCE_CLASS = \App\Entity\Credit::class;
private const ALREADY_CALLED = 'CREDIT_COLLECTION_NORMALIZER_ALREADY_CALLED';
private NormalizerInterface $normalizer;
public function __construct(NormalizerInterface $normalizer)
{
$this->normalizer = $normalizer;
}
public function normalize($object, string $format = null, array $context = []): array
{
$context[self::ALREADY_CALLED] = true;
$data = $this->normalizer->normalize($object, $format, $context);
if ('collection' === $context['operation_type'] &&
'get' === $context['collection_operation_name'] &&
self::RESOURCE_CLASS == $context['resource_class']) {
$balance = 0;
foreach ($object as $item) {
$balance += $item->amount;
}
$data['hydra:meta']['balance'] = $balance;
}
return $data;
}
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return $this->normalizer->supportsNormalization($data, $format, $context);
}
public function setNormalizer(NormalizerInterface $normalizer)
{
if ($this->normalizer instanceof NormalizerAwareInterface) {
$this->normalizer->setNormalizer($normalizer);
}
}
}
Then register it as a CollectionNormalizer decorator in services.yaml:
services:
App\Serializer\Normalizer\CreditCollectionNormalizer:
decorates: 'api_platform.hydra.normalizer.collection'
arguments: [ '#App\Serializer\Normalizer\CreditCollectionNormalizer.inner' ]
public: false
api/credits should now return the following:
{
"#context": "/api/contexts/Credit",
"#id": "/api/credits",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/credits/1",
"#type": "Credit",
"id": 1,
"value": 200,
"createdAt": "2019-03"
},
{
"#id": "/api/credits/2",
"#type": "Credit",
"id": 2,
"value": 200,
"createdAt": "2019-04"
}
],
"hydra:totalItems": 2,
"hydra:meta": {
"balance": 400
}
}

Symfony 4 and fos user bundle not working

I'm new to Symfony. I've installed version 4 and fos user bundle 2.0. I want to override the default registration type and i added my own validations on fields but they are not considered. I tried to use validation on entity and it doesn't work.
Here is my json is here:
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"cayetanosoriano/hashids-bundle": "^1.1",
"friendsofsymfony/user-bundle": "~2.0",
"gregwar/image-bundle": "^3.0",
"hashids/hashids": "~1.0",
"nexmo/client": "^2.0",
"odolbeau/phone-number-bundle": "^1.3",
"passwordlib/passwordlib": "^1.0#beta",
"sensio/framework-extra-bundle": "^5.5",
"symfony/asset": "4.4.*",
"symfony/console": "4.4.*",
"symfony/dotenv": "4.4.*",
"symfony/flex": "^1.3.1",
"symfony/form": "4.4.*",
"symfony/framework-bundle": "4.4.*",
"symfony/intl": "4.4.*",
"symfony/mailer": "4.4.*",
"symfony/messenger": "4.4.*",
"symfony/monolog-bundle": "^3.5",
"symfony/options-resolver": "4.4.*",
"symfony/security-bundle": "4.4.*",
"symfony/security-core": "4.4.*",
"symfony/security-csrf": "4.4.*",
"symfony/security-guard": "4.4.*",
"symfony/security-http": "4.4.*",
"symfony/translation": "4.4.*",
"symfony/twig-bundle": "4.4.*",
"symfony/twig-pack": "^1.0",
"symfony/yaml": "4.4.*",
"twig/cssinliner-extra": "^3.0",
"twig/extensions": "^1.5",
"twig/inky-extra": "^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^5.0",
"symfony/profiler-pack": "^1.0"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "4.4.*"
}
}
}
My config file
fos_user:
db_driver: orm # other valid values are 'mongodb' and 'couchdb'
firewall_name: main
user_class: App\Entity\User\User
from_email:
address: "contact#flyout0.com"
sender_name: "Josaphat Imani"
service:
mailer: fos_user.mailer.twig_swift
# user_manager: custom_user_manager
# class: App\Repository\User\UserRepository
# arguments: ['#fos_user.util.password_updater', '#fos_user.util.canonical_fields_updater', '#doctrine.orm.entity_manager', sApp\Entity\User\User]
registration:
# confirmation:
# enabled: true
# from_email:
# address: "contact#flyout0.com"
# sender_name: "Josaphat Imani"
form:
type: App\Form\Type\User\RegistrationType
name: app_user_registration
My registration form
class RegistrationType extends AbstractType
{
protected $countryRepository;
protected $realCountry;
protected $currentCountry;
public function __construct(CountryRepository $m, SessionInterface $session)
{
$this->countryRepository = $m;
$this->realCountry = $session->get('realCity')->getCountry();
$this->currentCountry = $session->get('city')->getCountry();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('names', TextType::class, [
'constraints' => [
new Assert\Length([
'min' => 3,
'max' => 255,
'minMessage' => 'invalid.names',
'maxMessage' => 'invalid.names'
]),
new ValidName(),
],
'invalid_message' => 'invalid.value',
])
->remove('email')
->add('username', TextType::class, array(
'invalid_message' => 'invalid.username',
'constraints' => [
new Assert\NotBlank(),
new ValidNewUsername()
],
))
->add('plainPassword', PasswordType::class, array(
'invalid_message' => 'invalid.value',
'constraints' => [
new Assert\Length([
'min' => 6,
'minMessage' => 'password.too.low',
'max' => 72,
'maxMessage' => 'password.too.long',
]),
]
))
->addEventSubscriber(new RegistrationSubscriber());
}
public function getParent()
{
return RegistrationFormType::class;
}
public function getBlockPrefix()
{
return 'app_user_registration';
}
}
My entity
<?php
namespace App\Entity\User;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Home\Image;
use Doctrine\ORM\Mapping\AttributeOverrides;
use Doctrine\ORM\Mapping\AttributeOverride;
use libphonenumber\PhoneNumber;
use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
use App\Validator\Constraints\User\ValidName;
use App\Validator\Constraints\User\ValidNewUsername;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Constraints as Assert;
use App\Validator\Constraints\User as UserAssert;
/**
* #ORM\Entity(repositoryClass="App\Repository\User\UserRepository")
* #ORM\Table(name="Fos_User")
* #AttributeOverrides({
* #AttributeOverride(name="username",
* column=#ORM\Column(
* name="username",
* type="string",
* length=255,
* unique=false,
* nullable=true
* )
* )
* })
* #UniqueEntity(fields="email", message="email.used")
* #UniqueEntity(fields="telNumber", message="tel.number.used")
* #Assert\Callback(methods={"validateNames"})
*/
class User extends BaseUser
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="names", type="string", length=255)
* #UserAssert\ValidName
* #Assert\Length({"in"="50"})
* #Assert\Choice(
* choices = { "male", "female" },
* message = "Choose a valid gender."
* )
*/
protected $names;
/**
* #var string
*
* #ORM\Column(name="birthday", type="date", nullable=true)
*/
protected $birthday;
public function __construct()
{
parent::__construct();
// your own logic
$this->roles = array('ROLE_USER');
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And i'm receiving errors after registration on this repository
<?php
namespace App\Repository\User;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function findUserByUsernameOrEmail($usernameOrEmail)
{
if (filter_var($usernameOrEmail, FILTER_VALIDATE_EMAIL)) {
return $this->findUserBy(array(
'emailCanonical' => $this->canonicalizeEmail($usernameOrEmail)
));
}
return $this->createQueryBuilder('user_manager')
->where('user_m.phone_number = :phone_number')
->setParameter(':phone_number', $usernameOrEmail)
->getQuery()
->getOneOrNullResult();
}
}
I need your help please because i've read the symfony documentation and other answers in this site but nothing worked.

How to add an extra information on api-platform result

I'am using symfony and api platform.
I have a ressource with :
/**
* #ApiResource()
*/
class Credit
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $value;
}
the result of /api/credits is :
{
"#context": "/api/contexts/Credit",
"#id": "/api/credits",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/credits/1",
"#type": "Credit",
"id": 1,
"value": 200,
"createdAt": "2019-03"
},
{
"#id": "/api/credits/2",
"#type": "Credit",
"id": 2,
"value": 200,
"createdAt": "2019-04"
}
],
"hydra:totalItems": 2
}
i want to add an extra information to my result like the totalValues :
400 ( sum of "value" of all results )
how can i do it
expected result :
{
"#context": "/api/contexts/Credit",
"#id": "/api/credits",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/credits/1",
"#type": "Credit",
"id": 1,
"value": 200,
"createdAt": "2019-03"
},
{
"#id": "/api/credits/2",
"#type": "Credit",
"id": 2,
"value": 200,
"createdAt": "2019-04"
}
],
"hydra:totalItems": 2,
"totalValues": 400
}
Solution is to implement NormalizerInterface and NormalizerAwareInterface like this :
ApiCollectionNormalizer :
namespace App\Serializer;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class ApiCollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
/**
* #var NormalizerInterface|NormalizerAwareInterface
*/
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
if (!$decorated instanceof NormalizerAwareInterface) {
throw new \InvalidArgumentException(
sprintf('The decorated normalizer must implement the %s.', NormalizerAwareInterface::class)
);
}
$this->decorated = $decorated;
}
/**
* #inheritdoc
*/
public function normalize($object, $format = null, array $context = [])
{
$data = $this->decorated->normalize($object, $format, $context);
if ('collection' === $context['operation_type'] && 'get' === $context['collection_operation_name']) {
if ($data['#id'] === '/api/credits') {
$totalValues = 0;
foreach ($data['hydra:member'] as $credit) {
$totalValues += $credit['value'];
}
$data['totalValues'] = $totalValues;
}
}
return $data;
}
/**
* #inheritdoc
*/
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
/**
* #inheritdoc
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
$this->decorated->setNormalizer($normalizer);
}
}
services.yaml :
'App\Serializer\ApiCollectionNormalizer':
decorates: 'api_platform.hydra.normalizer.collection'
arguments: [ '#App\Serializer\ApiCollectionNormalizer.inner' ]

Symfony 4.2/API-platform: non-deterministic security related errors

We're developing a REST API using symfony 4.2 and API-platform and we're experience a strange behavior. When we issue e.g. GET HTTP requests on the generated endpoints : we either get the expected JSON response or a 500 error for the very same request, randomly as it seems. Interestingly enough:
the random errors are experienced using our angular client
the errors NEVER show when issuing curl commands on the very same URLs
Here is the beginning of the returned JSON stack trace when an error occurs:
{
"type": "https://tools.ietf.org/html/rfc2616#section-10",
"title": "An error occurred",
"detail": "The class 'App\\Security\\User\\AppUser' was not found in the chain configured namespaces App\\Entity, Vich\\UploaderBundle\\Entity",
"trace": [
{
"namespace": "",
"short_class": "",
"class": "",
"type": "",
"function": "",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/MappingException.php",
"line": 22,
"args": []
},
{
"namespace": "Doctrine\\Common\\Persistence\\Mapping",
"short_class": "MappingException",
"class": "Doctrine\\Common\\Persistence\\Mapping\\MappingException",
"type": "::",
"function": "classNotFoundInNamespaces",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php",
"line": 87,
"args": [
[
"string",
"App\\Security\\User\\AppUser"
],
[
"array",
[
[
"string",
"App\\Entity"
],
[
"string",
"Vich\\UploaderBundle\\Entity"
]
]
]
]
},
{
"namespace": "Doctrine\\Common\\Persistence\\Mapping\\Driver",
"short_class": "MappingDriverChain",
"class": "Doctrine\\Common\\Persistence\\Mapping\\Driver\\MappingDriverChain",
"type": "->",
"function": "loadMetadataForClass",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php",
"line": 151,
"args": [
[
"string",
"App\\Security\\User\\AppUser"
],
[
"object",
"Doctrine\\ORM\\Mapping\\ClassMetadata"
]
]
},
{
"namespace": "Doctrine\\ORM\\Mapping",
"short_class": "ClassMetadataFactory",
"class": "Doctrine\\ORM\\Mapping\\ClassMetadataFactory",
"type": "->",
"function": "doLoadMetadata",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php",
"line": 305,
"args": [
[
"object",
"Doctrine\\ORM\\Mapping\\ClassMetadata"
],
[
"null",
null
],
[
"boolean",
false
],
[
"array",
[]
]
]
},
{
"namespace": "Doctrine\\Common\\Persistence\\Mapping",
"short_class": "AbstractClassMetadataFactory",
"class": "Doctrine\\Common\\Persistence\\Mapping\\AbstractClassMetadataFactory",
"type": "->",
"function": "loadMetadata",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php",
"line": 78,
"args": [
[
"string",
"App\\Security\\User\\AppUser"
]
]
},
{
"namespace": "Doctrine\\ORM\\Mapping",
"short_class": "ClassMetadataFactory",
"class": "Doctrine\\ORM\\Mapping\\ClassMetadataFactory",
"type": "->",
"function": "loadMetadata",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php",
"line": 183,
"args": [
[
"string",
"App\\Security\\User\\AppUser"
]
]
},
{
"namespace": "Doctrine\\Common\\Persistence\\Mapping",
"short_class": "AbstractClassMetadataFactory",
"class": "Doctrine\\Common\\Persistence\\Mapping\\AbstractClassMetadataFactory",
"type": "->",
"function": "getMetadataFor",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php",
"line": 283,
"args": [
[
"string",
"App\\Security\\User\\AppUser"
]
]
},
{
"namespace": "Doctrine\\ORM",
"short_class": "EntityManager",
"class": "Doctrine\\ORM\\EntityManager",
"type": "->",
"function": "getClassMetadata",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/doctrine-bundle/Repository/ContainerRepositoryFactory.php",
"line": 45,
"args": [
[
"string",
"App\\Security\\User\\AppUser"
]
]
},
{
"namespace": "Doctrine\\Bundle\\DoctrineBundle\\Repository",
"short_class": "ContainerRepositoryFactory",
"class": "Doctrine\\Bundle\\DoctrineBundle\\Repository\\ContainerRepositoryFactory",
"type": "->",
"function": "getRepository",
"file": "/home/beta/www/cel2-dev/cel2-services/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php",
"line": 713,
"args": [
[
"object",
"Doctrine\\ORM\\EntityManager"
],
[
"string",
"App\\Security\\User\\AppUser"
]
]
}
...
}
For the sake of simplicity, we implemented a dummy AbstractGuardAuthenticator which always returns a hard coded user and which exhibits the very same behavior. Here follows its code, the code of the user entity class along with the interesting bits of the security configuration:
<?php
namespace App\Security;
use App\Security\User\AppUser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class YesAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request)
{
return true;
}
public function getCredentials(Request $request)
{
return array(
'token' => 'sdflsdklfjsdlkfjslkdfjsldk46541qsdf',
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$user = new AppUser(22, 'toto#wanadoo.fr', 'toto', 'litoto', 'tl', 'totoL', '', array(), null);
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = array(
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
);
return new JsonResponse($data, Response::HTTP_FORBIDDEN);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$data = array(
'message' => 'Authentication Required'
);
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}
The user entity class:
<?php
namespace App\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
/**
* Represents a user logged in the SSO authentication system.
*
* #package App\Security\User
*/
class AppUser implements UserInterface, EquatableInterface {
private $id;
private $email;
private $pseudo;
private $avatar;
private $surname;
private $lastName;
private $usePseudo;
private $administeredProjectId;
private $roles;
const ADMIN_ROLE = "administrator";
public function __construct(
$id, $email, $surname, $lastName, $pseudo, $usePseudo, $avatar,
array $roles, $administeredProjectId) {
$this->id = $id;
$this->email = $email;
$this->surname = $surname;
$this->lastName = $lastName;
$this->pseudo = $pseudo;
$this->usePseudo = $usePseudo;
$this->avatar = $avatar;
$this->administeredProjectId = $administeredProjectId;
$this->roles = $roles;
}
public function setId($idd) {
$this->id = $idd;
}
public function getId() {
return $this->id;
}
public function isAdmin() {
return in_array(AppUser::ADMIN_ROLE, $this->roles);
}
public function isProjectAdmin() {
return (!is_null($this->administeredProjectId));
}
public function isLuser() {
return (
!( $this->isAdmin() ) ||
( $this->isProjectAdmin() ) );
}
public function getRoles() {
return $this->roles;
}
public function getSurname() {
return $this->surname;
}
public function getLastName() {
return $this->lastName;
}
public function getAvatar() {
return $this->avatar;
}
public function getAdministeredProjectId() {
return $this->administeredProjectId;
}
public function getPassword() {
return null;
}
public function getSalt() {
return null;
}
public function getEmail() {
return $this->email;
}
public function getUsername() {
return $this->usePseudo ? $this->pseudo : ($this->surname . ' ' . $this->lastName);
}
public function getPseudo() {
return $this->pseudo;
}
public function eraseCredentials() {
}
public function isEqualTo(UserInterface $user) {
if (!$user instanceof AppUser) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}
Extract of the security.yaml file:
security:
providers:
user:
entity:
class: App\Security\User\AppUser
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: true
guard:
authenticators:
- App\Security\YesAuthenticator
main:
anonymous: ~
logout: ~
guard:
authenticators:
- App\Security\YesAuthenticator
Here is an example of an entity/resource for which Web services are exhibiting the behavior. Nothing fancy...
<?php
namespace App\Entity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* #ORM\Entity
* #ORM\Table(name="tb_project")
* #ApiResource(attributes={
* "normalization_context"={"groups"={"read"}},
* "formats"={"jsonld", "json"},
* "denormalization_context"={"groups"={"write"}}},
* collectionOperations={"get"},
* itemOperations={"get"}
* )
*/
class Project
{
/**
* #Groups({"read"})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*/
private $id = null;
/**
* #Groups({"read"})
* #ORM\OneToOne(targetEntity="Project")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
/**
* #Assert\NotNull
* #Groups({"read"})
* #ORM\Column(type="string", nullable=false)
*/
private $label = null;
/**
*
* #Assert\NotNull
* #Groups({"read"})
* #ORM\Column(name="is_private", type="boolean", nullable=false)
*/
private $isPrivate = true;
public function getId(): ?int {
return $this->id;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getIsPrivate(): ?bool
{
return $this->isPrivate;
}
public function setIsPrivate(bool $isPrivate): self
{
$this->isPrivate = $isPrivate;
return $this;
}
public function getParent(): ?self
{
return $this->parent;
}
public function setParent(?self $parent): self
{
$this->parent = $parent;
return $this;
}
public function __clone() {
if ($this->id) {
$this->id = null;
}
}
public function __toString()
{
return $this->getLabel();
}
}
Please note we have other Web services with custom data providers filtering returned data based upon the current user. In a non-deterministic manner, requests to these also sometimes fail because the current user is null (while a user is indeed logged).

Unknown Entity namespace alias 'AppBundle' and "Class 'App\\Entity\\Product' does not exist

I'm trying to implement FOSRestBundle so I've just created my first class controller.
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\View\View;
use App\Entity\Product;
class ProductController extends FOSRestController
{
/**
* #Rest\Get("/product")
*/
public function getAction()
{
$em = $this->getDoctrine()->getManager();
$restresult = $em->getRepository('AppBundle:Product')->getAllProduct();
if ($restresult === null) {
return new View("there are no products exist", Response::HTTP_NOT_FOUND);
}
return $restresult;
} // "Get products" [GET] /product*/
}
But Symfony server give me that error:
- "Unknown Entity namespace alias 'AppBundle'."
- "Doctrine\ORM\ORMException";
My composer.json is:
{
"name": "symfony/framework-standard-edition",
"license": "MIT",
"type": "project",
"description": "The \"Symfony Standard Edition\" distribution",
"autoload": {
"psr-4": {
"AppBundle\\": "src/AppBundle"
},
"psr-0": {"": "src/"},
"classmap": [ "app/AppKernel.php", "app/AppCache.php" ]
},
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" },
"files": [ "vendor/symfony/symfony/src/Symfony/Component/VarDumper/Resources/functions/dump.php" ]
},
"require": {
"php": ">=5.5.9",
"doctrine/doctrine-bundle": "^1.6",
"doctrine/orm": "^2.5",
"friendsofsymfony/rest-bundle": "^2.3",
"incenteev/composer-parameter-handler": "^2.0",
"jms/serializer-bundle": "^2.4",
"nelmio/cors-bundle": "^1.5",
"sensio/distribution-bundle": "^5.0.19",
"sensio/framework-extra-bundle": "^5.0.0",
"symfony/monolog-bundle": "^3.1.0",
"symfony/polyfill-apcu": "^1.0",
"symfony/swiftmailer-bundle": "^2.6.4",
"symfony/symfony": "3.4.*",
"twig/twig": "^1.0||^2.0",
"symfony/orm-pack": "^1.0"
},
"require-dev": {
"sensio/generator-bundle": "^3.0",
"symfony/phpunit-bridge": "^4.1"
},
"scripts": {
"symfony-scripts": [
"Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
],
"post-install-cmd": [
"#symfony-scripts"
],
"post-update-cmd": [
"#symfony-scripts"
]
},
"extra": {
"symfony-app-dir": "app",
"symfony-bin-dir": "bin",
"symfony-var-dir": "var",
"symfony-web-dir": "web",
"symfony-tests-dir": "tests",
"symfony-assets-install": "relative",
"incenteev-parameters": {
"file": "app/config/parameters.yml"
},
"branch-alias": {
"dev-master": "3.4-dev"
}
}
Product Entity class is:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table(name="Product")
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="idProduct", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idproduct;
/**
* #var string
*
* #ORM\Column(name="productName", type="string", length=100, nullable=false)
*/
private $productname;
/**
* #var boolean
*
* #ORM\Column(name="Purchased", type="boolean", nullable=true)
*/
private $purchased;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="User", mappedBy="productproduct")
*/
private $useruser;
/**
* Constructor
*/
public function __construct()
{
$this->useruser = new \Doctrine\Common\Collections\ArrayCollection();
}
}
and then, ProductRepository file is:
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function getAllProduct()
{
$conn = $this->getEntityManager()->getConnection();
$sql = "SELECT * from product";
$stmt = $conn->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
}
I've also tried to replace
$restresult = $em->getRepository('AppBundle:Product')->getAllProduct();
with
$restresult = $em->getRepository(Product::class)->getAllProduct();
but the error now is:
-"Class 'App\Entity\Product' does not exist"
-"Doctrine\Common\Persistence\Mapping\MappingException"
I've already tried the solutions of other stack overlow's users about this topic but nothing.
Someone can help me please?
use App\Entity\Product;
This should be the culprit. (in your Controller)
EDIT: Sorry, missed the 1st error. Could you post your doctrine config? Is automapping on?
In ProductController file
instead of
use App\Entity\Product;
replace it with
use AppBundle\Entity\Product;
Your dir structure is wrong. https://github.com/axeowl/projectKobe/tree/master/src
Entity and Repo dirs should be inside AppBundle.

Resources