Laravel Dingo nested transformers - laravel

I'm trying to get one to many relationship objects with transformers. I want to get include metas but i only get just regular transform fields.
my transformer:
class AssistantTransformer extends TransformerAbstract
{
protected $availableIncludes = [
'assistantmetas'
];
public function transform(User $user)
{
return [
'id' => (int) $user->id,
'firstname' => ucfirst($user->first_name),
'lastname' => ucfirst($user->last_name),
];
}
public function includeMetas(User $user)
{
$assistantmetas = $user->userMetas;
return $this->item($assistantmetas, new AssistantsMetaTransformer);
}
}

Just use defaultIncludes not available includes, because it needs to send request via url? include=assistantmetas to get result like this.

Related

Merge FormRequest rules

This question is based on this thread: Merge 2 rules FormFequest for validate an update and store action in laravel5.5
Context: Let's suppose I have these 2 requests and I want to merge the SocialMediaFormRequest rules in ReadersFormRequest rules.
ReadersFormRequest
class ReadersFormRequest extends FormRequest
{
public function rules(SocialMediaFormRequest $social)
{
$mediaRules = $social->rules();
$rules = [
'first_name'=>'required',
'last_name'=>'required',
'birthday'=>'required',
'region'=>'required',
'photo_url'=>'required',
'support'=>'required',
'riwayas_id'=>'required',
'description'=>'required',
];
return array_merge($rules,$mediaRules);
}
}
SocialMediaFormRequest
class SocialMediaFormRequest extends FormRequest
{
public function rules()
{
return [
'url'=>'required|url',
'title'=>'required'
];
}
}
Form that I received
first_name: "example"
last_name: "example"
birthday: 2022-06-13
region: somewhere
photo_url: "https:XXX"
support: false
riwayas_id: 1
description: ""
media.url: "https:YYY"
media.title: "stackoverflow"
Question: How can I only pass the argument media.XXX in my form SocialMediaFormRequest?
You can use prepareForValidation() method in the form request to sanitize the inputs : https://laravel.com/docs/9.x/validation#preparing-input-for-validation
So, if in SocialMediaFormRequest you receive the full request you can only get the required fields like that:
public function prepareForValidation()
{
$this->replace([
'url' => $this->url ?? ($this->media['url'] ?? null),
'title' => $this->title ?? ($this->media['title'] ?? null),
]);
}
Also, in ReadersFormRequest when you inject the other request or resolve it from the container it doesn't work correctly, so it is better to get the rules like that:
public function rules()
{
$mediaRules = (new SocialMediaFormRequest())->rules();
and in order to access the media.* attributes in ReadersFormRequest you can again use prepareForValidation:
public function prepareForValidation()
{
$this->merge([
'url' => $this->media['url'] ?? null,
'title' => $this->media['title'] ?? null,
]);
}

Laravel / OctoberCMS frontend filter

I am using OctoberCMS and I have created a custom component. I am trying to create a frontend filter to filter Packages by the Tour they are assigned to.
This is what I have so far. The issue is that the code is looking for a tour field within the packages table rather than using the tour relationship. Does anyone have any ideas?
<?php namespace Jakefeeley\Sghsportingevents\Components;
use Cms\Classes\ComponentBase;
use JakeFeeley\SghSportingEvents\Models\Package;
use Illuminate\Support\Facades\Input;
class FilterPackages extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Filter Packages',
'description' => 'Displays filters for packages'
];
}
public function onRun() {
$this->packages = $this->filterPackages();
}
protected function filterPackages() {
$tour = Input::get('tour');
$query = Package::all();
if($tour){
$query = Package::where('tour', '=', $tour)->get();
}
return $query;
}
public $packages;
}
I really appreciate any help you can provide.
Try to query the relationship when the filter input is provided.
This is one way to do it;
public $packages;
protected $tourCode;
public function init()
{
$this->tourCode = trim(post('tour', '')); // or input()
$this->packages = $this->loadPackages();
}
private function loadPackages()
{
$query = PackagesModel::query();
// Run your query only when the input 'tour' is present.
// This assumes the 'tours' db table has a column named 'code'
$query->when(!empty($this->tourCode), function ($q){
return $q->whereHas('tour', function ($qq) {
$qq->whereCode($this->tourCode);
});
});
return $query->get();
}
If you need to support pagination, sorting and any additional filters you can just add their properties like above. e.g;
protected $sortOrder;
public function defineProperties(): array
{
return [
'sortOrder' => [
'title' => 'Sort by',
'type' => 'dropdown',
'default' => 'id asc',
'options' => [...], // allowed sorting options
],
];
}
public function init()
{
$filters = (array) post();
$this->tourCode = isset($filters['tour']) ? trim($filters['tour']) : '';
$this->sortOrder = isset($filters['sortOrder']) ? $filters['sortOrder'] : $this->property('sortOrder');
$this->packages = $this->loadPackages();
}
If you have a more complex situation like ajax filter forms or dynamic partials then you can organize it in a way to load the records on demand vs on every request.e.g;
public function onRun()
{
$this->packages = $this->loadPackages();
}
public function onFilter()
{
if (request()->ajax()) {
try {
return [
"#target-container" => $this->renderPartial("#packages",
[
'packages' => $this->loadPackages()
]
),
];
} catch (Exception $ex) {
throw $ex;
}
}
return false;
}
// call component-name::onFilter from your partials..
You are looking for the whereHas method. You can find about here in the docs. I am not sure what your input is getting. This will also return a collection and not singular record. Use ->first() instead of ->get() if you are only expecting one result.
$package = Package::whereHas('tour', function ($query) {
$query->where('id', $tour);
})->get();

