Symfony 3 ArrayCollection key search - symfony

If I have a variable of a type ArrayCollection how do I check if a key of a specific name exists in the collection including nesting. And if it does how do I get and change that value?

I guess you are talking about Doctrines ArrayCollection \Doctrine\Common\Collections\ArrayCollection.
It does implement phps native ArrayAccess interface, so have a look at the docs. Just check like:
use Doctrine\Common\Collections\ArrayCollection;
$myCollection = new ArrayCollection(array('testKey' => 'testVal'));
var_dump(isset($myCollection['testKey']));
It does also implement its own method from the Collection interface.
/**
* Checks whether the collection contains an element with the specified key/index.
*
* #param string|integer $key The key/index to check for.
*
* #return boolean TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
*/
public function containsKey($key);
For nested objects there is no build in method, you have to traverse the collection yourself like you would do with a normal array.

The way that I found was to use the toArray() method in the ArrayCollection object and then use the array_search function:
$newArray = $arrayCollectionObject->toArray();
$keyThatIneed = array_search($value, $newArray);

Related

Get symfony doctrine findAll to return array with specific type of keys

I'm looking for elegant way to restructure the array returned by Symfony findAll with specific keys. For instance If I have an entity Animal with fields id, name, color etc. I would like to make a repository method that returns an array with all animals and the key for each one to be the 'name' field so I can make lookups in this array. The way I'm doing it now is by iterating the array and creating a new one, but I'm wondering if there is something ready made that I can use?
No there is a thing built-in doctrine, which is called index by, I also didn't know that for long time. Check code examples :).
By using function getAllLocationsAssoc, it will return associative array indexed by location.id. So the keys of array will be the id's of objects in db. You can use it like second parameter in createQueryBuilder function.
class LocationRepository extends EntityRepository
{
/**
* #return array
*/
public function getAllLocationsAssoc(): array
{
return $this->createQueryBuilder('location', 'location.id')
->getQuery()
->getArrayResult();
}
}
Another option is to specify as third parameter in ->from qb function.
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
/**
* Class CityRepository
* #package AppBundle\Repository
*/
class CityRepository extends EntityRepository
{
/**
* #return array
*/
public function getAllCitiesAssoc(): array
{
return $this->_em->createQueryBuilder()
->select('c')
->from('AppBundle:City', 'c', 'c.name') // Third parameter is index-by
->getQuery()
->getResult();
}
}
No there is nothing like that built-in by doctrine. However, instead of iterating the array which is somewhat ugly, if the returned array is an array of entity objects, you could do something like this to get the names of the animals:
$names = array_map(function($animal) { return $animal->getName(); }, $arrayOfAnimals);
If the returned array is an array of arrays you could simple do that to get animals names (in case you use doctrine HYDRATE_ARRAY hydration):
$names = array_column($arrayOfAnimals, 'name');
Then you could simple use this to get your final array:
$finalArrayOfAnimals = array_combine($names, $arrayOfAnimals);

How do I create a custom exclusion strategy for JMS Serializer that allows me to make run-time decisions about whether to include a particular field?

As the title says, I am trying to make a run-time decision on whether or not to include fields in the serialization. In my case, this decision will be based on permissions.
I am using Symfony 2, so what I'm looking to do is add an additional annotation called #ExcludeIf which accepts a security expression.
I can handle the annotation parsing and storing of the meta data, but I am not able to see how to integrate a custom exclusion strategy with the library.
Any suggestions?
Note: exclusion strategies are an actual construct in the JMS codebase, I just haven't been able to figure out the best way to integrate an extra on top of the others
PS: I had asked about this before and was pointed to using groups. For various reasons this is a very poor solution for my needs.
You just have to create a class that implements JMS\Serializer\Exclusion\ExclusionStrategyInterface
<?php
namespace JMS\Serializer\Exclusion;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Context;
interface ExclusionStrategyInterface
{
/**
* Whether the class should be skipped.
*
* #param ClassMetadata $metadata
*
* #return boolean
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $context);
/**
* Whether the property should be skipped.
*
* #param PropertyMetadata $property
*
* #return boolean
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $context);
}
In your case, you can implement your own custom logic in the shouldSkipProperty method and always return false for shouldSkipClass.
Example of implementation can be found in the JMS/Serializer repository
We will reference the created service as acme.my_exclusion_strategy_service below.
In your controller action:
<?php
use Symfony\Component\HttpFoundation\Response;
use JMS\Serializer\SerializationContext;
// ....
$context = SerializationContext::create()
->addExclusionStrategy($this->get('acme.my_exclusion_strategy_service'));
$serial = $this->get('jms_serializer')->serialize($object, 'json', $context);
return new Response($serial, Response::HTTP_OK, array('Content-Type' => 'application/json'));
Or if you are using FOSRestBundle
<?php
use FOS\RestBundle\View;
use JMS\Serializer\SerializationContext;
// ....
$context = SerializationContext::create()
->addExclusionStrategy($this->get('acme.my_exclusion_strategy_service'))
$view = new View($object);
$view->setSerializationContext($context);
// or you can create your own view factory that handles the creation
// of the context for you
return $this->get('fos_rest.view_handler')->handle($view);
As of jms/serializer 1.4.0, the symfony expression language is integrated in its core.
The feature is documented at http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#dynamic-exclusion-strategy and this allows to use runtime exclusion strategies.
An example taken from the documentation is:
class MyObject
{
/**
* #Exclude(if="service('user_manager_service').getSomeRuntimeData(object)")
*/
private $name;
/**
* #Expose(if="service('request_stack').getCurrent().has('foo')")
*/
private $name2;
}
I this example, the services user_manager_service and request_stack are invoked at runtime, and depending on the return (true or false), the property will be exposed or not.
With the same expression language, as of 1.6.0 is possible also to use virtual properties via expression language.
Documented at http://jmsyst.com/libs/serializer/master/reference/annotations#virtualproperty allows to add on the fly data coming from external services

