I'm building livewire components that shares 50% of public properties and almost 90% of submit function logic.
each component using this trait has its own rules according to its html-form fields. and also each component perform some custom logic after validating the data. other that that they are all the same.
<?php
namespace App\Traits;
trait ParentServiceComponent
{
public $desc = '';
public function rules()
{
return [
'desc' => 'required|max:2000'
];
}
public abstract function componentCustomLogic(array $data);
public function submit()
{
$data = $this->validate();
$performCusomLogic = $this->componentCustomLogic($data);
// save to db and show success message
}
}
here an example of two components that uses this trait.
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Traits\ParentServiceComponent;
class RequestService extends Component
{
public $type = '';
use ParentServiceComponent { rules as traitRules; }
public function rules()
{
return array_merge($this->traitRules, [
'type' => 'required|max:200'
]);
}
public function componentCustomLogic(array $data)
{
// do the logic of this component here
}
public function render()
{
return view('livewire.request-service');
}
}
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Traits\ParentServiceComponent;
class ReplyService extends Component
{
public $body = '';
use ParentServiceComponent { rules as traitRules; }
public function rules()
{
return array_merge($this->traitRules, [
'body' => 'required|max:200'
]);
}
public function componentCustomLogic(array $data)
{
// do the logic of this component here
}
public function render()
{
return view('livewire.reply-service');
}
}
so my question is: am I doing it right?
Related
I do not understand how to properly connect the model and filter. I implemented this using Dependency Injection in the controller, but I don’t like the fact that it needs to be done in every method where filtering should be applied. It would be very convenient if the model itself understood which class with filters to use.
Tell me how to do better.
Filtration Class:
namespace App\Classes\Filter;
class QueryFilter
{
protected $query;
protected $params;
public function apply($query, $params)
{
$this->query = $query;
$this->params = $params;
foreach ($this->filters() as $filter => $value){
if(method_exists($this, $filter)){
$this->$filter($value);
}
}
return $this->query;
}
public function filters()
{
return $this->params;
}
}
Heirs implement filters for different models:
namespace App\Classes\Filter;
class PositionFilter extends QueryFilter
{
public function title($value)
{
$this->query->where('title', 'LIKE', "%$value%");
}
}
class GasStationFilter extends QueryFilter
{
public function number($value)
{
$this->query->where('number', 'LIKE', "%$value%");
}
public function region($value)
{
$this->query->whereHas('region', function ($query) use ($value){
$query->where('regions.id', $value);
});
}
}
In the controller, I inject the desired class with filters and apply filtering like this (I use scope in the model):
public function index(GasStationIndexRequest $request, GasStationFilter $filters)
{
$gasStations = GasStation::with('region')
->filter($request->validated(), $filters)
->take(10)
->get();
return GasStationSelect2Resource::collection($gasStations);
}
Model:
namespace App\Models;
class GasStation extends ListModel
{
public function region(): BelongsTo
{
return $this->belongsTo(Region::class);
}
public function scopeFilter($query, $params, $filters) : Builder
{
return $filters->apply($query, $params);
}
}
I'm trying to make an api that have lists and inside each list there is anther list inside of it called cards and the cards list is the cards of this list.
I tried to show it in index function and didn't work it was like this:
public function index()
{
// $list = List -> cards();
$list = List::cards();
return response( $list );
}
Card Model:
public function list()
{
return $this->belongsTo( List::class() );
}
Card Model:
public function cards()
{
return $this->hasMany( Card::class() );
}
What i want to output is json data like this:
"lists":[
'name':listname
'cards':[
'card one': card name,
]
]
If you use Laravel framework use Resource for response, in Resource of laravel you can load cards. For example in ListController :
public function index()
{
return ListResource::collection(List::all()->paginate());
}
And in ListResource :
public function toArray($request)
{
'cards' => CardResource::collection('cards');
}
belongsTo or hasMany accepts model name as a first argument. In your case you need to pass your model class name in your relations methods.
public function list()
{
return $this->belongsTo(List::class);
}
and
public function cards()
{
return $this->hasMany(Card::class);
}
So if you want to receive models including relations you can use with method.
return response(List::query()->with('cards'));
You can use resources.
Http\Resources\List:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class List extends JsonResource
{
public function toArray($request)
{
$cards = [];
foreach ($this->cards as $card) {
$cards[] = $card->name;
}
return [
'name' => $this->name,
'cards' => $cards,
];
}
}
Http\Controllers\ListController:
namespacce App\Http\Controllers;
use App\Http\Resources\List as ListResource;
use App\Components\List;
class ListController extends Controller
{
$lists = List::query()->get();
return ListResource::collection($lists)->response();
}
I have a standard controller, whose logic the same in other controllers. These are admin panel controllers.
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PageRequest;
use App\Page;
use App\Repositories\PageRepository;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class PageController extends Controller
{
private const REDIRECT_INDEX = 'PageController#index';
protected $model;
public function __construct()
{
$this->model = new PageRepository(new Page());
}
public function index(): View
{
$pages = $this->model->all();
return view('pages.index', compact('pages'));
}
public function create(): View
{
return view('pages.create');
}
public function store(PageRequest $request): RedirectResponse
{
(new Page($request->all()))->save();
return redirect()->action(self::REDIRECT_INDEX)->with('status', 'Created');
}
public function show(Page $page): View
{
return view('pages.show', compact('page'));
}
public function edit(Page $page): View
{
return view('pages.edit', compact('page'));
}
public function update(PageRequest $request, Page $page)
{
$page->fill($request->all())->save();
return redirect()->action(self::REDIRECT_INDEX)->with('status', 'Updated');
}
public function destroy(Page $page): RedirectResponse
{
$page->delete();
return redirect()->action(self::REDIRECT_INDEX)->with('status', 'Deleted');
}
}
In other controllers different only
const REDIRECT_INDEX
$model
vies - 'pages.index', 'pages.create' and etc.
PageRequest $request - request with validation
Page $page - auto finded row from db by slug
So I have PageController, NewsController, TabController, TypeController with the same logic. How can I abstract?
I created custom actions for rest api in yii2
my codes are:
namespace app\controllers;
use yii\rest\ActiveController;
use yii\web\Response;
use Yii;
class RsController extends ActiveController{
public $modelClass='app\models\Mymodel';
/*some another actions*/
public function actionOne($id){
return \app\models\Anothermodel::findAll(['my_id'=>$id]);
}
public function actionTwo($id){
return \app\models\Anothermodel::findAll(['my_name'=>'xxxx']);
}
}
I know we can override fields function in model to get special fields but
now I wanted to get different fields for actionOne and actionTwo (of a model)
How can I override fields function in Anothermodel for this purpose?
I found my answer from here
I create a component like this
<?php
namespace app\components;
class Serializer extends \yii\rest\Serializer {
public $defaultFields;
public $defaultExpand;
public function init() {
parent::init();
$this->defaultFields = !is_null($this->defaultFields) ? implode(",", $this->defaultFields) : $this->defaultFields;
$this->defaultExpand = !is_null($this->defaultExpand) ? implode(",", $this->defaultExpand) : $this->defaultExpand;
}
protected function getRequestedFields() {
$fields = is_null($this->request->get($this->fieldsParam)) ? $this->defaultFields : $this->request->get($this->fieldsParam);
$expand = is_null($this->request->get($this->expandParam)) ? $this->defaultExpand : $this->request->get($this->expandParam);
return [
preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY),
preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY),
];
}
}
and then in my controllers action set my fields
like this.
public function actionOne($id){
$this->serializer['defaultFields'] = ["field1",
"field2"];
return new \yii\data\ActiveDataProvider([
'query' => \app\models\Anothermodel::find()->where(['my_id'=>$id]),
]);
}
public function actionTwo($id){
$this->serializer['defaultFields'] = ["field1",
"field2","field3"];
return new \yii\data\ActiveDataProvider([
'query' => \app\models\Anothermodel::find()->where(['my_id'=>$id]),
]);
}
I suggest to use events
public function actionPublic()
{
\yii\base\Event::on(Thing::class, Thing::EVENT_AFTER_FIND, function ($event) {
$event->sender->scenario = Thing::SCENARIO_SEARCH_PUBLIC;
});
return new ActiveDataProvider([
'query' => Thing::find(),
]);
}
public function actionPrivate()
{
\yii\base\Event::on(Thing::class, Thing::EVENT_AFTER_FIND, function ($event) {
$event->sender->scenario = Thing::SCENARIO_SEARCH_PRIVATE;
});
return new ActiveDataProvider([
'query' => Thing::find(),
]);
}
and inside of ActiveRecord (Thing in my case) check the scenario in fields() method
public function fields()
{
$fields = parent::fields();
if ($this->scenario === self::SCENARIO_SEARCH_PUBLIC) {
unset($fields['field1'], $fields['field2'], $fields['field3'], $fields['field4']);
}
return $fields;
}
check my answer in gihub
In my controller, I have something like the following:
public function index()
{
$questions = Question::all();
return view('questions.index', compact('questions'));
}
However, I would like this route to also be used by my ajax requests. In which case, I'd like to return JSON. I'm considering the following:
public function index()
{
$questions = Question::all();
return $this->render('questions.index', compact('questions'));
}
public function render($params)
{
if ($this->isAjax()) {
return Response::json($params);
} else {
return view('questions.index')->with($params);
}
}
..by the way, I haven't tested any of this yet, but hopefully you get the idea.
However, I was wondering if I can alter the built in view(...) functionality itself to keep things even lighter. So I just keep the following:
public function index()
{
$questions = Question::all();
// this function will detect the request and deal with it
// e.g. if header X-Requested-With=XMLHttpRequest/ isAjax()
return view('questions.index', compact('questions'));
}
Is this possible?
You probably want to make custom response:
add ResponseServiceProvider.php
namespace App\Providers;
use Request;
use Response;
use View;
use Illuminate\Support\ServiceProvider;
class ResponseServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
Response::macro('smart', function($view, $data) {
if (Request::ajax()) {
return Response::json($data);
} else {
return View::make($view, $data);
}
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Add 'App\Providers\ResponseServiceProvider' to providers list in config/app.php:
'providers' => [
'App\Providers\ResponseMacroServiceProvider',
];
Use new helper in controller:
return Response::smart('questions.index', $data);
Simply check if the Request is an Ajax request in your index method itself.
public method index() {
$questions = Question::all();
if(\Request::ajax())
return \Response::json($questions);
else
return view('questions.index', compact('questions'));
}
Use Request::ajax(), or inject the request object:
use Illuminate\Http\Request;
class Controller {
public function index(Request $request)
{
$data = ['questions' => Question::all()];
if ($request->ajax()) {
return response()->json($data);
} else {
return view('questions.index')->with($data);
}
}
}
Your view should never know anything about the HTTP request/response.
I guess the simple method is just to put a method inside the parent Controller class:
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController {
...
protected function render($view, $data)
{
if (Request::ajax()) {
return Response::json($data);
} else {
return view($view, $data);
}
}
}
and then instead of doing view('questions.index, $data);, do $this->render('questions.index', $data);