URL to resource as additional (eloquent) model attribute - php

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.

Related

How to set custom headers in all methods of a Symfony 4 Controller with FOSRestBundle?

I'm building a RESTFUL API with FOSRest Bundle and return data with custom headers like this:
class InvestorController extends AbstractFOSRestController
{
/**
* Retrieve a list of investors
*
* #Rest\Get("/{page}", defaults={"page"=1}, requirements={"page"="\d+"})
*
* #param Integer $page
* #param Request $request
* #param InvestorRepository $investorRepository
*
* #return Response
*/
public function getInvestorsAction($page, Request $request, InvestorRepository $investorRepository)
{
$data = $investorRepository->getInvestorsList($page);
$data = $this->getUser()->getId();
$view = $this->view($data, 200)
->setHeader('Access-Control-Allow-Origin', 'http://localhost:3000') // Remove this bit in PROD
->setHeader('Access-Control-Allow-Credentials', 'true'); // Remove this bit in PROD
return $this->handleView($view);
}
}
Now I would like to pass these headers for all of the responses in this Controller. I would like to avoid having to send them manually each time. Is there a way to set custom headers for all of the controller's responses automatically, say, in the constructor or in somewhere else?
Thank you
There is multiple ways how to do this:
First one that comes to my mind would be to configure your webserver to attach these headers to every response it sends based on URL.
Second one would be to utilize Symfony internal event system and catch kernel.response and attach the headers there. You would need to filter the response based on where is coming from -
https://symfony.com/doc/current/reference/events.html#kernel-response
The other thing would be to create your own custom handler which calls these setHeader() methods instead of doing it inside Controller. To provide this handler you can simply override handleView() method -
https://symfony.com/doc/master/bundles/FOSRestBundle/2-the-view-layer.html#custom-handler

How to create a route with custom path in Symfony RoutingBundle (PHPCR)?

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.

correct syntax for route name when routing with annotations in presence or url parameters

I am using annotations in a controller to resolve url "/registerded"
/**
* #Route("/registered", name="registered_user")
*/
public function registeredAction(){
return $this->render('MyBundle:Default:registered.html.twig');
}
This works ok, without arguments . It works good when I refer to it as "path" defined with "name" parameter in annotation. Exactly I use it in another action of the same controller:
return $this->redirectToRoute('/registered_user');
This is all ok.
Now I 'd like to have a working url like "/registerded/45" and I need to refer to it with a path name such above. I think something like :
return $this->redirectToRoute('/registered_user/45');
I would like to use annotations (I know how to make it under routing_dev.yml and so on, but I want to do it with annotations in the controller).
I've tried
/**
* #Route("/registered/{id}", name="registered_user/{id}")
*/
public function registeredAction($id){
return $this->render('MyBundle:Default:registered.html.twig');
}
But it's not the correct configuration for name parameter. I receive :
Unable to generate a URL for the named route "/registered_user/45" as such route does not exist.
Please is there someone can suggest me the right syntax for name parameter in annotation when I need to pass a url parameter as with the redirect url above ?
You don't need to add / prefix, when generating a url address.
Also, if you have troubles using a method from Symfony, you can always look how is defined and what arguments expects.
To answer your question, since you already added name to your route registered_user, in order to get URL like /registered/45, you need to edit your route path only, and pass the required arguments.
So, for this:
/**
* #Route("/registered/{id}", name="registered_user")
*/
your redirect call should look like this:
return $this->redirectToRoute('registered_user', array(
'id' => 45
));
A copy from Controller.php
/**
* Returns a RedirectResponse to the given route with the given parameters.
*
* #param string $route The name of the route
* #param array $parameters An array of parameters
* #param int $status The status code to use for the Response
*
* #return RedirectResponse
*/
protected function redirectToRoute($route, array $parameters = array(), $status = 302)
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}

Save entities to a REST API instead of DB using Doctrine 2

