Symfony2 FOSUserBundle Invitation : only work on owning side associations - symfony

Entity/User
namespace My\SampleBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends \FOS\UserBundle\Entity\User
{
/** #ORM\Id #ORM\Column(type="integer") #ORM\GeneratedValue(strategy="AUTO") */
protected $id;
/**
* #ORM\OneToOne(targetEntity="Invitation", inversedBy="user")
* #ORM\JoinColumn(referencedColumnName="code")
* #Assert\NotNull(message="Your invitation is wrong")
*/
protected $invitation;
public function setInvitation(Invitation $invitation)
{
$this->invitation = $invitation;
}
public function getInvitation()
{
return $this->invitation;
}
}
Entity/Invitation
namespace My\SampleBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity */
class Invitation
{
/** #ORM\OneToOne(targetEntity="User", mappedBy="invitation", cascade={"persist", "merge"}) */
protected $user;
/** #ORM\Id #ORM\Column(type="string", length=6) */
protected $code;
/** #ORM\Column(type="string", length=256) */
protected $email;
/**
* When sending invitation be sure to set this value to `true`
*
* It can prevent invitations from being sent twice
*
* #ORM\Column(type="boolean")
*/
protected $sent = false;
public function __construct()
{
// generate identifier only once, here a 6 characters length code
$this->code = substr(md5(uniqid(rand(), true)), 0, 6);
}
public function getCode()
{
return $this->code;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function isSent()
{
return $this->sent;
}
public function send()
{
$this->sent = true;
}
public function getUser()
{
return $this->user;
}
public function setUser(User $user)
{
$this->user = $user;
}
/**
* Set code
*
* #param string $code
* #return Invitation
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Set sent
*
* #param boolean $sent
* #return Invitation
*/
public function setSent($sent)
{
$this->sent = $sent;
return $this;
}
/**
* Get sent
*
* #return boolean
*/
public function getSent()
{
return $this->sent;
}
}
Error
You cannot search for the association field
'My\SampleBundle\Entity\Invitation#user', because it is the inverse
side of an association. Find methods only work on owning side
associations. 500 Internal Server Error - ORMException
I am the stage which just performed the documentation.
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/adding_invitation_registration.md
The display of the bundle is normal. However, It has become an error if I submit registration form.
Any help?
EDIT:
Actually, I had set up 'inversedBy' at first.
A pre-question.
Symfony2 FOSUserBundle Invitation : 'inversedBy' mapping errors
On the surface, it does work. However, mapping errors is displayed by profiler.
My\SampleBundle\Entity\Invitation# does not contain the required
'inversedBy' attribute.
so, I changed it in response to advice.
I don't know what to think of it.

It's just as the error says. Instead of mappedBy, you should use inversedBy on the Invitation entity and use mappedBy on the User entity for $invitation.
/** #ORM\OneToOne(targetEntity="User", inversedBy="invitation", cascade={"persist", "merge"}) */
protected $user;
You can also overcome this problem by creating a custom repository method to find user based on invitation.

There was solution.
https://github.com/FriendsOfSymfony/FOSUserBundle/issues/800
public function reverseTransform($value)
{
// ...
return $this->entityManager
->getRepository('My\SampleBundle\Entity\Invitation')
->findOneBy(array(
'code' => $value,
'user' => null, <= Removing 'user' solves the issue
));
}

I ended up modifying my transformer to the following:
public function reverseTransform($value)
{
if (null === $value || '' === $value) {
return null;
}
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');
}
$invitation = $this->entityManager
->getRepository('SixString\PearBundle\Entity\Invitation')
->findOneBy(array(
'code' => $value,
));
if($this->entityManager->getRepository('SixString\PearBundle\Entity\User')->findOneBy(array("invitation" => $invitation))){
return null;
}
return $invitation;
}
I stripped out the 'user' => null but added a check to see if the invitation has already been used

Related

Symfony 4 with Doctrine - save ManyToOne

