I'm currently researching Symfony CMF and PHPCR for a project I recently started. What I'm currently trying to figure out is how to create a Route and save it into the database. As far as I understand, I must use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route and persist the element into the database. This works fine, but automatically generates a route path, which is not what I want. What I need to do is generate a custom route which links to a specific controller. Here is my code:
$em = $this->get('doctrine_phpcr.odm.document_manager');
$parent = $em->find(null, '/cms/routes');
$route = new \Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route();
$route->setParentDocument($parent);
$route->setName('my_route_name');
$route->setDefault('_controller', 'AppBaseBundle:Frontend/Users:index');
$em->persist($route);
$em->flush();
If i execute this code, the generated route will be /cms/routes/my_route_name. From what I can see, you could use $route->setPath('/testing');, but that generates the following exception:
Can not determine the prefix. Either this is a new, unpersisted document or the listener that calls setPrefix is not set up correctly.
Does anybody have any ideas how to solve this?
In PHPCR, every document has a path where it is store. If you are familiar with doctrine ORM, the path has the role of the ID. The difference with ORM is that all documents (regardless of their type) live in the same tree. This is great, because your route can reference just anything, it is not limited to specific document types. But we need to create some structure with the paths. This is why we have the prefix concept. All routes are placed under a prefix (/cms/routes by default). That part of the document path is removed for the URL path. So repository path /cms/route/testing is the url domain.com/testing.
About your sample code: Usually, you want to configure the controller either by class of the content document or by route "type" attribute to avoid storing a controller name into your database to allow for future refactoring. A lot of this is explained in the [routing chapter of the CMF documentation][1] but the prefix is only used there, not explicitly explained. We need to improve the documentation there.
[1] http://symfony.com/doc/master/cmf/book/routing.html
I managed to find a way to overcome this issue. Because in my project I also have the RouteAutoBundle, I created a class which extends \Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route. Inside that class I added:
/**
* #PHPCR\Document(referenceable=true)
*/
class MenuRoute extends Route
{
protected $url;
/**
* Get $this->url
*
* #return mixed
*/
public function getUrl() {
return $this->url;
}
/**
* Set $this->url
*
* #param mixed $url
*/
public function setUrl($url) {
$this->url = $url;
}
}
After that I added this to cmf_routing_auto.yml:
App\MenuBundle\Document\MenuRoute:
uri_schema: /{getUrl}
token_providers:
getUrl: [content_method, { method: getUrl }]
So now one would just create an instance of MenuRoute (just like when using Route) and call the method setUrl($your_url) passing the desired url.
If anybody finds a better way, I'm opened to suggestions.
Related
Within EasyAdmin for Symfony you can use the AdminUrlGenerator for easily generating URLs to for example EasyAdmin CRUD Controllers.
Documentation here: https://symfony.com/doc/current/EasyAdminBundle/crud.html#generating-admin-urls
In my case i want to generate an URL to CRUD controller which is also linked within the Dashboard. If i create a link to a CRUD controller, the link works, but the corresponding MenuItem is not highlighted.
I found out, that EasyAdmin highlights the MenuItem with an URL parameter calling menuIndex. I can unset the menuIndex during Link Generation, but then no menuItem is highlightd in the menu.
I havn't found any information on how to get the correct menuIndex for a generated CRUD URL.
So how can i generate Admin URLs with correct menuIndex?
I want to give one possible answer. But to be honest, i don't really like my solution. Maybe someone has a better solution.
So, i am iterating over the configured menuItems within the DashboardController and trying to determine the menuIndex for a given Entity.
The code lookes like this:
private function getIndexLinkForCrudController(string $controller): string
{
return $this->adminUrlGenerator
->unsetAll()
->setController($controller)
->setAction(Action::INDEX)
->set('menuIndex', $this->determineMenuIndexForEntity($controller::getEntityFqcn()))
->generateUrl();
}
private function determineMenuIndexForEntity(string $entity): ?int
{
$menuItems = $this->configureMenuItems();
foreach ($menuItems as $id => $menuItem) {
/* #var MenuItemInterface $menuItem */
$routeParameter = $menuItem->getAsDto()->getRouteParameters();
if (
is_array($routeParameter) &&
array_key_exists(EA::ENTITY_FQCN, $routeParameter) &&
$routeParameter[EA::ENTITY_FQCN] == $entity
) {
return $id;
}
}
return null;
}
This code works for me. But this code only works within the DashboardController. If i want to create Admin URLs within a CRUD controller i need to move the menu config to a static method and accessing it there.
Also i am not fetching the error case when i cannot determine the menuIndex and returning null. For my case it's fine.
Maybe this is helpfull for someone.
If somebody has better solution, i would be happy to here about it.
Before i state my problem, please be aware that this is my first time working with Typo3 and/or creating an Extbase extension.
SO basically i want to create an extbase extension for Typo3, but i seem not to be able to wrap my head around the concept of transfering an object (assigned to the view of a specific template) via arguments to an action, with the purpose of attaching the object to another (with a 1:n relation).
My example:
I have an Objekt of the type "Appliance" assigned to the view of a template ("Show.html"). I can list all the properties of it in the Template, so it definitely exists in the view.
Now i want to create an Object of the type "Host" using a form and then attaching it to this specific "Appliance" object.
The problem is: I can't transfer the object of the type "Appliance" to the specific Action of the Controller of the type "Host" which itself should then assign it to the view of another template.
Look at the following code example:
<f:link.action action="new" controller="Host" arguments="{appliance:appliance}" >Add Host X</f:link.action>
This is the specific code line in the "Show.html" template that transfers the "Appliance" object to the Action "new" of the "Host" controller using arguments... The "Host" controller:
public function newAction(\Cjk\Icingaconfgen\Domain\Model\Appliance $appliance, \Cjk\Icingaconfgen\Domain\Model\Host $host = NULL)
{
$this->view->assign('appliance', $appliance);
$this->view->assign('host', $host);
}
At this point i get the following error message:
"Argument 1 passed to
Cjk\Icingaconfgen\Controller\HostController::newAction() must be an
instance of Cjk\Icingaconfgen\Domain\Model\Appliance, none given"
What am i doing wrong?
You need a Docblock that describes these parameters.
What may look like just comments, actually follows the PHPDoc standard. These declarations are interpreted by the TYPO3 ReflectionClass to map your Domain Model and validate parameters and object properties.
Make sure you completely flush the cache whenever you add or update one of these.
/*
* #param \Cjk\Icingaconfgen\Domain\Model\Appliance $appliance
* #param \Cjk\Icingaconfgen\Domain\Model\Host $host
* #return void
*
*/
public function newAction(\Cjk\Icingaconfgen\Domain\Model\Appliance $appliance, \Cjk\Icingaconfgen\Domain\Model\Host $host = NULL)
{
$this->view->assign('appliance', $appliance);
$this->view->assign('host', $host);
}
You need to be sure that there is an Appliance model given in your Fluid template, easily by debugging it before the link with e.g. <f:debug>{appliance}</f:debug>
If this is okay, you should add some doc comments above your newAction because Extbase is referring to that.
An example would be: (just as I am writing this, a good example was posted). :)
I am sorry for asking such a question and I know there have been questions like this before, but my case is rather...stupid. What I have is a project management system and I want to display all projects which works fine and is auto generated by Symfony. In the same controller which is ProjectController I made another action. I wanted to list all archived projects which have been completely done. For a start I simply copy pasted the code and the annotations and changed the routes and the name of the function. Here is my indexAction function
/**
* Lists all project entities.
*
* #Route("/", name="project_index")
* #Method("GET")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$projects = $em->getRepository('AppBundle:Project')->findAll();
return $this->render('project/index.html.twig', array(
'projects' => $projects,
));
}
It is as simple as it can get. Now here is my archiveAction function which is the same
/**
* Lists all project entities.
*
* #Route("/archive", name="project_archive")
* #Method("GET")
*/
public function archiveAction()
{
$em = $this->getDoctrine()->getManager();
$projects = $em->getRepository('AppBundle:Project')->findAll();
return $this->render('project/index.html.twig', array(
'projects' => $projects,
));
}
So far I am not even filtering the projects, but doing the exact same thing as I do in the indexAction function and bear in mind both functions are in the same controller. Here is the error I receive when trying to show all archived projects -
AppBundle\Entity\Project object not found.
The index action works just fine and displays all projects, but if I change the route and change the name of the function and keep everything the same and they are in the same Controller - in one case it can find AppBUndle\Entity\Zadanie, but in the other case - it can't.
SOLVED I managed to solve the problem by putting the archive function on top. By on top I mean in first position before any other function. Before doing so I tried switching the routes. I took the route from indexAction and put it in the annotations from the archiveAction and the opposite. Both functions worked just fine. Then I returned them as they were and archive still didn't work. Then I simply put archive in first place before index and it worked as a charm. I have no idea what just happened and why but...didn't matter...it works. ( seriously though - if anybody knows why that is I would appreciate it)
EDIT: So 2 years and 4 months later I think I got the crank of it, the routes are cached so what I did was just clear the cache and it worked, but at the time I didnt pay attention to this detail
I found the same problem and I managed to find the cause of the problem.
routing problem, for example:
#Route ("/ {id}", name = "project_archive_show")
function2
#Route ("/ archive", name = "project_archive")
function1
urls must have personalized specifications:
for example :
#Route ("/ show / {id}", name = "project_archive_show")
function2
or says that id is an integer
I'm trying to generate link to URL which contains two parameters (both of those parameters are not really necessary but I do it for practice). I created custom showAction in DiscovererController
/**
* #Route("/rivers/{river_id}/discoverers/{id}", name="discoverer_show")
* #Template
*/
public function showAction($river_id, $id){
$em = $this->getDoctrine()->getEntityManager();
$river = $em->getRepository('MyOwnBundle:River')->find($river_id);
if(!$river){
throw $this->createNotFoundException("no river with provided id");
}
$entity = $river->getDiscoverer();
return array('entity' => $entity);
}
As you can see two parameters are passed, id of the river and id of the discoverer (which is absurd but as I said, practice...).
In show action of a river (/rivers/1) I decided to put following code:
<p>{{entity.discoverer.name}}</p>
Note that 'entity' is a river here, and river has a discoverer. Unfortunatelly, when I try to render this action, I get error which tells me that:
An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "discoverer_show" as such route does not exist.") in /path/to/project/src/My/OwnBundle/Resources/views/River/show.html.twig at line 9.
I dont have a clue what is wrong, I provided both necessary parameters and used "discoverer_show" which I defined in my controller. How to correctly render this link?
A piece of advice: do not use tabs in your source code at all! Make your IDE to replace tab character with 4 spaces. This could save you a lot of trouble... Tabs does not behave well in git too.
Ok, by accident i figured it out. Turns out annotations in symfony2 CANNOT begin with tab.
So this thing right here is NOT going to work
/**
* #Route("/people")
*/
But this will work like a charm:
/**
* #Route("/people")
*/
When retrieving models from database and sending them to the client I want to include for each model the url to that resource.
Let's take as example this Article model, with:
id
title
content
etc.
Storing the url to an article in the DB doesn't make sense, because it can be easily made up from id and title:
ex: http://www.example.com/articles/article_id/article_title
So, this is what I am doing now:
I use the $appends array:
/**
* Additional attributes
*
* #var array
*/
protected $appends = array('article_url');
and created a getter for article_url:
/**
* Get the article url attribute
*
* #return string
*/
protected function getArticleUrlAttribute()
{
return $this->exists
? url('articles', $parameters = array(
$this->getKey(),
Str::title(Str::limit($this->title, 100))
))
: null;
}
This works just fine. The problem is, that probably the model should not include any logic for creating urls. What is a good approach for this problem? Where should I create the
url to the article before sending it to the client?
That sort of logic would usually go in whatever your framework's routing engine is. For instance, since it sounds like you're using Laravel, you'd probably make a Named Route -- call it, say, "canonical_article".
Then you can use the link_to_route helper to have your framework generate the URL.