This is related to my other question: Persisting entities using a REST API.
For a project in Symfony2 I need to be able to persist entities using an remote (third-party) RESTful API. I also want to be able to retrieve entities with data from that API.
In other words, my objects are saved in the third-party database. They are not saved in my own database. Whenever I need to save data, or find data, I use their REST API.
I have been pointed to several libraries, including one made by Doctrine itself. However, none of them offers me what I'm looking for. The one made by Doctrine is the best option, but uses the Active Record pattern and doesn't offer all the sweet Doctrine 2 stuff. Don't get me wrong, I've been using Active Record implementations for a long time, but I've fallen in love with Doctrine's Data Mapper pattern now.
Ideally, I'd like to be able to use Doctrine's ORM and simply replace the database-specific part with logic that saves entities using an API call. (and of course retrieves them using that same API). This way I can save my entities using roughly the same syntax:
// current way to save $entity in database:
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
// desired way to save $entity using REST API:
// (just an example, it doesn't have to be exactly like this)
$em = $this->getDoctrine()->getManager('rest');
$em->persist($entity);
$em->flush();
Note that I'm not trying to build my own API, I'm simply trying to communicate with a third party API in order to save my entities. I'm relatively new to Doctrine, but I'm liking it so far. I really like the idea of seperating the persistence logic from the entities, but so far I can't find out how I can use that to save them using an API.
There is an article in Symfony's documentation, describing how to work with multiple Entity Managers. I'm looking for a solution similar to this, but with an entity manager that enables me to use REST instead of the DB.
I've been trying to tweak Doctrine's ORM myself, but I only end up rewriting half their code because it (seems to be) too tightly coupled to the Database-specific logic. I might be doing something stupid of course.
So my question is, is there a way to replace / override the database-specific parts of Doctrine's ORM with custom ones? Without rewriting a lot of things that should be common for all persistence methods? Has it been done before? Or is it simply not possible because Doctrine is intended for use with a database and isn't flexible enough for other uses?
My own progress
CakePHP seems to be able to do this, by letting you define a custom DataSource. This way you can save your models using an SQL database, but also using an API, sessions, etc. I want to do roughly the same, but using Doctrine instead of CakePHP.
Update 1
The actual database queries seem to be executed by the
Doctrine\ORM\Persisters\BasicEntityPersister class. There are several other xxxPersister classes, to deal with different types of inheritance. It might be possible to replace the xxxPersister classes with our own, so we can replace the DB code with REST API code.
The persister objects are created within the getEntityPersister() method of the Doctrine\ORM\UnitOfWork class. The classnames are hardcoded so we need to override Doctrine\ORM\UnitOfWork if we want to use our own persisters.
Update 2
Doctrine\ORM\UnitOfWork seems to be hardcoded into Doctrine\ORM\EntityManager, so we need to override that one as well. However, this class seems to contain some database-specific parts. For instance, it's constructor requires a Doctrine\DBAL\Connection object as parameter. Perhaps it's better to create our own EntityManger (implementing the Doctrine\Common\Persistence\ObjectManager interface), as long as that doesn't take too much time / effort.
Update 3
The database-specific code for retrieving/loading/finding objects lives in the same class as the code for persisting / deleting etc: the Doctrine\ORM\Persisters\xxxPersister classes. So if we are able to replace them with our own, in order to persist objects, we can retrieve objects as well. When you call $entityRepository->findAll(), for instance, it will return $entityRepository->findBy(array()) (because findAll() is simply an alias for findBy(array())) which will run the following code:
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->loadAll($criteria, $orderBy, $limit, $offset);
In other words, once we get EntityManager to create the right UnitOfWork and xxxPersister objects, we will be able to use the find methods in the EntityRepository.
Update 4
I discovered that a new feature is developed for Doctrine: custom persisters (also see this). This should make it easier to use a custom persister class. I don't know yet if it will enable us to create a non-DB persister, but it looks promising. However, the last updates were in August, so I'm not sure if it's still in active development.
You might use https://github.com/doctrine/rest to build a REST client, which talks to the target server. The essential part here is the mapping from entity (local) to REST API (target).
In short: Doctrine2 (local DB) -> Rest client (entity to rest mapping) -> Request (target server)
Doctrine/Rest provides also the other way around: a Doctrine Rest Server, to expose your local entities via REST (requests to your server). But thats not what you are looking for.
DoctrineRestDriver is exactly doing what you are looking for.
https://github.com/CircleOfNice/DoctrineRestDriver
Configure Doctrine:
doctrine:
dbal:
driver_class: "Circle\\DoctrineRestDriver\\Driver"
host: "http://www.your-url.com/api"
port: 80
user: "Circle"
password: "CantRenember"
Build entity:
/**
* This annotation marks the class as managed entity:
*
* #ORM\Entity
*
* You can either only use a resource name or the whole url of
* the resource to define your target. In the first case the target
* url will consist of the host, configured in your options and the
* given name. In the second one your argument is used as it is.
* Important: The resource name must begin with its protocol.
*
* #ORM\Table("products|http://www.yourSite.com/api/products")
*/
class Product {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
public function getId() {
return $this->id;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
Let's assume we have used the value http://www.yourSite.com/api/products for the product entity's #Table annotation.
Controller:
<?php
namespace CircleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\HttpFoundation\Response;
class UserController extends Controller {
/**
* Sends the following request to the API:
* POST http://www.yourSite.com/api/products HTTP/1.1
* {"name": "Circle"}
*
* Let's assume the API responded with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* Response body is "1"
*/
public function createAction() {
$em = $this->getDoctrine()->getManager();
$entity = new CircleBundle\Entity\Product();
$entity->setName('Circle');
$em->persist($entity);
$em->flush();
return new Response($entity->getId());
}
/**
* Sends the following request to the API by default:
* GET http://www.yourSite.com/api/products/1 HTTP/1.1
*
* which might respond with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* Response body is "Circle"
*/
public function readAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
return new Response($entity->getName());
}
/**
* Sends the following request to the API:
* GET http://www.yourSite.com/api/products HTTP/1.1
*
* Example response:
* HTTP/1.1 200 OK
* [{"id": 1, "name": "Circle"}]
*
* Response body is "Circle"
*/
public function readAllAction() {
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CircleBundle\Entity\Product')->findAll();
return new Response($entities->first()->getName());
}
/**
* After sending a GET request (readAction) it sends the following
* request to the API by default:
* PUT http://www.yourSite.com/api/products/1 HTTP/1.1
* {"name": "myName"}
*
* Let's assume the API responded the GET request with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* and the PUT request with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "myName"}
*
* Then the response body is "myName"
*/
public function updateAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
$entity->setName('myName');
$em->flush();
return new Response($entity->getName());
}
/**
* After sending a GET request (readAction) it sends the following
* request to the API by default:
* DELETE http://www.yourSite.com/api/products/1 HTTP/1.1
*
* If the response is:
* HTTP/1.1 204 No Content
*
* the response body is ""
*/
public function deleteAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
$em->remove($entity);
$em->flush();
return new Response();
}
}
You can even use DQL or native queries.
As a ready-to-use solution wasn't available, I decided to write my own. I called it RAPL. It's heavily inspired by Doctrine's ORM (in fact, it uses many of the interfaces provided by Doctrine Common).
Using RAPL I can simply write a small YAML file to configure the mapping between my entities and the web service, allowing me to persist/retrieve entities using the custom EntityManager.
I think you are in not right way.
I'm not ready to dig into the documentation now, but I understand doctrine stack as:
ORM -> DQL (doctrine query language) ->dbal ->Some database sql
And point for implementation you feature in DBAL as custom database driver.
I think create common REST-Driver realy interesting feature and it will do easy integration with third-party services.
I'm not sure, but you can try to use lifecycle callback events for entities to perform persisting logic via REST.
I wanted to do a similar thing, so I built this library to help expose doctrine entities as RESTful resources. It has a fair amount of features, and allows you to define exactly what you want to have exposed via both pull (GET) and push (POST/PUT/PATCH) methods.
http://leedavis81.github.io/drest/
https://github.com/leedavis81/drest
Hope it helps