I have problem with my symfony code:
My repository update method
/**
* #param MenuModel $menu
* #return MenuEntity|MenuModel
* #throws RepositoryException
*/
public function updateMenu(MenuModel $menu)
{
try {
$transformedMenu = $this->menuTransformer->transform($menu);
$transformedMenu = $this->getEntityManager()->merge($transformedMenu);
$this->getEntityManager()->flush($transformedMenu);
$this->getEntityManager()->detach($transformedMenu);
return $this->menuTransformer->transform($transformedMenu);
} catch (\Exception $e) {
throw new RepositoryException($e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
}
}
My Element entity:
/**
* Element.
*
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="element")
* #ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
* #ORM\Entity(repositoryClass="CP\API\Repository\ElementRepository")
*/
class Element extends \CP\RestBundle\Model\Element
{
use Traits\SystemObjectTrait;
use Traits\ChannelsTrait;
use Traits\PropertiesTrait;
use Traits\WorkflowTrait;
/**
* #ORM\ManyToOne(targetEntity="Menu", inversedBy="contents")
* #ORM\JoinColumn(name="menu_id", referencedColumnName="id")
*/
protected $menu;
/**
* Element constructor.
*/
public function __construct()
{
parent::__construct();
}
/**
* #param int $id
*
* #return Element
*/
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
/**
* Update publication by modification when empty
* Update status according to publication, unpublication and archiving
*
* #ORM\PrePersist()
*/
protected function prePersist()
{
$this->updatePublication();
$this->updateStatus();
}
/**
* Update publication by modification when empty
* Update status according to publication, unpublication and archiving
*
* #ORM\PreUpdate()
*/
public function preUpdate()
{
$this->updatePublication();
$this->updateStatus();
}
/**
* Increases object version
*/
public function increaseVersion()
{
++$this->version;
}
/**
* #return mixed
*/
public function getMenu()
{
return $this->menu;
}
/**
* #param mixed $menu
*/
public function setMenu($menu): void
{
$this->menu = $menu;
}
}
My Menu entity
<?php
namespace CP\API\Entity;
use CP\API\Entity\Traits\TimestampableTrait;
use CP\Model\Configuration;
use CP\Model\Content;
use CP\Model\Language;
use CP\Model\MenuTranslation;
use CP\RestBundle\Model\Locator;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="custom_menu")
* #ORM\Entity(repositoryClass="CP\API\Repository\MenuRepository")
*/
class Menu
{
use TimestampableTrait;
/**
* #var int
*
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="CP\API\Entity\Element",mappedBy="menu",cascade={"persist"})
*/
protected $contents;
public function __construct(Menu $parent = null)
{
$dateTime = new \DateTime();
$this->creation = $dateTime;
$this->modification === null && $this->setModification($dateTime);
$this->contents = new ArrayCollection();
}
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* #return ArrayCollection
*/
public function getContents(): ArrayCollection
{
return $this->contents;
}
/**
* #param ArrayCollection $contents
*/
public function setContents(ArrayCollection $contents): void
{
$this->contents = $contents;
}
/**
* #param Element $element
* #return $this
*/
public function addContent(Element $element): self
{
if (!$this->contents->contains($element)) {
$this->contents[] = $element;
$element->setMenu($this);
}
return $this;
}
/**
* #param Element $element
* #return $this
*/
public function removeContent(Element $element): self
{
if ($this->contents->contains($element)) {
$this->contents->removeElement($element);
if ($element->getMenu() === $this) {
$element->setMenu(null);
}
}
return $this;
}
}
My menu model
<?php
namespace CP\Model;
use CP\RestBundle\Model\Element;
use CP\RestBundle\Model\Locator;
use CP\RestBundle\Model\Product;
use CP\RestBundle\Model\Traits\TimestampableTrait;
class Menu extends BaseModel
{
/** #var Content[] */
protected $content;
/**
* #return array|null
*/
public function getContent(): ?array
{
return $this->content;
}
/**
* #param Content[] $content
*/
public function setContent(array $content): void
{
$this->content = $content;
}
}
My transformer from model to entity
public function transform($object)
{
if ($object instanceof Menu) {
$menuData = new MenuEntity();
if ($object->getId())
$menuData->setId($object->getId());
if ($object->getContent() instanceof Content) {
$contentEntity = new ContentEntity();
$content = $object->getContent();
$contentEntity->setId($content->getId());
$menuData->addContent($this->attachToEntityManager($contentEntity));
}
return $menuData;
}
private function attachToEntityManager($object)
{
try {
$attachedObject = $this->entityManager->merge($object);
return $attachedObject;
} catch (ORMException $e) {
throw new ModelTransformationException(sprintf('Model transformation error, object could not be attached to Entity Manager: %s in %s',
$e->getMessage(), $e->getFile() . ':' . $e->getLine()));
}
}
When i try saved data then i don't have any error, but on database nothing change i.e. element entity don't have assign menu_id from relation. I don't know what is wrong, maybe i don't know how to use and save OneToMany relation.
Any idea?
Can you give us more context of what your code is doing ? The constructor of MenuEntity is strange, no use of $parent, a boolean comparaison isn't used. Why do you need to use detach and merge methods of entityManager
- Mcsky
Is right, I don't see the point of using detach and/or merge here.
Normally with a OneToMany relationship you use entity manager and persist both relations, flush and you should have an entity with a OneToMany relationship.
I hope this might be helpful, specifically this part: https://symfony.com/doc/current/doctrine/associations.html#saving-related-entities
example:
// relates this product to the category
$product->setCategory($category);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($category);
$entityManager->persist($product);
$entityManager->flush();

Symfony authenticator: In controllers, Doctrine returns user item with empty string field, although a value is set

My database schema consists mainly of the following entities: user and auth_token. Each user can have multiple auth_token's.
The problem: when selecting the currently authenticated user in a controller, the string field saltedPasswordHash is empty (""), although a value is set in the database. Getting saltedPasswordHash in the ApiKeyAuthenticator.php works (please have a look at the two TODO comments).
For whatever reason, selecting the email (string) or created (datetime) field works. Persisting new user entities with a saltedPasswordHash or selecting any other user item works fine.
An APIKeyAuthenticator is handling the authorization. When disabling firewall and authentication, everything works as expected. I included the source files below.
I'm using PHP 7.2.15-1 with mysql Ver 15.1 Distrib 10.3.13-MariaDB.
Security/ApiKeyAuthenticator.php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
public function createToken(Request $request, $providerKey)
{
$apiKey = $request->headers->get('authToken');
if (!$apiKey) {
throw new BadCredentialsException();
// or to just skip api key authentication
// return null;
}
return new PreAuthenticatedToken(
'anon.',
$apiKey,
$providerKey
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$userProvider instanceof ApiKeyUserProvider) {
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
get_class($userProvider)
)
);
}
$apiKey = $token->getCredentials();
$username = $userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
throw new BadCredentialsException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $userProvider->loadUserByAuthToken($apiKey);
if (!isset($user)) {
throw new BadCredentialsException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
// TODO: HERE, THE $user->getSaltedPasswordHash() RETURNS THE CORRECT VALUE!
return new PreAuthenticatedToken(
$user, // TODO: with "new User()" instead, it works!
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new Response(
// this contains information about *why* authentication failed
// use it, or return your own message
strtr($exception->getMessageKey(), $exception->getMessageData()),
401
);
}
}
Security/ApiKeyUserProvider.php
namespace App\Security;
use App\Entity\AuthToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* ApiKeyUserProvider constructor.
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getUsernameForApiKey($apiKey)
{
return $apiKey;
}
public function loadUserByUsername($username)
{
// TODO: Implement loadUserByUsername() method.
}
/**
* Auth token is used as username
*
* #param string $authToken
* #return null|UserInterface
*/
public function loadUserByAuthToken($authToken): ?UserInterface
{
if (!isset($authToken)) {
return null;
}
$token = $this->em
->getRepository(AuthToken::class)
->findOneBy(['id' => AuthToken::hex2dec($authToken)]);
if (!isset($token)) {
return null;
}
return $token->getUser();
}
public
function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public
function supportsClass($class)
{
return User::class === $class;
}
}
Entity/User.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface, EquatableInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* This needs to be nullable because the email includes the id of newly created users, which can only be obtained after inserting the new record.
* #ORM\Column(type="string", length=255, nullable=true, unique=true)
* #Assert\Length(max=255)
* #Assert\NotBlank()
*/
private $email;
/**
* Set null to disable login
* #ORM\Column(type="string", length=255, nullable=true)
* #Assert\Length(max=255)
* #Assert\NotBlank()
*/
private $saltedPasswordHash;
/**
* #ORM\Column(type="datetime")
*/
private $created;
// ...
/**
* #ORM\OneToMany(targetEntity="App\Entity\AuthToken", mappedBy="user", fetch="LAZY")
*/
private $authTokens;
/**
* #ORM\Column(type="string", length=5)
*/
private $role;
// ...
public function __construct()
{
$this->role = 'user';
$this->saltedPasswordHash = null;
}
public function getId()
{
return $this->id;
}
public function getSaltedPasswordHash(): ?string
{
return $this->saltedPasswordHash;
}
public function setSaltedPasswordHash(?string $saltedPasswordHash): self
{
$this->saltedPasswordHash = $saltedPasswordHash;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
public function getCreated(): ?\DateTimeInterface
{
return $this->created;
}
public function setCreated(\DateTimeInterface $created): self
{
$this->created = $created;
return $this;
}
/**
* #return ArrayCollection
*/
public function getAuthTokens()
{
return $this->authTokens;
}
/**
* #param ArrayCollection $authTokens
* #return User
*/
public function setAuthTokens(ArrayCollection $authTokens): User
{
$this->authTokens = $authTokens;
return $this;
}
/**
* #param AuthToken $authToken
* #return User
*/
public function addAuthToken(AuthToken $authToken): User
{
$this->authTokens->add($authToken);
return $this;
}
/**
* #param AuthToken $authToken
* #return User
*/
public function removeAuthToken(AuthToken $authToken): User
{
$this->authTokens->removeElement($authToken);
return $this;
}
// ...
/**
* Returns the password used to authenticate the user.
*
* This should be the encoded password. On authentication, a plain-text
* password will be salted, encoded, and then compared to this value.
*
* #return string The password
*/
public function getPassword()
{
return $this->getSaltedPasswordHash();
}
/**
* Returns the salt that was originally used to encode the password.
*
* This can return null if the password was not encoded using a salt.
*
* #return string|null The salt
*/
public function getSalt()
{
// TODO: Implement getSalt() method.
}
/**
* Returns the username used to authenticate the user.
*
* #return string The username
*/
public function getUsername()
{
return $this->getEmail();
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
$this->setSaltedPasswordHash('');
}
/**
* #return mixed
*/
public function getRole()
{
return $this->role;
}
/**
* #param mixed $role
* #return User
*/
public function setRole($role): User
{
$this->role = $role;
return $this;
}
/**
* #return string
*/
public function __toString()
{
return "User " . $this->email;
}
/**
* The equality comparison should neither be done by referential equality
* nor by comparing identities (i.e. getId() === getId()).
*
* However, you do not need to compare every attribute, but only those that
* are relevant for assessing whether re-authentication is required.
*
* Also implementation should consider that $user instance may implement
* the extended user interface `AdvancedUserInterface`.
*
* https://stackoverflow.com/a/39884792/6144818
*
* #param UserInterface $user
* #return bool
*/
public function isEqualTo(UserInterface $user)
{
return (
$this->getUsername() == $user->getUsername()
) && (
$this->getRoles() == $user->getRoles()
);
}
// ...
}
Entitiy/AuthToken.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\AuthTokenRepository")
*/
class AuthToken
{
/**
* #ORM\Id()
* #ORM\Column(type="decimal", precision=32, scale=0, options={"unsigned": true})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="authTokens")
*/
private $user;
/**
* #ORM\Column(type="datetime")
*/
private $added;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $lastSeen;
/**
* #ORM\Column(type="string", length=12, nullable=true)
*/
private $apiVersion;
/**
* #return string
*/
public function getId(): string
{
return $this->id;
}
/**
* #return string
*/
public function getHexId(): string
{
return $this->dec2hex($this->id);
}
/**
* #param mixed $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* #param mixed $id
* #throws \Exception
*/
public function generateId(): void
{
$length = 32;
$str = "";
$characters = range('0', '9');
$max = count($characters) - 1;
for ($i = 0; $i < $length; $i++) {
$rand = random_int(0, $max);
$str .= $characters[$rand];
}
$this->id = $str;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param mixed $user
*/
public function setUser($user): void
{
$this->user = $user;
}
/**
* #return mixed
*/
public function getAdded()
{
return $this->added;
}
/**
* #param mixed $added
*/
public function setAdded($added): void
{
$this->added = $added;
}
/**
* #return mixed
*/
public function getLastSeen()
{
return $this->lastSeen;
}
/**
* #param mixed $lastSeen
*/
public function setLastSeen($lastSeen): void
{
$this->lastSeen = $lastSeen;
}
public function getApiVersion(): ?string
{
return $this->apiVersion;
}
public function setApiVersion(string $apiVersion): self
{
$this->apiVersion = $apiVersion;
return $this;
}
public static function dec2hex(string $dec): string
{
$hex = '';
do {
$last = bcmod($dec, 16);
$hex = dechex($last) . $hex;
$dec = bcdiv(bcsub($dec, $last), 16);
} while ($dec > 0);
return $hex;
}
public static function hex2dec($hex)
{
$dec = '0';
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++)
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
return $dec;
}
/**
* #return string
*/
public function __toString()
{
return "AuthToken " . $this->id . " (" . $this->user . ")";
}
}
This function of your Entity/User.php is the reason of your behavior:
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
$this->setSaltedPasswordHash('');
}
When authentication comes in play on Symfony, after the authentication, the AuthenticationProviderManager would call that eraseCredentials function, so it doesn't leak sensible information, or even worse, the sensible information does not end up in your session.
Just try to comment the setter in that function and you should have what you expect.

