filter entity fields on symfony controller - php

How can I choose(filter) on my controller which fields I want (or don't want) to pass to my frontend?
my Controller:
/**
* #Route("/", name="dashboard")
*/
public function index()
{
$aniversariantes = $this->getDoctrine()->getRepository(Usuario::class)->aniversariantes();
return $this->render('dashboard/index.html.twig', [
'controller_name' => 'DashboardController',
'aniversariantes' => $aniversariantes
]);
}
My repository:
/**
* #return []
*/
public function aniversariantes(): array
{
$qb = $this->createQueryBuilder('u')
->andWhere('u.ativo = 1')
->andwhere('extract(month from u.dtNascimento) = :hoje')
->setParameter('hoje', date('m'))
->getQuery();
return $qb->execute();
}
Dump from entity:
What can I do if I don't want to pass the "password" field for example?

If you are just trying to prevent certain fields from being dumped, it is useful to know
Internally, Twig uses the PHP var_dump function.
https://twig.symfony.com/doc/2.x/functions/dump.html
This means you can can define the PHP magic method __debugInfo in your entity
This method is called by var_dump() when dumping an object to get the properties that should be shown. If the method isn't defined on an object, then all public, protected and private properties will be shown.
https://www.php.net/manual/en/language.oop5.magic.php#object.debuginfo
So in your entity do something like this:
class Usuario {
...
public function __debugInfo() {
return [
// add index for every field you want to be dumped
// assign/manipulate values the way you want it dumped
'id' => $this->id,
'nome' => $this->nome,
'dtCadastro' => $this->dtCadastro->format('Y-m-d H:i:s'),
];
}
...
}

Related

How to generalize a resource function to be used in all controllers for different models?

In laravel API Resources:
I need a dynamic way to generalize a code for all resources to be used in all controllers instead of using resources in all methods for each controller .. for more clarification, I have a trait that includes generalized functions which return json responses with data and status code, lets take a "sample function" suppose it is showAll(Collection $collection) which is used for returning a collection of data of the specified model for example it is used for returning all users data ..
so I need to build a function that call what ever resource of the specified model, knowing that I have many models...
a) trait that include showAll method:
namespace App\Traits;
use Illuminate\Support\Collection;
trait ApiResponser
{
private function successResponse($data, $code) {
return response()->json($data, $code);
}
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$collection, 'code' => $code], $code);
}
protected function resourceData(Collection $collection) {
return $collection;
}
}
b) usercontroller as a sample
namespace App\Http\Controllers\User;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiController;
class UserController extends ApiController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$users = User::all();
// Here the showAll(Collection $collection) is used
return $this->showAll($users);
}
}
c) UserResource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'identity' => $this->id,
'name' => $this->name,
'email' => $this->email,
'isVerified' => $this->verified,
'isAdmin' => $this->admin,
'createDate' => $this->created_at,
'updateDate' => $this->updated_at,
'deleteDate' => $this->deleted_at,
];
}
}
generalize: means used everywhere without code redundancy
What about providers, you may load data there and make that data reachable at places where user data can be reachable ?
laravel docs
I found a simple solution.. by adding the following method
protected function resourceData($collection) {
$collection = get_class($collection[0]);
$resource = 'App\Http\Resources\\' . str_replace('App\\', '', $collection) .
'Resource';
return $resource;
}
The $collection[0] in the first line of this method will get the
model you are currently using.
get_class will get the model name ex: App\User
'App\Http\Resources\\' . str_replace('App\\', '', $collection):
This will get the path of the resource by adding 'App\Http\Resources\' before the
model
str_replace('App\\', '', $collection): will remove App\ path from the collection
name so App\User should be User
then 'Resource' would be concatenated with the previous results and the whole
string should be like that: App\Http\Resources\UserResource
So at the end you should return the whole string App\Http\Resources\UserResource
,finally you should call the resourceData() in
the showAll() method:
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
//Calling resourceData() method
$resource = $this->resourceData($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$resource::collection($collection), 'code' => $code], $code);
}

How to use conditional relationship in API Resource?