Changing separating character in Zend URLs

Using the Zend Framework and the url method for the view:
$this->url(array('field1' => this, 'field2' => 'is', 'field3' => 'my example'), 'route_name');
Where route_name is the name of the url route and each field# is retrieved from the database.
I noticed that by default it changes spaces in Controller/Action names into plus sign so that what looked like:
www.example.com/this is my example
to
www.example.com/this+is+my+example
I would like to change the separatoer from + to - to have something like
www.example.com/this-is-my-example
I know that another thread: How to change the separation character of Zend Url?
as documented a way to do it which I tried without success.
A thorough explanation on how to do it would be much appreciated.
EDIT2: I know where the problem lies if anyone is interested, it comes from the way the url is assemble, it uses urlencode which converts all non-alphanumeric characters expect - and _ and the spaces as +, there is no way to override that than replace the character create the url manually (as Maxime suggested) or create a custom url function replacing the characters (as suggest by aporat)...
Thanks!
If you really want to do that, you can extend the stock Zend_View_Helper_Url view helper and add your url logic into your view helper.
<?php
namespace Application\View\Helper;
class MyUrl extends \Zend_View_Helper_Url
{
/**
* Generates an url given the name of a route.
*
* #access public
*
* #param array $urlOptions Options passed to the assemble method of the Route object.
* #param mixed $name The name of a Route to use. If null it will use the current Route
* #param bool $reset Whether or not to reset the route defaults with those provided
* #return string Url for the link href attribute.
*/
public function myUrl(array $urlOptions = array(), $name = null, $reset = false, $encode = true)
{
return str_replace('+', '-', parent::url($urlOptions, $name, $reset, $encode));
}
}
and then just load your new view helper and you're good to go:
$helper = new \Application\View\Helper\MyUrl;
$this->view->registerHelper($helper, 'myUrl');
Unfortunately, you can't set anything before calling the url(...) function to achieve what you want to do. The reason is that when the URL is assembled, it uses the php urlencode(...) function.
That said, you still have many options:
1) You simply don't use the url(...) function and create your URLs manually. (Best option)
2) You create a new helper that acts like url(...) but add extra changes to the function to achieve what you want to do.
3) You take the output of the url(...) function and do a str_replace to change + with -. (I DO NOT recommend that option)
Personally, I create all my URLs manually to avoid this kind of problem.

Categories