I want to get my model object directrly into my controller
I made an argument resolver to deserialise my incoming data to my model.
code looks like this
Controller :
public function updateAreaModes(UpdateAreaModes $updateAreaModes, int $id)
Argument resolver :
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $this->serializer->deserialize($request->getContent(), UpdateAreaModes::class, BaseResolver::JSON);
}
Request Content :
[{"type": "SomeType", "value": "someValue"}]
UpdateAreaModes:
(the model i need in my controller)
class UpdateAreaModes
{
/** #var array<ModeDTO> */
public array $modes;
}
ModeDTO:
class ModeDTO
{
public string $type;
public string $value;
}
All i get in the controller is an empty UpdateAreaModes object :(
What am I doing wrong ?
I found an answer, not the one I was looking for but it works I guess
yield $this->serializer->deserialize(
$request->getContent(),
ModeDTO::class . '[]',
BaseResolver::JSON
);
Related
I have the following routes in routes/api.php:
Route::get('items/{item}', function(Guid $item) {...});
Route::get('users/{user}', function(Guid $user) {...});
Since Guid is a custom type, how can I resolve that via dependency injection? As shown, the route parameter {item} differs from the callback parameter type-hint:Guid so it can not be automatically resolved.
That's what I've tried in app/Providers/AppServiceProvider.php:
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(Guid::class, function(Application $app, array $params) {
return Guid::fromString($params[0]);
});
}
}
I'd expect $params to be something like this: [ 'item' => 'guid' ] -- but it is: [].
You can make use of explicit binding Laravel Routing:
in RouteServiceProvider::boot():
public function boot()
{
Route::model('item', Guid $item);
Route::model('user', Guid $user);
}
If Guid is not a model use a Closure to map onto the string:
Route::bind('user', function ($value) {
return Guid::fromString($value);
});
UPDATED
And I found another way, much better - implement UrlRoutable contract Lavaravel API:
<?php
namespace App\Models;
use Illuminate\Contracts\Routing\UrlRoutable;
class Guid implements UrlRoutable
{
private string $guid;
public function setGuid(string $guid)
{
$this->guid = $guid;
return $this;
}
public function getGuid(): string
{
return $this->guid;
}
public static function fromString(string $guid): self
{
//you cannot set props from constructor in this case
//because binder make new object of this class
//or you can resolve constructor depts with "give" construction in ServiceProvider
return (new self)->setGuid($guid);
}
public function getRouteKey()
{
return $this->guid;
}
public function getRouteKeyName()
{
return 'guid';
}
public function resolveRouteBinding($value, $field = null)
{
//for using another "fields" check documentation
//and maybe another resolving logic
return self::fromString($value);
}
public function resolveChildRouteBinding($childType, $value, $field)
{
//or maybe you have relations
return null;
}
}
And, with this, you can use routes like you want as Guid now implements UrlRoutable and can turn {item} (or whatever) URL-path sub-string markers into Guids per dependency injection (by the type-hint as you asked for it):
Route::get('items/{item}', function(Guid $item) {
return $item->getGuid();
});
BTW: NEVER EVER use closures in routes as you cannot cache closure routes - and routes are good to be optimized, and caching helps with that in Laravel routing.
simple helper to utilize route binding callback.
if (!function_exists('resolve_bind')) {
function resolve_bind(string $key, mixed $value) {
return call_user_func(Route::getBindingCallback($key), $value);
}
}
usage
resolve_bind('key', 'value');
I am currently working on a HMS project (Hotel Management System). I am currently stucked in implementing a DTO based functionality in Symfony 5. The following from below is my HotelMapper.php file where I would like to build a method which transforms the Array of Hotels into an Array of DTOs so I can pass them in the Hotel Controller later on and for this I would like to use the objects from the dtoToHotel() function. I already created the DTO (setters & getters).
namespace App\Transformer;
use App\DTO\HotelDTO;
use App\Entity\HotelEntity;
class HotelMapper
{
public $hotel;
public function dtoToHotel(HotelDTO $hotelDTO, Hotel $hotel) : HotelEntity
{
$hotel->setId($hotelDTO->getId());
$hotel->setName($hotelDTO->getName());
$hotel->setLocation($hotelDTO->getLocation());
$hotel->setEmployees($hotelDTO->getEmployees());
$hotel->setAvailability($hotelDTO->getAvailability());
$hotel->setFacility($hotelDTO->getFacility());
$hotel->setPhoto($hotelDTO->getPhoto());
$hotel->setDescription($hotelDTO->getDescription());
$hotel->setEarnings($hotelDTO->getEarnings());
}
public function hotelToDto(HotelEntity $hotel)
{
return HotelDTO(
$hotel->getId(),
$hotel->getName(),
$hotel->getLocation(),
$hotel->getEmployees(),
$hotel->getAvailability(),
$hotel->getFacility(),
$hotel->getPhoto(),
$hotel->getDescription(),
$hotel->getEarnings()
);
}
public function transformHotelsArrayToDTO()
{
/* Code here */
}
}
The code from below is my HotelController where I would like to update the following line $hotels = $this->hotelRepository->findAll() inside the showAllHotels() function by passing the DTO in here. Any help is much appreciated!
class HotelController extends AbstractController
{
/**
* #var HotelRepository
*/
public $hotelRepository;
public function __construct(HotelRepository $hotelRepository)
{
$this->hotelRepository = $hotelRepository;
}
/**
* #Route (path="/", methods={"GET"})
*/
public function index(): Response
{
return $this->render('index/index.html.twig');
}
/**
* #Route (path="/hotel-management", methods={"GET"})
*/
// It does populate the table with the hotels from the DB
public function showAllHotels(): Response
{
$hotels = $this->hotelRepository->findAll();
return $this->render('hotel-management/hotel-management.html.twig', array('hotels' => $hotels));
}
}
Inject HotelMapper transformer to your controller and then pass findAll's result to this function:
public function transformHotelsArrayToDTO(array $items): array
{
if (empty($items) or is_null($items)) {
return [];
}
return array_map([$this, 'hotelToDto'], $items);
}
In this Symfony route
/**
* #Route("/board/{board}/card/{card}", name="card_show", methods={"GET"}, options={})
*/
public function show(Board $board, Card $card): Response
{
$card->getLane()->getBoard(); // Board instance
// ...
}
How is it possible to add the {board} parameter programatically, since it is already available in {card}? Now, I always need to add two parameters, when generating links to show action.
After some research I've found the RoutingAutoBundle (https://symfony.com/doc/master/cmf/bundles/routing_auto/introduction.html#usage) which would provide the functions I need, but it's not available for Symfony 5 anymore.
Thanks.
Okay, after some investigation I've found this question
Which lead me to this helpful answer.
My controller action (with #Route annotation) looks like this:
/**
* #Route("/board/{board}/card/{card}", name="card_show", methods={"GET"})
*/
public function show(Card $card): Response
{
}
We just have one argument ($card) in method signature, but two arguments in route.
This is how to call the route in twig:
path("card_show", {card: card.id})
No board parameter required, thanks to a custom router.
This is how the custom router looks like:
<?php // src/Routing/CustomCardRouter.php
namespace App\Routing;
use App\Repository\CardRepository;
use Symfony\Component\Routing\RouterInterface;
class CustomCardRouter implements RouterInterface
{
private $router;
private $cardRepository;
public function __construct(RouterInterface $router, CardRepository $cardRepository)
{
$this->router = $router;
$this->cardRepository = $cardRepository;
}
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
if ($name === 'card_show') {
$card = $this->cardRepository->findOneBy(['id' => $parameters['card']]);
if ($card) {
$parameters['board'] = $card->getLane()->getBoard()->getId();
}
}
return $this->router->generate($name, $parameters, $referenceType);
}
public function setContext(\Symfony\Component\Routing\RequestContext $context)
{
$this->router->setContext($context);
}
public function getContext()
{
return $this->router->getContext();
}
public function getRouteCollection()
{
return $this->router->getRouteCollection();
}
public function match($pathinfo)
{
return $this->router->match($pathinfo);
}
}
Now, the missing parameter board is provided programatically, by injecting and using the card repository. To enable the custom router, you need to register it in your services.yaml:
App\Routing\CustomCardRouter:
decorates: 'router'
arguments: ['#App\Routing\CustomCardRouter.inner']
Currently I'm stuck in making an accessor. I'm trying to access some values from the nested relationship after when I got that I'm returning the value and appending it to the model, but the problem is inside my response I'm getting values of the relationship which I try to access in my accessor.
public function getTranslatorEmailAttribute()
{
if (in_array(AddOnConfirmation::EMAIL, $this->customer->department->company->add_on_confirmation)) {
return $this->assignedTranslator()->first()->email;
} else {
return null;
}
}
Here is the customer relation which I'm trying to use
public function customer()
{
return $this->belongsTo(User::class)->with('customerData.customerType', 'customerData.department.company');
}
How can I fix this?
Here is a screenshot of response I'm getting with using accessor
Your GET route that handles api/bookings/{id} should return the resource in the end, something like:
return BookingResource::make($booking);
Then create BookingResource and likely put it in namespace App\Http\Resources\Api;
The file itself can look like:
namespace App\Http\Resources\Api;
use Illuminate\Http\Resources\Json\Resource;
/** #mixin \App\Models\Booking */
class BookingResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
$append = $request->get('append');
return [
// Here you'd put everything you want to show
'id' => $this->id,
'translator_email' => $append == 'translator_email' ? $this->translator_email : null,
// ...
];
}
}
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