Selecting If One Row From Entity Exists in Symfony / Twig

I am making a web app using Symfony 4.
The app has (among others) a User entity, Post entity, and a PostLike entity. A user can create many posts, and a post can have many likes. So PostLike references User and Post. Below is my PostLike entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostLikeRepository")
*/
class PostLike
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="postLikes")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="postLikes")
* #ORM\JoinColumn(nullable=true)
*/
private $post;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param mixed $user
*/
public function setUser($user): void
{
$this->user = $user;
}
/**
* #return mixed
*/
public function getPost()
{
return $this->post;
}
/**
* #param mixed $post
*/
public function setPost($post): void
{
$this->post = $post;
}
public function getCreatedAt()
{
return $this->createdAt;
}
}
When I am on the view page for an individual post, how would I reference whether a user has liked this post in TWIG? This will be the ‘many’ side of the relationship, but I just need one row (if it exists), and I’m not sure how to do this...
TIA.
In the controller you can check whether such PostLike with such user and post exist or not and pass it to the view:
$liked = false;
$postLike = $this->getDoctrine()->getManager()->getRepository('AppBundle:PostLike')->findOneBy(['user'=>$user->getId(),'post'=>$post->getId()]);
if($postLike !== null){
$liked = true;
}
If you want to simply show whether Likes exist you can add a field to the Post entity:
public function hasLikes()
{
return (0 === count($this->likes)) ? false : true;
}
and include in twig something like {% if post.hasLikes %}Liked{% endif %}.
You could do something similar with a count and a badge to show the number of likes.

