Doctrine replace associations with their ids - symfony

I'm building an API using FOSRestBundle in Symfony2 and I'm trying to remove the entity associations from an entity with no luck, to minimize the JSON data sent in the API response.
public function helperRemoveAssociations($entities) {
$em = $this->getEntityManager();
$data = array();
/** #var WBEntity $entity */
foreach ($entities as $entity) {
$entityAssociations = $entity->getAssociationNames($em); // ex: array('category', 'comments')
foreach ($entityAssociations as $associationName) {
$associationGetter = 'get' . ucfirst($associationName);
$associationSetter = 'set' . ucfirst($associationName);
/** #var WBEntity $associationObject */
$associationObject = $entity->$associationGetter();
$associationId = $associationObject->getEntityId($em); // ex: 51
$entity->$associationSetter($associationId); // I am not allowed to write an integer
}
$data[] = $entity;
}
return $data;
}
How can I accomplish this, replacing the associated entities with their respective ids.

Related

return random data form database in random order and with limit

I'm new with symfony and I'm trying to view data from one of my tables with random order and a limit of 4. I tried doing it in the repository but RAND() is not working so I'm trying in the controller.
The error is the following one:
"Warning: array_rand() expects parameter 1 to be array, object given"
And I don't understand why, when in the $response I set the data into an array.
This is my actual code:
/**
* #Route("/ws/random/superviviente", name="ws_random_survi")
*/
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$response = new JsonResponse();
$response -> setStatusCode(200);
$response -> setData(array('random perk' => $randomPerks));
$resultRandom = array_rand($response);
return $resultRandom;
}
You are trying to use array_rand on a doctrine array collection.
You could either convert it as array and back to a doctrine array :
use Doctrine\Common\Collections\ArrayCollection;
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$resultRandom = new ArrayCollection(array_rand($randomPerks->toArray()));
return new JsonResponse($resultRandom);
}
Otherwise it would work with shuffle :
$randomPerks = $entityManager->getRepository(Perks::class)->getRandomPerks();
$randomPerks = shuffle($randomPerks);
Or get random perks directly through your method in your repository.
See example from #Krzysztof Trzos:
public function getRandomProducts($amount = 7)
{
return $this->getRandomProductsNativeQuery($amount)->getResult();
}
/**
* #param int $amount
* #return ORM\NativeQuery
*/
public function getRandomProductsNativeQuery($amount = 7)
{
# set entity name
$table = $this->getClassMetadata()
->getTableName();
# create rsm object
$rsm = new ORM\Query\ResultSetMapping();
$rsm->addEntityResult($this->getEntityName(), 'p');
$rsm->addFieldResult('p', 'id', 'id');
# make query
return $this->getEntityManager()->createNativeQuery("
SELECT p.id FROM {$table} p ORDER BY RAND() LIMIT 0, {$amount}
", $rsm);
}
You could write your own query to achieve this, so create a new method inside the repository like so:
public function getRandomPerks(int $limit): array
{
$queryBuilder = $this->createQueryBuilder('p');
return $queryBuilder
->setMaxResults($limit)
->orderBy('RAND()')
->getQuery()
->getResult();
}
Then in your controller all you would to do is call the method and pass a limit:
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks(4);

Lifecycle callbacks not working

I'm currently trying out Symfony 4, but I am having some problems with events triggered by database action (prePersist, preUpdate...)
With Symfony 3, I used to use EntityListener to accomplish this, but I found them really convoluted in Symfony 4 documentation. But I also discovered the LifecycleCallbacks, that I used like this:
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Post
{
//Attributes and other functions not included for the sake of clarity, but if I use them, consider that they exist
/**
* #ORM\PrePersist
*/
public function setPostSlug()
{
$title = $this->getPostTitle();
$title = strtolower($title);
$keywords = preg_split("/[\s,']+/", $title);
$slug = implode('-', $keywords);
dump($slug);
$this->$slug = $slug;
return $this;
}
}
My post are created through a Symfony form, and before persistence, I want to break down the title I gave to my post in a standardized string that I will use in my URLs to access said post. Unfortunately, the event never trigger on persistence, despite the slug being generated correctly. I tried to do the operation both on prePersist and postPersist events, but none worked. I searched the issue, and saw that LifecycleCallbacks needed a cache clear to be taken into account, but doing so didn't help.
Here is the action responsible for the post creation, if that might help:
/**
* #Route("/admin/create/post", name="admin-create-post")
* #param Request $request
*/
public function createPost(Request $request)
{
$post = new Post();
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em = $this->getDoctrine()->getManager();
$post = $form->getData();
$em->persist($post);
$em->flush();
$this->redirectToRoute('main');
}
return $this->render('admin/new_post.html.twig', array(
'form' => $form->createView()
));
}
Would you know the source of the problem, or which other tools I could use to obtain the desired result?
Thanks in advance.
I handle complex Lifecycle with EventListener
for this .. do :
# services.yml
AppBundle\EventListener\YourListener:
tags:
- { name: doctrine.event_listener, event: prePersist }
// YourListener.php
namespace AppBundle\EventListener;
class YourListener {
/**
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args): void
{
$post = $args->getEntity();
if ($post instanceof Post) {
// Do your job
}
}
}
But I use symfony EventListenerSubscriber Like this:
/**
* This needs to be set through passed argument in case of accident duplicate
*
* #ORM\PrePersist()
*/
public function setTrackingNumber()
{
$this->trackingNumber = NumberCreator::randomStringWithNDigits(self::TRACKING_DIGIT_COUNT);
}
so I think you need do that in your slug setter like this
/**
* #ORM\PrePersist
*/
public function setSlug()
{
$title = $this->getPostTitle();
$title = strtolower($title);
$keywords = preg_split("/[\s,']+/", $title);
$slug = implode('-', $keywords);
dump($slug);
$this->$slug = $slug;
return $this;
}
I think method name is issue ... I hope this is help to you

Symfony doctrine ManyToMany unidirectional mapping

Well, back again, i'll try to simplify my question as much as i can.
First of all, i have 2 Entities
Post
PostRating
I've created unidirectional ManyToMany relation between them, because I only need ratings to be added to each Post, if I try to map Post to PostRating too, I get Circular Reference error.
Post Entity, it creates 3rd table post_has_rating, no mapping inside PostRating Entity, It workes like expected, rating collection is added to each post, but if i want to find one rating, and edit it if needed, then it comes to be bigger headache than expected.
/**
* Post have many PostRating
* #ORM\ManyToMany(targetEntity="PostRating")
* #ORM\JoinTable(name="post_has_rating",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="postrating_id", referencedColumnName="id", unique=true)}
* )
*/
protected $ratings;
PostController thumbAction, simple word "ratingAction"
/**
* Search related videos from youtube
* #Route("/post/thumb", name="post_thumb")
* #param Request $request
* #return string
*/
public function thumbAction (Request $request) {
$content = json_decode($request->getContent());
$serializer = $this->get('serializer');
$em = $this->getDoctrine()->getManager();
$postRatingRepo = $this->getDoctrine()->getRepository(PostRating::class);
$postRepo = $this->getDoctrine()->getRepository(Post::class);
$me = $this->getUser()->getId();
/** #var PostRating $rating */
$rating = $postRatingRepo->findOneBy(['userId' => $me]);
/** #var Post $post */
$post = $postRepo->find($content->id);
if ($post->getRatings()->contains($rating)) {
$post->removeRating($rating);
$em->remove($rating);
}
$rating = new PostRating();
$rating->setUserId($me);
switch ($content->action) {
//NVM about those hardcoded words, they are about to be removed
case 'up':
$rating->setRating(1);
break;
case 'down':
$rating->setRating(0);
break;
}
$post->addRating($rating);
$em->persist($rating);
$em->persist($post);
$em->flush();
return new JsonResponse( $serializer->normalize( ['success' => 'Post thumbs up created'] ) );
}
Problems: $rating = $postRatingRepo->findOneBy(['userId' => $me]); this row needs to have postId too for $post->getRatings()->contains($rating), right now im getting all the raitings, that I have ever created, but it Throws error if i add it, Unknown column
Should i create custom repository, so i can create something like "findRating" with DQL?
OR
Can i make Post and PostRating Entities mapped to each other more simple way, i don't really want many-to-many relation, because I don't see point of using it
Considering you want to keep OneToMany unidirectional here is my suggestion
create a custom repository for your Post Entity
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class PostRepository extends EntityRepository
{
public function findOneRatingByUser($post, $user)
{
$query = $this->createQueryBuilder('p')
->select('r')
->innerJoin('p.ratings', 'r')
->where('p.id = :post')
->andWhere('r.user = :user')
->setParameter('post', $post)
->setParameter('user', $user)
->getQuery()
;
return $query->getOneOrNullResult();
}
}
Then in your controller:
public function thumbAction (Request $request)
{
$content = json_decode($request->getContent());
$serializer = $this->get('serializer');
$em = $this->getDoctrine()->getManager();
$postRepo = $this->getDoctrine()->getRepository(Post::class);
$me = $this->getUser()->getId();
/** #var Post $post */
$post = $postRepo->find($content->id);
$rating = $postRepo->findOneRatingByUser($post->getId(), $me);
if (null === $rating) {
$rating = new PostRating();
$rating->setUserId($me);
}
switch ($content->action) {
//NVM about those hardcoded words, they are about to be removed
case 'up':
$rating->setRating(1);
break;
case 'down':
$rating->setRating(0);
break;
}
$post->addRating($rating);
$em->persist($rating);
$em->persist($post);
$em->flush();
return new JsonResponse( $serializer->normalize( ['success' => 'Post thumbs up created'] ) );
}
If you want your custom repository to work dont forget to declare it in your entity
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post

Symfony2 and ParamConverter(s)

Accessing my route /message/new i'm going to show a form for sending a new message to one or more customers. Form model has (among others) a collection of Customer entities:
class MyFormModel
{
/**
* #var ArrayCollection
*/
public $customers;
}
I'd like to implement automatic customers selection using customers GET parameters, like this:
message/new?customers=2,55,543
This is working now by simply splitting on , and do a query for getting customers:
public function newAction(Request $request)
{
$formModel = new MyFormModel();
// GET "customers" parameter
$customersIds = explode($request->get('customers'), ',');
// If something was found in "customers" parameter then get entities
if(!empty($customersIds)) :
$repo = $this->getDoctrine()->getRepository('AcmeHelloBundle:Customer');
$found = $repo->findAllByIdsArray($customersIds);
// Assign found Customer entities
$formModel->customers = $found;
endif;
// Go on showing the form
}
How can i do the same using Symfony 2 converters? Like:
public function newAction(Request $request, $selectedCustomers)
{
}
Answer to my self: there is not such thing to make you life easy. I've coded a quick and dirty (and possibly buggy) solution i'd like to share, waiting for a best one.
EDIT WARNING: this is not going to work with two parameter converters with the same class.
Url example
/mesages/new?customers=2543,3321,445
Annotations:
/**
* #Route("/new")
* #Method("GET|POST")
* #ParamConverter("customers",
* class="Doctrine\Common\Collections\ArrayCollection", options={
* "finder" = "getFindAllWithMobileByUserQueryBuilder",
* "entity" = "Acme\HelloBundle\Entity\Customer",
* "field" = "id",
* "delimiter" = ",",
* }
* )
*/
public function newAction(Request $request, ArrayCollection $customers = null)
{
}
Option delimiter is used to split GET parameter while id is used for adding a WHERE id IN... clause. There are both optional.
Option class is only used as a "signature" to tell that converter should support it. entity has to be a FQCN of a Doctrine entity while finder is a repository method to be invoked and should return a query builder (default one provided).
Converter
class ArrayCollectionConverter implements ParamConverterInterface
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
function apply(Request $request, ConfigurationInterface $configuration)
{
$name = $configuration->getName();
$options = $this->getOptions($configuration);
// Se request attribute to an empty collection (as default)
$request->attributes->set($name, new ArrayCollection());
// If request parameter is missing or empty then return
if(is_null($val = $request->get($name)) || strlen(trim($val)) === 0)
return;
// If splitted values is an empty array then return
if(!($items = preg_split('/\s*'.$options['delimiter'].'\s*/', $val,
0, PREG_SPLIT_NO_EMPTY))) return;
// Get the repository and logged user
$repo = $this->getEntityManager()->getRepository($options['entity']);
$user = $this->getSecurityContext->getToken()->getUser();
if(!$finder = $options['finder']) :
// Create a new default query builder with WHERE user_id clause
$builder = $repo->createQueryBuilder('e');
$builder->andWhere($builder->expr()->eq("e.user", $user->getId()));
else :
// Call finder method on repository
$builder = $repo->$finder($user);
endif;
// Edit the builder and add WHERE IN $items clause
$alias = $builder->getRootAlias() . "." . $options['field'];
$wherein = $builder->expr()->in($alias, $items);
$result = $builder->andwhere($wherein)->getQuery()->getResult();
// Set request attribute and we're done
$request->attributes->set($name, new ArrayCollection($result));
}
public function supports(ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
// Check if class is ArrayCollection from Doctrine
if('Doctrine\Common\Collections\ArrayCollection' !== $class)
return false;
$options = $this->getOptions($configuration);
$manager = $this->getEntityManager();
// Check if $options['entity'] is actually a Dcontrine one
try
{
$manager->getClassMetadata($options['entity']);
return true;
}
catch(\Doctrine\ORM\Mapping\MappingException $e)
{
return false;
}
}
protected function getOptions(ConfigurationInterface $configuration)
{
return array_replace(
array(
'entity' => null,
'finder' => null,
'field' => 'id',
'delimiter' => ','
),
$configuration->getOptions()
);
}
/**
* #return \Doctrine\ORM\EntityManager
*/
protected function getEntityManager()
{
return $this->container->get('doctrine.orm.default_entity_manager');
}
/**
* #return \Symfony\Component\Security\Core\SecurityContext
*/
protected function getSecurityContext()
{
return $this->container->get('security.context');
}
}
Service definition
arraycollection_converter:
class: Acme\HelloBundle\Request\ArrayCollectionConverter
arguments: ['#service_container']
tags:
- { name: request.param_converter}
It's late, but according to latest documentation about #ParamConverter, you can achieve it follow way:
* #ParamConverter("users", class="AcmeBlogBundle:User", options={
* "repository_method" = "findUsersByIds"
* })
you just need make sure that repository method can handle comma (,) separated values

Symfony2 Doctrine2 - how convert entity (not fetched form database) to array

I have entity from form framework.
public function editAction($id)
{
$request = $this->getRequest();
$r = $this->getProfileRepository();
$profile = $id ? $r->find($id) :
new \Alden\BonBundle\Entity\Profile();
/* #var $profile \Alden\BonBundle\Entity\Profile */
$form = $this->createForm(
new \Alden\BonBundle\Form\Type\ProfileType(),
$profile);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
{
...
I need to convert $profile into array. In class Profile all properties are defined as private so I can't iterate like foreach($profile as $key => $value) {...}
You can use Reflection to retrieve class properties. Then, use a PropertyPath to retrieve properties values.
Here is an example:
$reflectedClass = new \ReflectionClass($yourClass);
$objectProperties = $reflectedClass->getProperties();
$datas = array();
foreach ($objectProperties as $objectProperty) {
$property = $objectProperty->getName();
$path = new PropertyPath($property);
$datas[] = $path->getValue($object);
}
But, if your form / entity is simple, you can just create a dedicated method in entity to return the right array.

Resources