Is there a way to add filters to custom endpoint with DTO?

I have a custom endpoint (which does some custom aggregations), the return of this endpoint is a collection of DTO. I want to add some filters sugestions for the consumers of my api. Is this possible ? How can you do that ?
To sum up :
I have a DTO (ApiResource but not linked to doctrine or a database).
I have a custom GET endpoint that return a collection of DTO (filtered or not).
I want to add filters sugestion to this endpoint.
Should i modify the hydra:search somehow ?
I tried to add ApiFilters (like i do for the entites) on my DTO but ApiFilters are linked to doctrine so it gives me the following error : Call to a member function getClassMetadata() on null on vendor/api-platform/core/src/Bridge/Doctrine/Common/PropertyHelperTrait.php
I encountered the same issue, to solve it I create a custom filter without the $this->isPropertyMapped part.
I injected the ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterExtension to my collection provider and apply my filters with
$this->filterExtension->applyToCollection($qb, $queryNameGenerator, $resourceClass, $operationName, $context); in order to alter the query.
Then I just need to configure my custom filter in my dto object
#ApiFilter(SearchFilter::class, properties={"columnName": "exact"})
<?php
declare(strict_types=1);
namespace App\ThirdParty\ApiPlatform\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
final class SearchFilter extends AbstractContextAwareFilter
{
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
string $operationName = null
): void {
if (
!$this->isPropertyEnabled($property, $resourceClass)
) {
return;
}
$parameterName = $queryNameGenerator->generateParameterName($property);
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder
->andWhere(sprintf('%s.%s = :%s', $rootAlias, $property, $parameterName))
->setParameter($parameterName, $value);
}
public function getDescription(string $resourceClass): array
{
if (!$this->properties) {
return [];
}
$description = [];
foreach ($this->properties as $property => $strategy) {
$description["regexp_$property"] = [
'property' => $property,
'type' => 'string',
'required' => false,
'swagger' => [
'description' => 'description',
'name' => $property,
'type' => 'type',
],
];
}
return $description;
}
}

Put other function on FormRequest in Laravel