Getting multiple rows using ParamConverter

Hi Im trying to use ParamConverter to get multiple rows from DB but profiler show query with limi 1. Is it possible to get it like that
/**
* #Route("/localization/{code}", name="pkt-index")
* #ParamConverter("localizations", class="PriceBundle:Localization")
*/
after entering localization/0003 I should get more than 100 rows.
EDIT:
I have used repository_method option
and
/*
* #Route("/localization/{id}", name="pkt-index")
* #ParamConverter("localizations", class="PriceBundle:Localization", options={
* "repository_method": "getByLocalizationCode"
* })
*/
but funny thing is that when I change {id} in route it does not work it throws and exception
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
even if variable exists in entity class, if variable dont exist it throws
Unable to guess how to get a Doctrine instance from the request information.
EXPLANATION
when I change {id} in route it does not work it throws and exception
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
Here I think symfony treads id like primary key and as parameter to repository method it pass string when I changed this id to something else it pass array
Example
/**
* #Route("/localization/{id}", name="pkt-index")
*/
pass string to method
/**
* #Route("/localization/{code}/{name}", name="pkt-index")
*/
pass array to method
array(
'code' => 003
'name' => localization_name
)
and last
/**
* #Route("/localization/{id}/{name}", name="pkt-index")
*/
will pass string id omit the name
Hope this sounds reasonable.
forgottenbas's answer isn't completely right. #ParamConverter will first try to find one entity by id ...
... then try to match the route variables against db columns to find an entity ...
but essentially it will only convert one entity at a time.
If you would still like to use a paramconverter you would need to write a custom one.
or just use a one-liner inside your controller action:
/**
* #Route("/localization/{code}", name="pkt-index")
*/
public function yourAction($code)
{
$localizations = $this->getDoctrine()->getRepository("YourBundle:Localization")->findBy(array("code" => $code));
// ...
ParamConverter currently can only extract id from request and find one entity from db. Look at
DoctrineParamConverter code. But you can specify your own param converter with some extra logic.

how to implement this symfony form, with a entity which has a array of entities?

i have a big problem with a symfony form. i need to do the following:
I want to generate a form for a entity, but this entity has a array of other entities which should be also added (if possible).
here is the data structure:
I hava a Entity called Foo (keeps a array of FooItems) and the a entity FooItems.
class Foo
{
{...}
/**
* #var string
* #ORM\Column(type="string")
*/
private $fooItems;
{...}
now i need to build a form for this, the user can add one or more FooItems to the Foo. How to realise this?
From Symfony's cookbook:
How to Embed a Collection of Forms
http://symfony.com/doc/current/cookbook/form/form_collections.html
If you want to store multiple form fields into a single string in the database instead of an one-to-many relation, you can also implement an DataTransformerInterface to convert the fields to a string and back.
Taka a look a DateTimeToStringTransformer which does it for the DateType form element

Symfony/Doctrine 2 - Use config parameter in Entity

I have a tree of Employee objects (they are in a tree-like hierarchy, with everyone having one leader, and all leaders having more employees). All the Employees have a integer parameter called units.
/**
* #ORM\Entity
* #ORM\Table(name="employees")
*/
class Employee
{
/**
* #ORM\Id
* #ORM\Column(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Employee", mappedBy="leader")
*/
protected $employees;
/**
* #ORM\ManyToOne(targetEntity("Employee", inversedBy="employees")
*/
protected $leader;
}
I need to get all the employees, who have at most N units, where N is defined in config.yml. At first, I was trying to push $configContainer into $GLOBALS, and use it in ArrayCollection::filter()'s Closure. Now I found a method, so I can use variables in the Closure:
public function getBestEmployees(&$configContainer)
{
return $this->getAllEmployees()->filter(
function bestEmployees($employee) use ($configContainer)
{
return ($employee->getUnits() >= $configContainer->getParameter('best_unit_count'));
}
);
}
Now I wonder if there is any other way to access the configuration parameters from an Entity, or do I really have to pass the whole configContainer as a reference? Or am I doing it totally wrong?
You shouldn't be accessing the service container at all inside entities. The value itself should be passed instead
public function getBestEmployees($bestUnitCount)
{
return $this->getAllEmployees()->filter(function ($employee) use ($bestUnitCount) {
return $employee->getUnits()->count() >= $bestUnitCount;
});
}
Of course, we haven't actually solved the problem yet: the parameter still needs to be fetched from the container somewhere. If this method gets invoked mostly in controller actions, I wouldn't bother doing any extra work to make things cleaner and would pass the container parameter straight in the controller action.
However, should there be a need to get the best employees in a Twig template, for example, it would be nice if it wouldn't be necessary to pass the parameter. One possibility would be using a setter method and passing the parameter down beforehand to each and every entity that gets retrieved from the database. You could do this either in repositories or entitiy managers. The most advanced solution would be to listen to the postLoad event and pass the parameter in an event listener.

Resources