I'm trying to get manyToMany relationship with Doctrine and symfony.
I'm new at Doctrine and Symfony. I came from Zend framework.
I've created the 3 tables:
Post, PostCategory and junction table PostToCategory as you can see below.
My goal is to do inner join and to get for every post its categories.
This is what I've done so far:
//CMS/GBundle/Entity/PostContent
class PostContent
{
/**
* #ORM\ManyToMany(targetEntity="CMS\GBundle\Entity\PostCategory", inversedBy="posts")
* #ORM\JoinTable(name="post_to_category")
*/
protected $categories;
public function __construct()
{
$this->categories = new ArrayCollection();
}
//CMS/GBundle/Entity/PostCategory
class PostCategory
{
/**
* #ORM\ManyToMany(targetEntity="CMS\GBundle\Entity\PostContent", mappedBy="categories")
*/
protected $posts;
public function __construct()
{
$this->posts = new ArrayCollection();
}
I would like now to create function that returns me joined data Post->PostCategories.
Where should I create function ? in PostToCategory Repository ? or somewhere else ? How should my query look like ?
I've tried a lot of options and I passed all the possible questions on Stack but I could not get it done..
Thanks in advance!
Update:
This is what i get when i do findAll method on PostContent repository.
The preferred way to make a relationship is to create your entities like you did, then run a console command to extend your entity with some getters and setters:
$ bin/console doctrine:generate:entities AppBundle
and then let doctrine create your tables:
$ bin/console doctrine:schema:update --force
Now that is everything ready you have to fill some data in your database tables.
$category = new Category();
$categroy->setName('PHP Programming');
$em->persist($category);
$post = new Post();
$post->setTitle('My first Blogpost');
$post->addCategory($category);
$em->persist($post);
$em->flush();
After that can get it out. I just give you an example
public function indexAction($id)
{
$em = $this->getDoctrine()->getManager();
$category= $em->getRepository('AppBundle:PostCategory')->find($id);
// test
foreach($category->getPosts() as $post)
{
echo $post->getTitle() . '<br>';
}
}
To load all the posts immediately instead of lazy loading i suggest to write your own query in the CategoryRepository class and change the find() method-name for your own method-name.
You can create repository class for your entity, for example:
CMS/GBundle/Repository/PostContentRepository.php
<?php
namespace CMS\GBundle\Repository;
use Doctrine\ORM\EntityRepository;
class PostContentRepository extends EntityRepository
{
public function getPostsWithCategories()
{
$qb = $this->createQueryBuilder('post_content')
->select(['post_content', 'post_categories'])
->join('post_content.categories', 'post_categories')
;
return $qb->getQuery()->getResult();
}
}
CMS/GBundle/Entity/PostContent.php
<?php
namespace CMS\GBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity(repositoryClass="CMS\GBundle\Repository\PostContentRepository")
*/
class PostContent
{
/* Your entity properties and methods */
}
And then you can use it in anywhere. For example, in the controller:
<?php
namespace CMS\GBundle\Controller;
use ;
class SomeController extends Controller
{
public function someAction()
{
$posts = $this->getDoctrine()
->getManager()
->getRepository('CMSGBundle:PostContent')
->getPostsWithCategories()
;
/* Your logic */
}
}
As Frankbeen advised me i created my custom query:
public function getPostCategories() {
$data = $this->createQueryBuilder('c')
->select('c.id, pc.name')
->innerJoin('CMSGBundle:PostToCategory', 'ptc', 'WITH', 'ptc.postId = c.id')
->innerJoin('CMSGBundle:PostCategory', 'pc', 'WITH', 'ptc.categoryId = pc.id')
->getQuery()
->getArrayResult();
return $data;
}
In Method above I'm fetching just post.id that is related to post_to_category_post_id category name
In controller I generate two queries one to fetch all Posts second to fetch data using getPostCategories method.
Controller:
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('CMSGBundle:PostContent')->findAll();
$postCategories = $em->getRepository('CMSGBundle:PostContent')->getPostCategories();
View:
{% for post in posts %}
{% for category in categories %}
{% if category.id == post.id %}
{{ category.name }}
{% endif %}
{% endfor %}
{% endfor %}
I know that this is not the best solution and please if anyone can suggest me with minifing same code or how to write it better I will be grateful!
Related
I have a query in DQL that in response generates a multidimensional array in this way:
How do I show this on screen with twig?
This is my solution.
Thanks to the help of DarkBee
I saw this solution https://stackoverflow.com/a/34402216/2400373 which adapts to my problem.
It presents other problems here I explain
My DQL is this:
$dql="SELECT c,o
FROM BackendBundle:Orders o
JOIN o.users u
JOIN BackendBundle:Customer c
WITH u.email = c.billEmail
where o.orderid='$var'";
After it's necessary to add a twig extension:
//src/AppBundle/Twig/AppExtension.php
<?php
// src/AppBundle/Twig/AppExtension.php
namespace AppBundle\Twig;
class AppExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('cast_to_array', array($this, 'objectFilter')),
);
}
public function objectFilter($stdClassObject) {
// Just typecast it to an array
$response = (array)$stdClassObject;
return $response;
}
}
After in twig:
{% for key, value in ordenes|cast_to_array %}
<td id="col" class="hidden-xs">{{ value }}</td>
{% endfor %}
Other problems in my Entities I need add __toString ... for example:
public function __toString()
{
return (string)$this->getBillEmail();
}
With this already works. thank you help
Lets say, I have a class Movie with a Orm\OneToMany relation ship to the class Actors.
I have already a working example of an getter for $movie->getActors(); which will return all actors of that movie.
But how to dynamically modify the query for that? For example, I show a list of all actors of the movie, and allow the user to sort by name, age, gender, whatever.
===== EDIT ======
After learning, that such things belongs to the repository class (thanks to Yoshi, scoolnico), here is the adapted question:
Lets say, I have got a Movie ID 4711. I will fetch the movie:
$movie = $this->getDoctrine()
->getRepository("Movie")
->find(4711);
And now, I need to get all Actors from this movie sorted by name (as an example).
$actorsOfMovie = $this->getDoctrine()
->getRepository("Actor")
->findBy(array("movie_id" => 4711), array('name'=>'asc'));
Is this really the correct way?
With this version, I need to know in the controller, how the relationship between movie and actors work! Thats a thing, doctrine should handle for me!
And how to use it with multiple movies?
// Controller
$movies = $this->getDoctrine()
->getRepository("Movie")
->findfindBy(array());
return $this->render("xyz.html.twig", array("movies": $movies));
// Twig: xyz.html.twig
{% for movie in movies %}
<h1>{% movie.getName() %}</h1>
<p>Actors: {% for actor in movie.getActorsOrderByName() %}{{ actor.getName() }},{% endfor %}
{% endfor %}
You just have to create a specific function in your class Repository:
class MovieRepository extends EntityRepository
{
public function getActoryByGender($gender)
{
/.../
}
}
And in your controller:
/.../
$em = $this->getDoctrine()>getManager();
$repository = $em->getRepository('YourBundle:Movie');
$actors = $repository->getActorByGender('male');
/.../
I think the best solution for this is to use doctrine's Criteria class (http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections).
Have a look at https://www.boxuk.com/insight/blog-posts/filtering-associations-with-doctrine-2
Based on this, I can do the following:
// In the Movie Class
/**
* Get actors
*
* #param array $options
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getActors($options = array())
{
if(!$options) {
return $this->actors;
}
$criteria = Criteria::create();
if(isset($options["order_by"])) {
$criteria->orderBy($options["order_by"]);
}
if(isset($options["limit"])) {
$criteria->setMaxResults($options["limit"]);
}
if(isset($options["offset"])) {
$criteria->setFirstResult($options["offset"]);
}
// Or I can define other filters or sorting stuff
if(..) {
...
}
return $this->actors->matching($criteria);
}
I am stuck with a problem please help me with it. Here is the scenarario:
I have an entity "User" and corresponding repository "UserRepository", inside my entity there are only getter and setter methods. All custom queries I have written to UserRepository. Now inside my UserController I am trying to access repository methods which I am not able to do so.
e.g.
User entity:
class User
{
...
public function getId()
{
return $this->id;
}
public function setId($id)
{
return $this->id=$id;
}
public function setProperty($property)
{
$this->property = $property;
}
public function getProperty()
{
return $this->property;
}
....
}
?>
UserRepository:
class UserRepository extends EntityRepository
{
public function findUsersListingById($id)
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$query = $em->createQuery(
"SELECT U
FROM UserEntityPathGoesHere
WHERE U.id IN (".implode(",", $id).")"
);
$users = $query->getResult();
return $users;
}
public function sayHelloWorld(){
echo ' Hello World';
}
}
?>
UserController
class UserController
{
...
$users=$this->getDoctrine()
->getRepository('MyUserEntityPath')
->findUsersListingById($ids);
//now I have multiple users I want to iterate through each user for associating additional data with each user
foreach($users as $user)
{
$temp = array();
//I am able to access getId method which is defined in User entity
$temp['id'] = $user->getId();
//however I am not able to access method from UserRepository, I tried something like below which gives me error call to undefined function sayHelloWorld
$temp['status'] = $user->sayHelloWorld();
....
}
}
....
How can I access repository methods for an entity? Is it possible ? If not then what are the alternatives for the solution?
Everything is possible however you should not access the entity's repository from the entity itself because of the separation of concerns.
See this Stackoverflow answer for more details.
Basically, the whole idea is that you want to have your application organized the following way.
In short:
Controller > Repository > Entities.
It should not go in the other direction otherwise it creates a mess.
If you want to go a bit further into the separation of concerns you could do the following.
Controller > Service > Repository > Entities
Alternative solutions:
Create a Twig extension that access a service (which access a repository) or a repository.
Create a method in your repository, call the method in your controller, map the data to IDs (keys of array are the IDs), pass the array to the template and then pull the data from the array using the entity IDs
Create a method in your repository, call the method in your controller, inject the data into your entities and access the data through the entity in your template.
There are probably others but you would know better how your application is organized.
If the bundle is Acme/DemoBundle, then one would expect at a minimum
User entity
namespace Acme/DemoBundle/Entity
use Doctrine\ORM\Mapping as ORM;
/**
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Acme/DemoBundle/Entity/UserRepository")
*/
class User
{
...
}
User repository
namespace Acme/DemoBundle/Entity
use Doctrine\ORM\Mapping as ORM;
class UserRepository extends EntityRepository
{
...
}
It is also true that with an array of ids, one can also do the following in a controller:
...
$em = $this->getDoctrine()->getManager();
$users = $em->getRepository("AcmeDemoBundle:User")->findAllById($idArray);
...
To iterate thru users in a controller, one can then use a foreach loop as in:
foreach ($users as $user) {
//each user is an array
...
$id = $user['id'];
...
}
or in a template:
{% for user in users %}
...
{{ user.firstName }}
...
{% endfor %}
You need to declare the UserRepository as an EntityRepository for your user entity. In your User entity add this annotation:
/**
* #ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\UserRepository")
*/
See the docs for a more detailed description.
You can use the postLoad event from doctrine and inject everything you want into the entity. The event listener looks like:
<?php
namespace AppBundle\EventListener;
use AppBundle\Entity\MyEntity;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* Class MyEntityListener
*/
class MyEntityListener
{
public function postLoad(LifecycleEventArgs $eventArgs)
{
/** #var MyEntity $document */
$document = $eventArgs->getEntity();
if(!($document instanceof MyEntity)){
return;
}
$document->setEntityManager($eventArgs->getEntityManager());
}
}
and service.yml:
services:
app.myentity.listener:
class: AppBundle\EventListener\MyEntityListener
tags:
- { name: doctrine.event_listener, event: postLoad }
Of cource your Entity needs the method setEntityManager and your're ready.
I have a controller which implements all routes/URL(s).
I had the idea to offer a generic index over all help-pages.
Is there a way to get all routes defined by a controller (from within a controller) in Symfony2?
What you can do is use the cmd with (up to SF2.6)
php app/console router:debug
With SF 2.7 the command is
php app/console debug:router
With SF 3.0 the command is
php bin/console debug:router
which shows you all routes.
If you define a prefix per controller (which I recommend) you could for example use
php app/console router:debug | grep "<prefixhere>"
to display all matching routes
To display get all your routes in the controller, with basically the same output
I'd use the following within a controller (it is the same approach used in the router:debug command in the symfony component)
/**
* #Route("/routes", name="routes")
* #Method("GET")
* #Template("routes.html.twig")
*
* #return array
*/
public function routeAction()
{
/** #var Router $router */
$router = $this->get('router');
$routes = $router->getRouteCollection();
foreach ($routes as $route) {
$this->convertController($route);
}
return [
'routes' => $routes
];
}
private function convertController(\Symfony\Component\Routing\Route $route)
{
$nameParser = $this->get('controller_name_converter');
if ($route->hasDefault('_controller')) {
try {
$route->setDefault('_controller', $nameParser->build($route->getDefault('_controller')));
} catch (\InvalidArgumentException $e) {
}
}
}
routes.html.twig
<table>
{% for route in routes %}
<tr>
<td>{{ route.path }}</td>
<td>{{ route.methods|length > 0 ? route.methods|join(', ') : 'ANY' }}</td>
<td>{{ route.defaults._controller }}</td>
</tr>
{% endfor %}
</table>
Output will be:
/_wdt/{token} ANY web_profiler.controller.profiler:toolbarAction
etc.
You could get all of the routes, then create an array from that and then pass the routes for that controller to your twig.
It's not a pretty way but it works.. for 2.1 anyways..
/** #var $router \Symfony\Component\Routing\Router */
$router = $this->container->get('router');
/** #var $collection \Symfony\Component\Routing\RouteCollection */
$collection = $router->getRouteCollection();
$allRoutes = $collection->all();
$routes = array();
/** #var $params \Symfony\Component\Routing\Route */
foreach ($allRoutes as $route => $params)
{
$defaults = $params->getDefaults();
if (isset($defaults['_controller']))
{
$controllerAction = explode(':', $defaults['_controller']);
$controller = $controllerAction[0];
if (!isset($routes[$controller])) {
$routes[$controller] = array();
}
$routes[$controller][]= $route;
}
}
$thisRoutes = isset($routes[get_class($this)]) ?
$routes[get_class($this)] : null ;
I was looking to do just that and after searching the code, I came up with this solution which works for a single controller (or any ressource actually). Works on Symfony 2.4 (I did not test with previous versions) :
$routeCollection = $this->get('routing.loader')->load('\Path\To\Controller\Class');
foreach ($routeCollection->all() as $routeName => $route) {
//do stuff with Route (Symfony\Component\Routing\Route)
}
If anyone is stumbling on this issue, this is how I exported the routes in the global twig scope (symfony 4).
src/Helper/Route.php
<?php
namespace App\Helper;
use Symfony\Component\Routing\RouterInterface;
class Routes
{
private $routes = [];
public function __construct(RouterInterface $router)
{
foreach ($router->getRouteCollection()->all() as $route_name => $route) {
$this->routes[$route_name] = $route->getPath();
}
}
public function getRoutes(): array
{
return $this->routes;
}
}
src/config/packages/twig.yaml
twig:
globals:
route_paths: '#App\Helper\Routes'
Then in your twig file to populate a javascript variable to use in your scripts
<script>
var Routes = {
{% for route_name, route_path in routes_service.routes %}
{{ route_name }}: '{{ route_path }}',
{% endfor %}
}
</script>
In Symfony 4 i wanted to get all the routes including controller and actions in one list. In rails you can get this by default.
In Symfony you need to add the parameter show-controllers to the debug:router command.
If somebody looking for the same feature it can be get with:
bin/console debug:router --show-controllers
this will produce a list like the following
------------------------------------------------------------------------- -------------------------------------
Name Method Scheme Host Path Controller
------------------------------------------------------------------------- -------------------------------------
app_some_good_name ANY ANY ANY /example/example ExampleBundle:Example:getExample
------------------------------------------------------------------------- -------------------------------------
The safest way to proceed is to use the symfony controller resolver, because you never know if your controller is defined as a fully qualified class name, a service, or whatever callable declaration.
foreach ($this->get('router')->getRouteCollection() as $route) {
$request = new Request();
$request->attributes->add($route->getDefaults());
[$service, $method] = $this->resolver->getController($request);
// Do whatever you like with the instanciated controller
}
Because you needed to get the route information from within a Controller, you can make use of Symfony's Autowiring. If it is configured properly, you can just pass RouterInterface to any routes in a Controller.
#[Route('/', name: 'app_default')]
public function someRoute(RouterInterface $router): JsonResponse
{
$routesPaths = [];
foreach ($router->getRouteCollection()->all() as $routeName => $route) {
$routesPaths["$routeName"] = $route->getPath();
}
return new JsonResponse([
'availableRoutes' => $routesPaths,
]);
}
However if you need this logic in multiple Controllers you should encapsulate the logic in a seperate class like #C Alex did in one of his answers.
I've a mixed array like this one (mobile numbers and entities):
$targets = array();
$targets[] = '+32647651212';
$targets[] = new Customer();
In my Twig template i have to call getMobile() if target is a Customer or just print the number if it's actually a number (string).
Is there something like instanceof operator in Twig?
<ul>
{% for target in targets %}
<li>{{ target instance of MyEntity ? target.getMobile : target }}</li>
{% else %}
<li>Nothing found.</li>
</ul>
In \Twig_Extension you can add tests
public function getTests()
{
return [
'instanceof' => new \Twig_Function_Method($this, 'isInstanceof')
];
}
/**
* #param $var
* #param $instance
* #return bool
*/
public function isInstanceof($var, $instance) {
return $var instanceof $instance;
}
And then use like
{% if value is instanceof('DateTime') %}
UPDATE 10-2021
Please be aware this answer has been written for symfony 3, and twig 2.
If you use a more recent version, please refer to the answer of #Garri Figueroa on this post.
As you can see in the twig documentation the class \Twig_Extension, \Twig_SimpleTest are now deprecated.
If you use a more recent version of symfony (I recommend it), please use the new class AbstractExtension, TwigFunction, etc
https://symfony.com/doc/5.3/templating/twig_extension.html
OLD VERSION : symfony 3.4
Here a nice way to do instanceof operator in twig with Extension :
1) Create your extention file where you want
(ex: src/OC/YourBundle/Twig/InstanceOfExtension.php )
With \Twig_Extension you can do many things, filter, fonction, but now we will create a Test.
So we implement function getTests(), and inside it we create a new \Twig_SimpleTest
The 1st arugment is the name of test you create, and the seconde a callable.
(can be a function() {}).
<?php
namespace OC\YourBundle\Twig;
class InstanceOfExtension extends \Twig_Extension {
public function getTests() {
return array(
new \Twig_SimpleTest('instanceof', array($this, 'isInstanceOf')),
);
}
public function isInstanceOf($var, $instance) {
$reflexionClass = new \ReflectionClass($instance);
return $reflexionClass->isInstance($var);
}
}
2) Register it in services.yml
(ex: src/OC/YourBundle/Resources/config/services.yml)
services:
[...may you have other services ...]
app.twig_extension:
class: OC\YourBundle\Twig\InstanceOfExtension
public: false
tags:
- { name: twig.extension }
3) Then use it in twig like this
{{ myvar is instanceof('\\OC\\YourBundle\\Entity\\YourEntityOrWhatEver') }}
Source from Adrien Brault => https://gist.github.com/adrienbrault/7045544
My solution for Symfony 4.3
1) Create the AppExtension class in src/Twig folder. (The class is automatically detected).
2) Extend the AbstractExtension class:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigTest;
class AppExtension extends AbstractExtension
{
public function getTests()
{
return [
new TwigTest('instanceof', [$this, 'isInstanceof'])
];
}
/**
* #param $var
* #param $instance
* #return bool
*/
public function isInstanceof($var, $instance) {
return $var instanceof $instance;
}
}
3) Then use same code of valdas.mistolis answer:
{% if value is instanceof('DateTime') %}
4) Thanks valdas.mistolis and symfony documentation i got my own solution:
Twig Extension templating
Since PHP 5.5.0 you can compare class names next way:
{{ constant('class', exception) is constant('\\Symfony\\Component\\HttpKernel\\Exception\\HttpException') }}
This snippet can help in particular cases when you need strict comparison of class names. If you need to check implementation of interface or to check inheritance would be better to create twig extension described above.
Another solution :
class A {
...
public function isInstanceOfB() {
return $this instanceof B;
}
}
class B extends A {}
class C extends A {}
then in your twig :
{{ a.isInstanceOfB ? ... something for B instance ... : ... something for C instance ... }}
OR
{% if a.isInstanceOfB %}
... do something for B instance ...
{% else %}
... do something for C instance ...
{% endif %}
Another example when iterating through Symfony forms:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigTest;
use App\Document\Embeded\Image;
use App\Document\Embeded\Gallery;
use App\Document\Embeded\Article;
class AppExtension extends AbstractExtension
{
public function getTests()
{
return [
new TwigTest('image', [$this, 'isImage']),
new TwigTest('gallery', [$this, 'isGallery']),
new TwigTest('article', [$this, 'isArticle']),
];
}
public function isImage($var) {
return $var instanceof Image;
}
public function isGallery($var) {
return $var instanceof Gallery;
}
public function isArticle($var) {
return $var instanceof Article;
}
}
Twig
{% if form.vars.data is gallery %}
This is a Gallery
{% elseif form.vars.data is article %}
This is an Article
{% endif %}
Other solution without ReflectionClass with a twig filter :
First you need a TwigExtension class. Then, add a function as twig filter or twig function,
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class TwigExtension extends AbstractExtension
/**
* #return array|\Twig_Filter[]
*/
public function getFilters()
{
return [
new TwigFilter('is_instance_of', [$this, 'isInstanceOf'])
];
}
/**
* #param $object
* #param $class
* #return bool
*/
public function isInstanceOf($object, $class): bool
{
return is_a($object, $class, true);
}
}
And in twig template :
{% if true is same as (object|is_instance_of('ClassName')) %}
// do some stuff
{% endif %}
Check http://php.net/manual/fr/function.is-a.php
Use default filter in Twig like this:
{{ target.mobile|default(target) }}
Quite old, but I can't see here one more good possibility to achive this:
Enity:
public function __toString()
{
return 'NameOfYourEntity';
}
Twig:
{{ entity }}