How can I get an object is every response in Symfony? - php

Context
I have an entity User and an entity Cart in my project. There is a ManyToOne relation between both entities :
/* /src/Entity/User.php */
/**
* #ORM\OneToMany(targetEntity="App\Entity\Cart", mappedBy="user", orphanRemoval=true)
*/
private $carts;
/* /src/Entity/Cart.php */
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="carts")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
I also have a method in my CartRepository that returns the current cart of the user.
/*src/Repository/CartRepository.php */
/**
* #param User $user
* #return mixed
* #throws NonUniqueResultException If the user somehow has several non-validated
* carts bidden to their account.
*/
public function findCurrentUserCart(User $user)
{
/* ... */
}
My Problem
What I would like to do is to be able to access easily the current count of cart items in the current user cart, so I can display it in my navbar. To do so, I need to be able to access the user's current cart easily.
I thought about getting it through the controller, but it seems quite annoying because, as the information is requested on all the pages of the site (because it is in the navbar) then I would need to retrieve the count from every single action of the controllers.
I then thought about having a simple function in my User entity like getCartTotalCount() but this would imply using the CartRepository directly from my User entity which, IMHO, feels wrong (I mean, my entity is a plain object and I don't feel like getting a repository is a good way to go, is it ?)
So my question is : Is there a way to globally get the current cart object, so I can have it in all my views, without needing to passing it in any single render() method in my controllers ? Or maybe is there another way to go ?

Presuming you’re using twig to render your templates, you have to create an extension.
Declaring a twig extension as a service you can inject the entity manager or the repository directly in the service constructor.
Here’s the documentation on symfony docs on how to create a twig extension: https://symfony.com/doc/current/templating/twig_extension.html
In this case you need to create a custom twig function.

Even if the Twig extension looks like a feasible solution, I'd do it differently: using an embedded controller, you have more control. Place the cart template snippet in a distinct file, create a controller that renders this tiny piece, and call it using
{{ render(controller(
'App\\Controller\\CartController::cartNavbar'
)) }}
If the template to display this information grows (for example, as you want to use an icon for an empty cart, and another one if something is placed in the cart), you will see that this helps to seperate concerns better than using an extension

Related

Generic/wildcard Entity class for multiple indentical structured set of tables

I'm rewriting some parts of a big SaaS ecommerce project, that has been built and extended for many years. The main parts are splitted, so I'm having a backend, a shop frontend and a regular landing page. Because of this I can rewrite the different parts but can't alter the database. It's a lot of mixed spaghetti procedural code, no separated function files etc. and a more or less bad DB-design.
As a start, I'll rewrite the shop frontend. The base is built by slim framework v3 and Doctrine ORM, in an OOP-manner.
For structuring the Entities I'm having a problem with the existing database schema. Every existing shop has its own set of tables (min 3x, max 5x) holding order info ($client_order, $client2_order, ...), cart info ($client_info, $client2_info, ...), and additional needed extras. I wouldn't want to have the amount of $total_shop*3 Entity files, with 100% same code in it.
So I thought about having something like abstracted/generic Shop-Entities that contain the properties of each table. This way I might get the $client beforehand, load the Entity and change the corresponding table.
Is this possible, and how would I build and use these very Entities?
Just for picturing it, what I mean, some pseudo-code:
// App\Entity\GenericShopOrder
/**
* #ORM\Entity(repositoryClass="App\Repository\GenericShopOrderRepository")
* #ORM\Table(name="###PROBLEMATIC###") <-----
*/
class GenericShopOrder
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id = 0;
/**
* #ORM\Column(type="string", length=200)
*/
protected $myString = '';
// ---------
// App\Controller\PseudoController
public function myMethod() {
$db_name = "client1"; // gotten beforehand
$Orders = $this->container->get(GenericShopOrderRepository::class)->#####setTable($db_name)#####->findAll();
}
I found a solution:
Remove the table annotation of the Entity
Use following snippet in the controller:
$meta = $em->getClassMetadata('App\Entity\GenericShopOrder');
$meta->setTableName($var."_info");
$repo = $em->getRepository('App\Entity\GenericShopOrder');
By using getClassMetadata the Entity gets loaded, and the additional setTableName assigns the corresponding table name. The Repo/Entity works normal after that - with a table assigned on code execution.

How to implement instance counter in symfony?

I'm trying to implement an instance counter on a php/symfony3 project, but I don't find how.
I have an "Advert" entity, and I want to show how many instances exist on a Twig view.
So, in the entity class, I created a static attribute private static $nbAdverts = 0; with a getter/setter, and two static methods:
/**
* #ORM\PrePersist
*/
public static function increaseAdverts()
{
self::$nbAdverts++;
}
and
/**
* #ORM\PreRemove
*/
public static function decreaseAdverts()
{
self::$nbAdverts--;
}
I'm calling these methods using the Doctrine events: PrePersist and PreRemove.
These adverts are created in the controller using the Advert repository before being persisted and flushed. Also called using this repository before being removed.
The counter remains stuck at 0 when I add or remove an advert.
I think I'm doing something wrong: is it possible to do this without rewriting the repository add method? If it is, any idea about how?
The behavior you are getting is absolutely normal. You are running a web application, any variable not stored in the session, is set to it default value if exists, at every request. So to achieve what you want to do you can save your static variable in session and update it in the increaseAdverts() and decreaseAdverts() Methods.

Symfony : is it bad practice to add custom functions in an Entity?

I understand that an Entity is a basic class that holds data.
But is it bad practice if the Entity has custom functions that manipulates the data ?
I personally think that this kind of functions should go into a different Service. But in this case, the getNextPayroll is quite useful :
<?php
class Payroll
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="last_payroll", type="datetime", nullable = true)
*/
private $lastPayroll;
/**
* Set lastPayroll
*
* #param \DateTime $lastPayroll
* #return CompanyBase
*/
public function setLastPayroll($lastPayroll)
{
$this->lastPayroll = $lastPayroll;
return $this;
}
/**
* Get lastPayroll
*
* #return \DateTime
*/
public function getLastPayroll()
{
return $this->lastPayroll;
}
public function getNextPayroll()
{
$payrollNext = clone $this->getLastPayroll();
$payrollNext->add(new \DateInterval("P1M"));
return $payrollNext;
}
}
The date of the next payroll is not stored in database. Only the date of the last payroll. Should I get the next payroll date in a different service or is it OK to use use a custom function non-generated by doctrine in an entity ?
It's not a bad practice, if your code still satisfies SOLID principles (mainly, Single Responsibility principe in that case)
So, if the method isn't related to entity logic (for example, sending emails or persisting something to database right from your entity) -- it's wrong. Otherwise, it's absolutely OK.
The major attribute of the logic related to entity -- it should be in the same layer with another stuff in the entity.
Actually, Doctrine entities is not just Data Transfer Objects (without behavior). Doctrine's developers insist using the entities as Rich Models (look the video by Marco Pivetta, one of Doctrine's developers and see his nice presentation)
As far as I know it shouldn't be bad practice as long as your entity doesn't get into the Database layer, which the Repository should take care of.
So things where you more or less just get EntityData back (like your method which returns just modified data that belongs to the Entity) should be fine in the Entity it self. This way you also easily use the methods inside Twig, which automaticaly searchs for the method name (ex. {{ User.name }} will search for User->getName() and if it doesn't find it, it will search for User->name())
If you reusing this part and want to be dynamic, it also could be a good idea to create a custom Twig extension.
I think you will only need a service if you going to do very complicated things where you actually also need to inject the EntityManager and also retrive data from other entities that maybe are not part of the usual relations.

Is it considered a bad practice to add fields to Symfony entity in controller?

Is it considered a bad practice to add fields to Symfony entity in controller? For example lets say that I have a simple entity:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And then in UserController.php I want to do the following:
foreach($users as $user){
$user->postsCount = someMethodThatWillCountPosts();
}
So later that postsCount can be displayed in Twig. Is it a bad practice?
Edit:
It's important to count posts on side of mysql database, there will be more than 50.000 elements to count for each user.
Edit2:
Please take a note that this questions is not about some particular problem but rather about good and bad practices in object oriented programming in Symfony.
As #Rooneyl explained that if you have relation between user and post then you can get count easily in your controller, refer this for the same. But if you are looking to constructing and using more complex queries from inside a controller. In order to isolate, reuse and test these queries, it's a good practice to create a custom repository class for your entity.Methods containing your query logic can then be stored in this class.
To do this, add the repository class name to your entity's mapping definition:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
Doctrine can generate empty repository classes for all the entities in your application via the same command used earlier to generate the missing getter and setter methods:
$ php bin/console doctrine:generate:entities AppBundle
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
More Deatils
Updated Answer
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: SOURCE
"rather about good and bad practices in object oriented programming"
If that's the case then you really shouldn't have any business logic in controller, you should move this to services.
So if you need to do something with entities before passing them to twig template you might want to do that in specific service or have a custom repository class that does that (maybe using some other service class) before returning the results.
i.e. then your controller's action could look more like that:
public function someAction()
{
//using custom repository
$users = $this->usersRepo->getWithPostCount()
//or using some other service
//$users = $this->usersFormatter->getWithPostCount(x)
return $this->render('SomeBundle:Default:index.html.twig', [
users => $users
]);
}
It's really up to you how you're going to do it, the main point to take here is that best practices rather discourage from having any biz logic in controller. Just imagine you'll need to do the same thing in another controller, or yet some other service. If you don't encapsulate it in it's own service then you'll need to write it every single time.
btw. have a read there:
http://symfony.com/doc/current/best_practices/index.html

Advice for implementing field whitelists with Symfony/FosRestBundle/JMS Serializer

I'm currently learning how to implement a relatively simple API using Symfony 3 (with FOSRestBundle) and JMS Serializer. I've been trying recently to implement the ability to specify, as a consuming client, which fields should be returned within a response (both fields within the requested entity and relationships). For example;
/posts with no include query string would return all Post entity properties (e.g. title, body, posted_at etc) but no relationships.
/posts?fields[]=id&fields[]=title would return only the id and title for posts (but again, no relationships)
/posts?include[]=comment would include the above but with the Comment relationship (and all of its properties)
/posts?include[]=comment&include[]=comment.author would return as above, but also include the author within each comment
Is this a sane thing to try and implement? I've been doing quite a lot of research on this recently and I can't see I can 1) restrict the retrieval of individual fields and 2) only return related entities if they have been explicitly asked for.
I have had some initial plays with this concept, however even when ensuring that my repository only returns the Post entity (i.e. no comments), JMS Serializer seems to trigger the lazy loading of all related entities and I can't seem to stop this. I have seen a few links such as this example however the fixes don't seem to work (for example in that link, the commented out $object->__load() call is never reached anyway in the original code.
I have implemented a relationship-based example of this using JMSSerializer's Group functionality but it feels weird having to do this, when I would ideally be able to build up a Doctrine Querybuilder instance, dynamically adding andWhere() calls and have the serializer just return that exact data without loading in relationships.
I apologise for rambling with this but I've been stuck with this for some time, and I'd appreciate any input! Thank you.
You should be able to achieve what you want with the Groups exclusion strategy.
For example, your Post entity could look like this:
use JMS\Serializer\Annotation as JMS;
/**
* #JMS\ExclusionPolicy("all")
*/
class Post
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*
* #JMS\Expose
* #JMS\Groups({"all", "withFooAssociation", "withoutAssociations"})
*/
private $id;
/**
* #ORM\Column(type="string")
*
* #JMS\Expose
* #JMS\Groups({"all", "withFooAssociation", "withoutAssociations"})
*/
private $title;
/**
* #JMS\Expose
* #JMS\Groups({"all", "withFooAssociation"})
*
* #ORM\OneToMany(targetEntity="Foo", mappedBy="post")
*/
private $foos;
}
Like this, if your controller action returns a View using serializerGroups={"all"}, the Response will contains all fields of your entity.
If it uses serializerGroups={"withFooAssociation"}, the response will contains the foos[] association entries and their exposed fields.
And, if it uses serializerGroups={"withoutAssociation"}, the foos association will be excluded by the serializer, and so it will not be rendered.
To exclude properties from the target entity of the association (Fooentity), use the same Groups on the target entity properties in order to get a chained serialisation strategy.
When your serialization structure is good, you can dynamically set the serializerGroups in your controller, in order to use different groups depending on the include and fields params (i.e. /posts?fields[]=id&fields[]=title). Example:
// PostController::getAction
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerBuilder;
$serializer = SerializerBuilder::create()->build();
$context = SerializationContext::create();
$groups = [];
// Assuming $request contains the "fields" param
$fields = $request->query->get('fields');
// Do this kind of check for all fields in $fields
if (in_array('foos', $fields)) {
$groups[] = 'withFooAssociation';
}
// Tell the serializer to use the groups previously defined
$context->setGroups($groups);
// Serialize the data
$data = $serializer->serialize($posts, 'json', $context);
// Create the view
$view = View::create()->setData($data);
return $this->handleView($view);
I hope that I correctly understood your question and that this will be sufficient for help you.

Categories