Get a bundles object/instance - symfony

How would I go about obtaining the instance of a specific bundle?
In trying to adhere to this best practice:
http://symfony.com/doc/current/best_practices/configuration.html#constants-vs-configuration-options
I would like to stick some configuration details in a global bundle as they are likely to rarely change but are not specific to any given entity or bundle. Multiple bundles from multiple entities can use this to pre-populate a "Unit of Measure" drop down.
So I have done something like:
class ZincBundle extends Bundle {
private $units = [
'PCS' => 'PCS',
'LOT' => 'LOT',
'LBS' => 'LBS',
'SET' => 'SET',
'EACH' => 'EACH'
];
public function getUnitsOfMeasure ()
{
return $this->units;
}
}
Now I am trying to access this to populate some forms - this is what I have:
$container = $this->getConfigurationPool()->getContainer();
//$bundle = $this->container->get('ZincBundle');
What am I missing???

Related

EntityType and many to many with extra field relation presented as dropdown (select)

I created a form like that
$builder->add('employees', EntityType::class, [
'class' => ActivityEmployee::class,
'choice_label' => function (ActivityEmployee $employee) {
return sprintf('%s %s', $employee->getEmployee()->getName(), $employee->getEmployee()->getLastName());
},
'multiple' => true,
])
As a result it presents already existing data fine. It shows me all employees with relation to edited activity.
However as choices there should be all employess to choose (employee entity) and as selected data only employess in activityEmployee relation like right now.
I tried to add a query_builder option to provide lists of all employess, but I can only use EntityRepository which means ActivityEmployeesRepository not EmployeesRepository per se.
A can't figure out how to implement it. Basically such relation can be done by CollectionType of custom activityEmployeeType but I'd like to use multi-select for selecting employees.
I can use another approach to not mapping my employees field to entity like that
$currentEmployees = [];
foreach ($activity->getEmployees() as $activityEmployee) {
$currentEmployees[] = $activityEmployee->getEmployee();
}
$builder->add('employees', EntityType::class, [
'class' => Employee::class,
'choice_label' => function (Employee $employee) {
return sprintf('%s %s', $employee->getName(), $employee->getLastName());
},
'mapped' => false,
'multiple' => true,
'data' => $currentEmployees,
]);
It works fine, but I need to deal with updating relation by myself. Which is ok, however I wonder how to achieve such thing in first approach.
Implementation details matter. As far as I can understand you have the following entities:
Activity (entity)
- employees (OneToMany -> ActivityEmployee)
ActivityEmployee (entity)
- activity (ManyToOne -> Activity)
- employee (ManyToOne -> Employee)
Employee (entity)
- activities (OneToMany -> ActivityEmployee) - this one might be missing, actually.
Now you apparently don't hide any implementation details. Meaning, your Activity::getEmployees() returns []ActivityEmployee.
I would have done it like this:
class Activity {
/** #ORM\OneToMany(targetEntity=ActivityEmployee::class) */
private $activityEmployees;
/** #return Employee[] */
public function getEmployees() :Collection {
return $this->activityEmployees->map(function(ActivityEmployee $ae) {
return $ae->getEmployee();
});
}
public function addEmployee(Employee $employee) {
// check, if the employee is already registered, add only then!
if(!$this->getEmployees()->contains($employee)) {
$this->activityEmployees->add(new ActivityEmployee($this, $employee));
}
}
public function removeEmployee(Employee $employee) {
foreach($this->activityEmployees as $activityEmployee) {
if($activityEmployee->getEmployee() === $employee) {
$this->activityEmployees->removeElement($activityEmployee);
}
}
}
}
This way, you hide away how Activity handles the employees and to the outside world (and specifically the PropertyAccessor, that the form component uses) it appears as if Activity has a property employees which are actually Employee[].
If you implement it like this, your first form should actually just work (obviously exchanging ActivityEmployee for Employee) - under the assumption that I didn't make some major mistake. Of course I would also add methods like getActivityEmployees when I would actually specificially need the relation objects.
This whole thing certainly is less beautiful if your many-to-many can contain duplicates.
IF your ActivityEmployee actually has NO other properties besides activity and employee, you could obviously replace the whole thing with a #ORM\ManyToMany and just work with Employee[] instead of the ActivityEmployee[]. However, I assume you have some additional columns like created or something.

Doctrine weird behavior, changes entity that I never persisted