I have created an API Resource:
class OrderResource extends JsonResource
{
public function toArray($request)
{
return [
"id" => $this->Id,
"photo" => ''
];
}
}
In controller I get data from model OrderModel the put data into resource OrderResource:
public function show($id)
{
$order = OrderModel::with('OrderPhoto')->findOrFail(1);
return new OrderResource($order);
}
So, I tried to use relation OrderPhoto in OrderResource like this:
public function toArray($request)
{
return [
"id" => $this->Id,
"photo" => OrderPhotoResource::collection($this->whenLoaded('OrderPhoto')),
];
}
But it does not work and gives this error:
Undefined property: Illuminate\Database\Query\Builder::$map
I did dd($this) in resource and what I got:
Class OrderPhoto:
class OrderPhoto extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
TL;DR
Try this in your OrderResource:
use OrderPhoto as OrderPhotoResource;
//
public function toArray($request)
{
return [
"id" => $this->Id,
"photo" => new OrderPhotoResource($this->whenLoaded('OrderPhoto')),
];
}
Explanation
As you can see, you are already defining the OrderPhoto as a Resource Collection:
class OrderPhoto extends ResourceCollection // <-- note the extended class
So in this case, you'll need to use this class instanciating it and pass in it the collection, instead of using the static method collection.
When you define a API Resource for a single object, like this:
php artisan make:resource PostResource
you use it like below:
$post = Post::find(1);
return new PostResource($post);
And if you want to use an API Resource to format a collection of resources instead of a single one, you need to do this:
$posts = Post::all();
return PostResource::collection($posts); // <-- note the ::collection part
Controlling the metadata
If you want to have a total control of the returned metadata in the response, define a custom API Resource Collection class instead.
Generate the class as a collection (adding the 'Collection' at the end or using the flag --collection):
php artisan make:resource PostResourceCollection
then, after customize it:
$posts = Post::all();
return new PostResourceCollection($posts); // <-- instantiating the class

I want to send a variable from a Controller to a Trait on Laravel

I have a controller that store the data. This use a form Request and after Show the messages on a trait. I am using the same trait on 3 API but i want to know if is posible send/add to the json/array a Custom Message for each API. For example if Category created (Category created successfull o Product Created Successfull) according the api.
This is my Store on my controller
public function store(StoreCategory $request)
{
$data = request()->all();
$newCategory = Category::create($data);
return $this->respondCreated(new CategoryResource($newCategory));
}
I am using CategoryResource like Resource
And this is my trait
public function respondCreated($data)
{
return $this->setStatusCode(IlluminateResponse::HTTP_CREATED)->respond($data
->response()
);
}
public function respond($data, $headers = [])
{
$response = $data;
return $response->setStatusCode($this->statusCode);
}
CategoryResource code:
public function toArray($request) {
return [ 'id' => $this->id,
'name' => $this->name,
'parent_id' => $this->parent_id,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Is Possible add a custom message per Api Request? May be can i pass a Variable from my controller and after add the custom message add the variable to the array?
Best Regards
Sorry my english is not good
You can achieve this by adding attribute to the model directly : link
Occasionally, when casting models to an array or JSON, you may wish to add attributes that do not have a corresponding column in your database. To do so, first define an accessor for the value:
public function getHasMessageAttribute()
{
return "the message that you want to pass";
}
After creating the accessor, add the attribute name to the appends property on the model. Note that attribute names are typically referenced in "snake case", even though the accessor is defined using "camel case":
{
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['has_message'];
}
There are many ways to go from here but;
and finally pass the message through CategoryResource
'message'=> $this->has_message,
if you want to separate that from the DB entries you can always use with.

How to configure entities in zf2 for ManyToOne unidirectional doctrine mapping

I'm having trouble developing a form in zf2 using a doctrine ManyToOne unidirectional relationship. My entities look like this:
namespace AdminMyPages\Entity;
class MyPageItem
{
// ...
/**
* #ORM\ManyToOne(targetEntity="MyMessage")
* #ORM\JoinColumn(name="myMessageID", referencedColumnName="myMessageID")
**/
private $myMessage;
// ...
/**
* Allow null to remove association
*
* #param Collection $myMessage
*/
public function setMyMessage(Collection $myMessage = null)
{
$this->myMessage = $myMessage;
}
/**
* #return myMessage
*/
public function getMyMessage()
{
return $this->myMessage;
}
}
class MyMessage
{
// ...
}
The fieldset for MyPageItemFieldset looks like this:
namespace AdminMyPages\Form;
class MyPageItemFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('mypage-item-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'AdminMyPages\Entity\MyPageItem'))
->setObject(new MyPageItem());
// ...
$myMessageFieldset = new MyMessageFieldset($objectManager);
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'myMessage',
'options' => array(
'count' => 1,
'target_element' => $myMessageFieldset
)
));
}
public function getInputFilterSpecification()
{
// ...
return array(
'myMessage' => array(
'required' => false
),
);
}
}
With this configuration I am able to "get" data from the MyMessage through getMyMessage(), so I know that the tables have been joined. However, when I try to bind the entity in a form, I get an error:
File:
C:\xampp\htdocs\GetOut\vendor\zendframework\zendframework\library\Zend\Form\Element\Collection.php:167
Message:
Zend\Form\Element\Collection::setObject expects an array or Traversable object argument; received "DoctrineORMModule\Proxy\__CG__\AdminMyPages\Entity\MyMessage"
Stack trace:
#0 ... Zend\Form\Element\Collection->setObject(Object(DoctrineORMModule\Proxy\__CG__\AdminMyPages\Entity\MyMessage))
...
One thought I have is that, since the ManyToOne relationship will only produce a single match, the MyMessage fieldset is hardly a collection - it's just one item - so Zend\Form\Element\Collection might not be the right form element to use. But, if it's not a collection, what is it?
You are totally right. Your MyMessage should not be a Collection. It should simply be an instance of MyMessage.
You defined a ManyToOne between MyPageItem and MyMessage meaning one page item has one message and a message has many page items. But since it is a unidirectional relationship that last part is never defined.
So the setter should look like this:
/**
* #param MyMessage $myMessage
*/
public function setMyMessage(MyMessage $myMessage = null)
{
$this->myMessage = $myMessage;
}
And you should also change your form field definition to a single MyMessage item.