I'm building a Laravel 6 application, and I am concerned about "best practices." I have one controller named CustomerController. In my controller, I want to update the Customer model, so I will have a function like the following.
public function update(UpdateCustomer $request, Customer $customer){
//
}
UpdateCustomer is my form request and where I will do the validation. In my update() method, I have classic validation.
public function rules()
{
$validationArray = [];
$validationArray['customer.name'] = 'string|required';
$validationArray['customer.vat'] = 'string|required';
$validationArray['customer.email'] = 'email|required';
return $validationArray;
}
Now I have to do some particular validation other the classic.
Let's assume that I have more data in my model, and I don't want these values to be changed.
For example, I have the following: address, cap, locality. I have a second method on the UpdateCustomer request that I can validate.
public function validateForDataCantChange()
{
$data = $this->input("customer");
$customer = $this->route("customerID");
$validator = Validator::make([], []); // Empty data and rules fields
$arrayDataThatCantChange = [
'address' => $data['address'] ?? NULL,
'cap' => $data['cap'] ?? NULL,
'locality' => $data['locality'] ?? NULL
];
foreach ($arrayDataThatCantChange as $key => $v) {
if ($customer->{$key} !== $v) {
$validator->errors()->add($key, __("messages.the field :field can't be changed", ['field' => $key]));
}
}
if ($validator->errors()->any()) {
throw new ValidationException($validator);
}
}
And then in my controller, I've added the following.
public function update(UpdateCustomer $request, Customer $customer){
$request->validateForDataCantChange();
}
Is this a bad practice? Should I create a new FormRequest? How, in this case (two form requests), can I use two different requests for a single controller?
For the little effort required, I'd personally create a new form request.
If you wish to use the same form request you can do the following:
public function rules()
{
$rules = [
'title' => 'required:unique:posts'
];
// when editing i.e. /posts/2/edit
if ($id = $this->segment(2)) {
$rules['title'] .= ",$id";
}
return $rules;
}
However, I always use a separate class for each action.

Laravel - request validation

I extedned request class to create my own valdiation rules. In that class I added my custom validation function. In function I check if tags are pass regEx and I would like to filter tags to remove tags shorter then 2 characters.
And later keep in request only tags that passed validation.
public function createPost(PostRequest $request)
{
dd($request->all()); //In this place I would like to keep only tags passed through validation not all tags recived in request
}
Is it possibile to do it? How to set it in Request class?
'tags' => [
'nullable',
'string',
function ($attribute, $value, $fail){
$tagsArray = explode(',', $value);
if(count($tagsArray) > 5) {
$fail(__('place.tags_max_limit'));
}
$tagsFiltered = [];
foreach ($tagsArray as $tag){
$tag = trim($tag);
if(preg_match('/^[a-zA-Z]+$/',$tag)){
$tagsFiltered[] = $tag;
};
}
return $tagsFiltered;
}
],
EDIT:
I think we miss understanding. I would like to after validation have only tags that returned in variable $tagsFiltered; Not the same as recived in input.
You have to create this custom regex rule and use it into rules() function.
Like so:
public function rules()
{
return [
'tag' => 'regex:/[^]{2,}/'
];
}
public function createPost(PostRequest $request)
{
$request->validated();
}
And then just call it via validated() function wherever you want.
first define validation rule with this command:
php artisan make:rule TagsFilter
navigate to TagsFilter rule file and define your filter on passes method:
public function passes($attribute, $value)
{
$tagsArray = explode(',', $value);
$tagsFiltered = [];
foreach ($tagsArray as $tag){
$tag = trim($tag);
if(preg_match('/^[a-zA-Z]+$/',$tag)){
$tagsFiltered[] = $tag;
};
}
return count($tagsArray) > 5 && count($tagsFiltered) > 0;
}
then include your rule in your validation on controller:
$request->validate([
'tags' => ['required', new TagsFilter],
]);

Resources