I have this situation:
Symfony 4.4.8, in the controller, for some users, I change some properties of an entity before displaying it:
public function viewAction(string $id)
{
$em = $this->getDoctrine()->getManager();
/** #var $offer Offer */
$offer = $em->getRepository(Offer::class)->find($id);
// For this user the payout is different, set the new payout
// (For displaying purposes only, not intended to be stored in the db)
$offer->setPayout($newPayout);
return $this->render('offers/view.html.twig', ['offer' => $offer]);
}
Then, I have a onKernelTerminate listener that updates the user language if they changed it:
public function onKernelTerminate(TerminateEvent $event)
{
$request = $event->getRequest();
if ($request->isXmlHttpRequest()) {
// Don't do this for ajax requests
return;
}
if (is_object($this->user)) {
// Check if language has changed. If so, persist the change for the next login
if ($this->user->getLang() && ($this->user->getLang() != $request->getLocale())) {
$this->user->setLang($request->getLocale());
$this->em->persist($this->user);
$this->em->flush();
}
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::TERMINATE => [['onKernelTerminate', 15]],
];
}
Now, there is something very weird happening here, if the user changes language, the offer is flushed to the db with the new payout, even if I never persisted it!
Any idea how to fix or debug this?
PS: this is happening even if I remove $this->em->persist($this->user);, I was thinking maybe it's because of some relationship between the user and the offer... but it's not the case.
I'm sure the offer is persisted because I've added a dd('beforeUpdate'); in the Offer::beforeUpdate() method and it gets printed at the bottom of the page.
alright, so by design, when you call flush on the entity manager, doctrine will commit all the changes done to managed entities to the database.
Changing values "just for display" on an entity that represents a record in database ("managed entity") is really really bad design in that case. It begs the question what the value on your entity actually means, too.
Depending on your use case, I see a few options:
create a display object/array/"dto" just for your rendering:
$display = [
'payout' => $offer->getPayout(),
// ...
];
$display['payout'] = $newPayout;
return $this->render('offers/view.html.twig', ['offer' => $display]);
or create a new non-persisted entity
use override-style rendering logic
return $this->render('offers/view.html.twig', [
'offer' => $offer,
'override' => ['payout' => $newPayout],
]);
in your template, select the override when it exists
{{ override.payout ?? offer.payout }}
add a virtual field (meaning it's not stored in a column!) to your entity, maybe call it "displayPayout" and use the content of that if it exists

Best practice - How to respond with different entity views to improve performance in a RESTful API (Symfony 4) [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
When listing an using a REST index endpoint I would like to have different outputs for performance reasons.
ex: on the same GET path for CRUD indexing
- List of Entities with all associations
- List of Entities with some associations
- List of Entities with just some basic fields
This would be used for listing entities for a detailed view with statistics or just listing them for a drop down selector.
Possible Solution 1: I can do this in the controller by returning an object where I customize all the fields based on a query parameter - but then my controllers become very fat because listing all the required fields can be 20-30 fields per object. (basically giving up on the benefit of a serializer doing this automatically based on the type of the entity)
return [
[
'id' => $event->getId(),
'name' => $event->getName(),
...
'order_count' => $orderRepository->getOrderCountForEvent($event->getId())
],
...
]
Possible Solution 2: I could define transform functions in the repository for the different views and filters that I want to return.
public function transform(Event $event, $type = 'shortlist')
{
$view = null;
switch($type) {
case 'shortlist':
$view = [
'id' => $event->getId(),
'name' => $event->getName(),
'participants' => $orderRepository->getParticipantCountForEvent($event->getId()),
...
];
break;
default:
... // all fields
break
}
return $view;
}
Are there any better / cleaner solutions? Most tutorials are only dealing with simple entities with simple relations and their CRUD implementations are very basic where they simply fetch every field and pass it to a serializer.
Maybe you are looking for the Symfony serializer, which has the feature of choosing fields you render.
Example from the doc:
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class User
{
public $familyName;
public $givenName;
public $company;
}
class Company
{
public $name;
public $address;
}
$company = new Company();
$company->name = 'Les-Tilleuls.coop';
$company->address = 'Lille, France';
$user = new User();
$user->familyName = 'Dunglas';
$user->givenName = 'Kévin';
$user->company = $company;
$serializer = new Serializer([new ObjectNormalizer()]);
$data = $serializer->normalize($user, null, [AbstractNormalizer::ATTRIBUTES => ['familyName', 'company' => ['name']]]);
// $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']];
See documentation here: https://symfony.com/doc/current/components/serializer.html#selecting-specific-attributes

Add method in Entity not reached

I need to bugfix a Symfony3 project with Sonata Admin & Doctrine.
I got a field list, with all textures saved in base
The problem is in the "addTexture" method, the previous developer did :
public function addTexture(\AppBundle\Entity\Texture $texture)
{
if (count($this->textures[]) < 26)
$this->textures[] = $texture;
return $this;
}
And we can add more than 25 textures : that's my bug.
So I added a dump($this->textures); exit; and nothing happen, the app never reach this. While in other entities, the "add" method is reached.
EDIT :
Seems like it's my Doctrine2 ArrayCollection that never fire all my addXXX methods. But it saving my collections on database, and i don't know why.
EDIT 2 :
I found where the list is declared :
->add('colors','sonata_type_model', [
'multiple' => true,
'expanded' => false,
'property' => 'Name'
])
Is there a way to add a limit or maximum item ? Then I won't need to update the Entity if it's UI restricted.
Try to use ArrayCollection methods (let's say that we work on Color Entity):
public function addTexture(\AppBundle\Entity\Texture $texture)
{
if ($this->textures->count() < 26) {
$this->textures->add($texture);
$texture->setColor($this); //Add this if you have bidirectional mapping
}
return $this;
}

Symfony2: Using Doctrine outside controller

I'm a bit of noob when it comes to OOP PHP, so please forgive me if I make this sound more complicated then it is.
Basically I am trying to clean up my controller as it's starting to get too cluttered.
I have my entities set up and I have also created a repository to add methods for some db queries to a sqlite database.
But now I also have to manipulate this data before outputting it, I've created a separate connector class that fetches additional info (from an XML web source) for each item being queried and then this gets added to the doctrine query data before being outputted.
I could manipulate this data in the repository but the data I am adding obviously doesn't originate from my entity. So I have therefore created a separate model class to add this data.
Please tell me if I'm on the right track.
In my entity repository I will have a custom method like this:
public function queryTop10All()
{
$query = $this->getEntityManager($this->em)
->createQueryBuilder('u')
->select('u.ratingkey, u.origTitle, u.origTitleEp, u.episode, u.season, u.year, u.xml, count(u.title) as playCount')
->from($this->class, 'u')
->groupBy('u.title')
->orderBy('playCount', 'desc')
->addOrderBy('u.ratingkey', 'desc')
->setMaxResults(10)
->getQuery();
return $query->getResult();
}
Now I created a new class in \Model\ChartsDataModel.php and I am injecting doctrine into it using a service and calling the custom method, getting the results and then adding the additional data from the web connector to it, like so:
namespace PWW\DataFactoryBundle\Model;
use Doctrine\ORM\EntityManager;
use PWW\DataFactoryBundle\Connector\XMLExtractor;
use PWW\DataFactoryBundle\Connector\WebConnector;
use PWW\ContentBundle\Entity\Settings;
class ChartsDataModel {
private $settings;
private $repository;
private $em;
public function __construct(EntityManager $em)
{
$this->settings = new Settings();
$this->repository = $this->settings->getGroupingCharts() ? 'PWWDataFactoryBundle:Grouped' : 'PWWDataFactoryBundle:Processed';
$this->em = $em;
}
public function getChartsTop10All()
{
$xmlExtractor = new XMLExtractor();
$webConnector = new WebConnector();
$results = $this->em->getRepository($this->repository)->queryTop10All();
$xml = $xmlExtractor->unXmlArray($results);
$outputArray = array();
foreach($xml as $item) {
$outputArray[] = array(
"ratingKey" => $item['ratingkey'],
"origTitle" => $item['origTitle'],
"origTitleEp" => $item['origTitleEp'],
"playCount" => $item['playCount'],
"episode" => $item['episode'],
"season" => $item['season'],
"year" => $item['year'],
"type" => $item['media']['type'],
"parent" => $webConnector->getMetaData($webConnector->getMetaDataParentKey($item['ratingkey'])),
"metadata" => $webConnector->getMetaData($item['ratingkey'])
);
}
return $outputArray;
}
}
The xmlExtractor class is used to pull out certain xml fields stored in a database field as a raw xml dump.
My config.yml:
services:
pww.datafactorybundle.model.charts_data_model:
class: PWW\DataFactoryBundle\Model\ChartsDataModel
arguments: [ #doctrine.orm.entity_manager ]
Then in my controller, I just instantiate a new ChartsDataModel and call the method like so:
namespace PWW\ContentBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
...
use PWW\DataFactoryBundle\Model\ChartsDataModel;
public function chartsAction()
{
$charts = new ChartsDataModel($this->getDoctrine()->getManager());
$top10Array = $charts->getChartsTop10All();
return $this->render('PWWContentBundle:Default:charts.html.twig', array('page' => 'charts', 'top10' => $top10Array));
}
I just want to know if I am doing this correctly and is there a better way of doing this (or right way)?
I'm also very new to Symfony and still getting my head around it. I just don't want to get into bad habits so I'm trying to do things right from the start.
I hope I explained this well enough :)
TIA
Just detected two things that are in the top of my head.
1 If you define the service like:
services:
pww.datafactorybundle.model.charts_data_model:
class: PWW\DataFactoryBundle\Model\ChartsDataModel
arguments: [ #doctrine.orm.entity_manager ]
Then you can inject it in the controller like described here, so you keep the service arguments out of the Controller:
public function chartsAction()
{
$myservice = $this->get('pww.datafactorybundle.model.charts_data_model');
$top10Array = $myservice->getChartsTop10All();
}
Secondly, I would not put this standard queries in the Model, I think is better to keep the models clean with their setters, getters and put this custom queries elsewhere like in a service that will handle all related Chart queries and you can instance from anywhere else.

Resources