Hi I am trying to generate json as response using symfony2.
Here is my code.
/**
* #Route("/viewComments/{taskId}")
*
*/
public function viewCommentsAction($taskId)
{
$commentsList = $this->getDoctrine()->getRepository("AppBundle:Comments")
->findBy(array("task" => $taskId));
if (!$commentsList) {
throw $this->createNotFoundException("non comments to show");
}
$serializer = $this->get('serializer');
$json = $serializer->serialize(
$commentsList,
'json', array('groups' => array('group1'))
);
return new Response($json);
}
unfortunately I am getting response:
[[],[],[],[]]
Any suggestions?
Try this, creates a file: CommentRepository
class CommentRepository extends EntityRepository
{
public function findCommentsList($id)
{
$query = $this->getEntityManager()->createQuery('YOUR QUERY HERE');
...
return $query->getArrayResult();
}
}
as you can see, I return an Array and now you can see values on the response.
Later, changes the next line in your Action Controller:
...
$commentsList = $this->getDoctrine()->getRepository("AppBundle:Comments")
->findCommentsList($taskId));
...
Note: You should call your entities in singular
Related
I have this business requirement where I need to create update entities of the same entity type with a single request(Tabular form).
Entity Class:
class Tag{
private $name;
}
I have setup this custom controller to accept multiple instances of the Tag class in json.
For example :
{
[
{"name":"tag 1","id":"T1"},
{"name":"tag 2","id":"T2"},
{"name":"tag 3","id":"T3"}
]
}
And my custom action is defined as below.
class TagMultipleAction extends AbstractController
{
public function __construct(
private EntityManagerInterface $entityManager,
private SerializerInterface $serializer,
private ValidatorInterface $validator
)
{
}
public function __invoke(TagData $data): JsonResponse
{
$tagPostErrors = [];
foreach ($data as $tagPst) {
$tag = new Tag();
$tag->setName($tagPst['name']);
try {
$this->validator->validate($tag, ['groups' => 'create']);
$this->entityManager->persist($tag);
} catch (ValidationException $ex) {
$tagPostErrors[$tagPst['id']] = [$ex->getMessage()];
}
}
if (count($tagPostErrors) > 0) {
return new JsonResponse($tagPostErrors, 400);
}
$this->entityManager->flush();
return new JsonResponse(null, 204);
}
This controller works fine and all the data persists without any issue.
What I want to know is that instead of me mapping the post data to object($tag->setName($tagPst['name']), is there a way to automate this using DTO's. Hoping to find a better way to deal with tabular data.
In my project, I have duplicated functions in my Controllers as each Controller needs to have similar functionality but it feels slightly dirty to repeat code.
In both my EventController and my ArticleController I have a duplicated function called handleTags() that literally does the same thing in each model.
The code looks like this:
/**
* Handle tagging of this resource
*
* First, get the tags field and make sure it isn't empty
* Convert the string into an array and loop through the array
* Create new tags if they don't exist
*
* #param Request $request: data from the request
* #param int $id: the ID of the model instance have tags synced to
*/
public function handleTags(Request $request, $id)
{
$event = Event::find($id);
if ($request->has('tags')) {
$tags = $request->get('tags');
if (!empty($tags)) {
// Turn a String into an array E.g. one, two
$tagArray = array_filter(explode(", ", $tags));
// Loop through the tag array that we just created
foreach ($tagArray as $tag) {
Tag::firstOrCreate([
'name' => ucfirst(trim($tag)),
'slug' => str_slug($tag)
]);
}
// Grab the IDs for the tags in the array
$tags = Tag::whereIn('name', $tagArray)->get()->pluck('id');
$event->tags()->sync($tags);
} else {
// If there were no tags, remove them from this model instance
$event->tags()->sync(array());
}
}
}
Would it be possible to move this functionality into a Trait? Something like Taggable?
Then you would call handleTags() in the relevant Controllers via the trait, in the same way Searchable gives you access to the search() method?
I think that a better solution will be to make a Model trait, I will explain my self.
trait HasTags {
public function handleTags($tags)
{
$tags = array_filter(explode(", ", $tags))
$tags = array_map(function () {
return Tag::firstOrCreate([
'name' => ucfirst(trim($tag)),
'slug' => str_slug($tag)
]);
}, $tags)
$this->tags()->sync(collect($tags)->pluck('id'))
}
public function tags()
{
return $this->morphMany(Tag::class);
}
}
Model
class Event extends Model
{
use HasTags;
}
Controller
$event = Event::find($id);
if ($request->has('tags')) {
$event->handleTags($request->get('tags'));
}
I Write it very quickly and without testing it but this is the general idea.
you can event have more refactoring by using all the array manipulations with collections.
You can make a trait in app/Http/Traits/TaggableTrait.php
You just need to pass an object instead of the id, so that the function will be independent from the class type.
then your trait will be something like this :
namespace App\Http\Traits;
use App\Tag;
trait TaggableTrait
{
/**
* #param Request $request: data from the request
* #param App\Article | App\Event $object: the model instance have tags synced to
*/
public function handleTags(Request $request, $object)
{
if ($request->has('tags')) {
$tags = $request->get('tags');
if (!empty($tags)) {
// Turn a String into an array E.g. one, two
$tagArray = array_filter(explode(", ", $tags));
// Loop through the tag array that we just created
foreach ($tagArray as $tag) {
Tag::firstOrCreate([
'name' => ucfirst(trim($tag)),
'slug' => str_slug($tag)
]);
}
// Grab the IDs for the tags in the array
$tags = Tag::whereIn('name', $tagArray)->get()->pluck('id');
$object->tags()->sync($tags);
} else {
// If there were no tags, remove them from this model instance
$object->tags()->sync(array());
}
}
}
}
EventController
use App\Http\Traits\TaggableTrait;
class EventController extends Controller
{
use TaggableTrait;
/*** ***** */
public function update(Request $request, $id)
{
/** ***/
$event = Event::findOrFail($id);
handleTags($request, $event);
/*** *** */
}
}
I've developed an API with Laravel 5 and Dingo/API.
Following the documentation, i used pagination and my code look like that
$users = User::paginate(50);
return $this->response->paginator($users, new UserTransformer);
Unfortunately, the response root key is "data"
{
"data": [
{
"id": 1,
"username": "superuser",
......
I'd like to change the "data" key to a custom one, because in my case, emberjs get this response and try to make a link with a "datum" model which doesn't exist, the key need to be set with the same name as the ember model in case of a RESTAdapter.
I already tried some parameters in the response but nothing change
return $this->response->paginator($users, new UserTransformer, ['key' => 'users']);
or
return $this->response->paginator($users, new UserTransformer, ['identifier' => 'users']);
Nothing work, i'm stuck with the "data" key.
Is someone have a solution ?
Thank you in advance.
I managed to fix my problem.
I don't modify the api.php configuration, transformer stay the same
'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
Firstly i create a new serializer
// app/Api/V1/Serializers/CustomJsonSerializer.php
<?php namespace App\Api\V1\Serializers;
use League\Fractal\Pagination\CursorInterface;
use League\Fractal\Pagination\PaginatorInterface;
use League\Fractal\Serializer\SerializerAbstract;
/**
* Create a new Serializer in your project
*/
use League\Fractal\Serializer\ArraySerializer;
class CustomJsonSerializer extends ArraySerializer
{
public function collection($resourceKey, array $data)
{
if ($resourceKey === false) {
return $data;
}
return array($resourceKey ?: 'data' => $data);
}
public function item($resourceKey, array $data)
{
if ($resourceKey === false) {
return $data;
}
return array($resourceKey ?: 'data' => $data);
}
}
And i set my new custom serializer inside the AppServiceProviders
// app\Providers\AppServiceProviders.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Dingo\Api\Transformer\Adapter\Fractal;
use League\Fractal\Manager;
use App\Api\V1\Serializers\CustomJsonSerializer;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new Manager();
$fractal->setSerializer(new CustomJsonSerializer());
return new Fractal($fractal);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
I hope it'll help ppl :)
or you can use something like this
Answer::where('question_id', '=', $questionId)
->join('articles', 'answers.article_id', '=', 'articles.id')
->orderBy('count_thanks', 'desc')
->limit($perPage)
->offset($offset)
->get();
I am trying to setup a project for an API using slim framework version 3, I don't know who made the PSR-7 and marked the response object as immutable, I don't see any use in that (IMHO. please explain me if I am wrong). Things were much easier when it was slim 2. Now I came back to slim after a long time.
I have a route which is a post method, I am getting data and saving it to the database and I am trying to send 201 as the response code. all the examples and the documentation is showing you how can you change the response code within the index.php file itself, But I am trying to change it from a response builder which I have tried to use the factory pattern to provide different responses. The problem is the response code always stays 200 no matter what function I call from the response builder class. I tried many forums and different ways of slim but still couldn't able to pull this up. I almost decided to give up on a PSR 7 router implementation and going to implement my own routing solution. But I remember not to reinvent the wheel again so I came here as a final try. Below is the code.
the route definition
$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
$data = $req->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $res);
$control = new \Apex\Controllers\User($model, $jsonBuilder);
$control->create($data);
});
the controller method (abstract I am just setting it up)
public function create($data) {
if($this->model->save($data)) {
$this->response->build($data,201);
} else {
$this->response->build('error',400);
}
}
the JSON builder
class JSONBuilder implements Response
{
public $response;
public function __construct($response)
{
$this->response = $response;
}
public function build($data, $status)
{
$response = $this->response->withJSON($data,$status);
return $response;
}
}
can anyone point me in the right direction?
The PSR-7 decision to use immutable objects for Request and Response is documented in the Why value objects? section of the Meta document.
With Slim 3, you must always return a Response instance from the controller method.
$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
$data = $req->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $res);
$control = new \Apex\Controllers\User($model, $jsonBuilder);
return $control->create($data);
});
and then your create method also needs to return the $response:
public function create($data) {
if($this->model->save($data)) {
$this->response->build($data,201);
} else {
$this->response->build('error',400);
}
return $this->response;
}
It should then work.
However, you can use the controller method directly from the route declaration and avoid the need for a the closure:
$app->post('/users', `Apex\Controllers\User::create`);
The controller's create method would then look like this:
namespace Apex\Controllers;
class User
{
public function create($request, $response)
{
$data = $request->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $response);
if ($model->save($data)) {
$response = $jsonBuilder->build($data, 201);
} else {
$response = $jsonBuilder->build('error', 400);
}
return $response;
}
}
Finally, consider rka-content-type-renderer instead of JsonBuilder, though maybe it does more than you've shown.
Update:
Ideally you'd use constructor injection to inject the User model into the controller. To do this:
Update your controller:
namespace Apex\Controllers;
use Apex\Models\User as UserModel;
class User
{
protected $userModel;
public function __construct(UserModel $userModel)
{
$this->userModel = $userModel;
}
public function create($request, $response)
{
$data = $request->getParsedBody();
$jsonBuilder = ApexResponse::getBuilder('JSON', $response);
if ($this->userModel->save($data)) {
$response = $jsonBuilder->build($data, 201);
} else {
$response = $jsonBuilder->build('error', 400);
}
return $response;
}
}
Write a factory for the Pimple dependency injection container:
$container = $app->getContainer();
$container['Apex\Controllers\User'] = function ($c) {
$userModel = new \Apex\Models\User(ApexDB::getInstance());
return new \ApexController\User($userModel);
};
In a bundle I'm developing and was working correctly I added a new functionality which involves adding a repository to the entity. Now, when I execute the newly added method I get the following error:
Warning: class_parents() [function.class-parents]: Class CmsPages does not exist and could not be loaded in /Applications/MAMP/htdocs/symfony-standard-2.1/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php line 40
The newly added code is:
Controller:
/**
* Returns an json formated tree
*
* #Route("/getTree", name="admin_cmsPages_getTree", options={"expose"=true})
*/
public function getTreeAction()
{
$em = $this->getDoctrine()->getManager();
$tree = $em->getRepository('CmsPages')->loadTree();
$response = new Response(json_encode( $tree ));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
Repository:
namespace Yanic\CmsBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
class CmsPagesRepository extends EntityRepository
{
public function loadTree()
{
$q = $this
->createQueryBuilder('p')
->select('p')
->orderBy( 'p.lft' )
->getQuery()
;
return $q->getArrayResult();
}
}
That's all that has changed... if any more code is needed for clarification I will post it.
So could anybody tell me what I'm doing wrong? I couldn't find anything neither on SO nor on Google.
Thanks in advance
I just found the error myself... the line
$tree = $em->getRepository('CmsPages')->loadTree();
has to be
$tree = $em->getRepository('CmsBundle:CmsPages')->loadTree();