new entity not detected

I created a new entity in an entity folder where I already have some other used entity. However this new entity is not detected by doctrine:mapping:info nor doctrine:schema:validate
It appears the file is simply not taken into account (if I write an error inside symfony is executed without issue).
I was thinking about a VM system issue but then I tried to create other new files (such as a new YML,a new symfony form) and it works...
I also cleared the cache:clear and doctrine:cache:all options
here is the class:
<?php
namespace NRtworks\BusinessDimensionBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use JsonSerializable;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table(name="test")
*/
class test implements JsonSerializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=50)
*/
protected $name;
/**
* #ORM\Column(type="string", length=100)
*/
protected $description;
/**
* Constructor
*/
public function __construct($id = NULL)
{
$this->id = $id;
$this->name = "Chart Of Accounts";
$this->description = "Default chart of accounts";
}
//this method returns an array with default values
public function getDefaultObject()
{
$result = Array();
$result['id'] = $this->id;
$result['name'] = $this->name;
$result['description'] = $this->code;
return $result;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
/**
* Set name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*/
public function getName()
{
return $this->name;
}
/**
* Set code
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Get description
*/
public function getDescription()
{
return $this->description;
}
public function jsonSerialize()
{
return array(
'id' => $this->id,
'name' => $this->name,
'description' => $this->description
);
}
}
?>
where could this come from ?
You need to define #ORM\Entity annotation for your class:
/**
* #ORM\Entity
* #ORM\Table(name="test")
*/
class test implements JsonSerializable
{

Adding a file upload to a Symfony2 form throws an "Entity was not found" and a session is active error

I have a Symfony2 form that I want to add a file upload dialog to.
According to the Symfony docs (http://symfony.com/doc/2.0/cookbook/doctrine/file_uploads.html), I have created a Document class:
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Document
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
/**
* #Assert\File(maxSize="6000000")
*/
public $file;
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
$this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set path
*
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
}
And a DocumentType form class:
<?php
namespace Acme\AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AppBundle\Entity\Document',
));
}
public function getName()
{
return 'document_form';
}
}
However, when I add this to my existing entity and form class:
<?php
namespace Acme\AppBundle\Entity\Profile;
use Doctrine\ORM\Mapping as ORM;
use Acme\AppBundle\Entity\jDocument;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="UserProfile")
*/
class UserProfile extends GenericProfile
{
//... Other entity params
/**
* #ORM\OneToOne(cascade={"persist", "remove"}, targetEntity="Acme\AppBundle\Entity\Document")
* #ORM\JoinColumn(name="picture_id", referencedColumnName="id", onDelete="set null")
*/
protected $picture;
/**
* Set picture
*
* #param Acme\AppBundle\Entity\Document $picture
*/
//\Acme\AppBundle\Entity\Document
public function setPicture($picture)
{
$this->picture = $picture;
}
/**
* Get picture
*
* #return Acme\AppBundle\Entity\Document
*/
public function getPicture()
{
return $this->picture;
}
//... Other entity getters and setters
}
Whenever I submit the form, I get the following error:
ErrorException: Warning: ini_set(): A session is active. You cannot change the session module's ini settings at this time in /var/www/wolseley-integrated-services/builds/dev/app/cache/prod/classes.php line 421
But the page title is "Entity was not found. (500 Internal Server Error)".
Can anybody spot which entity it can't find? Or if that's even the issue?
I've done some googling and checked that session.auto_start is set to 0 in php.ini, I've cleared all my sessions and caches... I'm stumped!
It turns out I was getting this strange session error message because of an error page definition in my nginx config.
The entity not found issue was resolved by correcting some errors in my entities. The Symfony developer bar provided me with enough information to track the issue down.

Resources