Laravel & Mockery: Unit testing the update method without hitting the database

Alright so I'm pretty new to both unit testing, mockery and laravel. I'm trying to unit test my resource controller, but I'm stuck at the update function. Not sure if I'm doing something wrong or just thinking wrong.
Here's my controller:
class BooksController extends \BaseController {
// Change template.
protected $books;
public function __construct(Book $books)
{
$this->books = $books;
}
/**
* Store a newly created book in storage.
*
* #return Response
*/
public function store()
{
$data = Input::except(array('_token'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
return Redirect::route('books.create')
->withErrors($validator->errors())
->withInput();
}
$this->books->create($data);
return Redirect::route('books.index');
}
/**
* Update the specified book in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
$book = $this->books->findOrFail($id);
$data = Input::except(array('_token', '_method'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
// Change template.
return Redirect::route('books.edit', $id)->withErrors($validator->errors())->withInput();
}
$book->update($data);
return Redirect::route('books.show', $id);
}
}
And here are my tests:
public function testStore()
{
// Add title to Input to pass validation.
Input::replace(array('title' => 'asd', 'content' => ''));
// Use the mock object to avoid database hitting.
$this->mock
->shouldReceive('create')
->once()
->andReturn('truthy');
// Pass along input to the store function.
$this->action('POST', 'books.store', null, Input::all());
$this->assertRedirectedTo('books');
}
public function testUpdate()
{
Input::replace(array('title' => 'Test', 'content' => 'new content'));
$this->mock->shouldReceive('findOrFail')->once()->andReturn(new Book());
$this->mock->shouldReceive('update')->once()->andReturn('truthy');
$this->action('PUT', 'books.update', 1, Input::all());
$this->assertRedirectedTo('books/1');
}
The issue is, when I do it like this, I get Mockery\Exception\InvalidCountException: Method update() from Mockery_0_Book should be called exactly 1 times but called 0 times. because of the $book->update($data) in my controller. If I were to change it to $this->books->update($data), it would be mocked properly and the database wouldn't be touched, but it would update all my records when using the function from frontend.
I guess I simply just want to know how to mock the $book-object properly.
Am I clear enough? Let me know otherwise. Thanks!
Try mocking out the findOrFail method not to return a new Book, but to return a mock object instead that has an update method on it.
$mockBook = Mockery::mock('Book[update]');
$mockBook->shouldReceive('update')->once();
$this->mock->shouldReceive('findOrFail')->once()->andReturn($mockBook);
If your database is a managed dependency and you use mock in your test it causes brittle tests.
Don't mock manage dependencies.
Manage dependencies: dependencies that you have full control over.

Categories