How to add few condition to hasMany relationship? - activerecord

I have model
class Drug extends ActiveRecord
{
/**
* #return \yii\db\ActiveQuery
*/
public function getProblems()
{
return $this->hasMany(Problem::class, ['id' => 'problem_id'])
->via('consumptionRateProblems');
}
/**
* #return \yii\db\ActiveQuery
*/
public function getConsumptionRateProblems()
{
return $this->hasMany(ConsumptionRateProblem::class, ['consumption_rate_id' => 'id'])
->via('consumptionRates');
}
/**
* #return \yii\db\ActiveQuery
*/
public function getConsumptionRates()
{
return $this->hasMany(ConsumptionRate::class, ['drug_id' => 'id']);
}
}
I need to get Problems for some Drug, that connected via culture_id and drug_id in consumptionRates table.
When I use andOnCondition - i get error "Not unique table/alias: 'consumption_rate'"
$drugs = Drug::find()
->joinWith(['consumptionRates' => function (ActiveQuery $query) use ($cultureId) {
return $query->andOnCondition(['consumption_rate.culture_id' => $cultureId]);
}])
->all();
How should I build my query for getting a result I need ?

Thanks to Serghiy Leonenco!
$drugs = Drug::find()
->joinWith(['consumptionRates'])->andWhere(['consumption_rate.culture_id' => $cultureId])
->all();

Related

Laravel separate functions to service class

I am refactoring my application according to this article:
https://laravel-news.com/controller-refactor
I had all logic in my controllers so it seems like a good idea to do this. But now I have some struggles with the update function.
class CategoryController extends Controller
{
/**
* Display a listing of the resource.
*
* #param Request $request
* #return JsonResponse
*/
public function index(Request $request): JsonResponse
{
$categories = Category::where('created_by', $request->company->id)->orderBy('order')->get();
return response()->json($categories);
}
/**
* Store a newly created category
*
* #param StoreCategoryRequest $request
* #param CategoryService $categoryService
* #return JsonResponse
*/
public function create(StoreCategoryRequest $request, CategoryService $categoryService): JsonResponse
{
$category = $categoryService->createCategory($request);
if ($category) {
return response()->json(['success' => true, 'message' => 'api.category.save.success']);
}
return response()->json(['success' => false, 'message' => 'api.category.save.failed']);
}
/**
* Update the specified resource in storage.
*
* #param StoreCategoryRequest $request
* #param Category $category
* #param CategoryService $categoryService
* #return JsonResponse
*/
public function update(StoreCategoryRequest $request, Category $category, CategoryService $categoryService): JsonResponse
{
try {
$result = $categoryService->updateCategory($request, $category);
if ($result) {
return response()->json(['success' => true, 'message' => 'api.category.update.success']);
}
return response()->json(['success' => false, 'message' => 'api.category.update.failed']);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => 'api.category.update.failed']);
}
}
}
And the route:
Route::put('category/{category}', [CategoryController::class, 'update']);
Laravel is getting the category based on the id, but I don't know how to handle this correctly in my controller. I autoload the CategoryService there, so that I can use the update function. After that I give the actual category as a property to that service, I also don't know if handling the exceptions like this is the 'best way'.
class CategoryService
{
public function createCategory(Request $request): bool {
$category = new Category();
$category->fill($request->all());
$category->created_by = $request->company->id;
return $category->save();
}
/**
* #throws \Exception
*/
public function updateCategory(Request $request, Category $category): bool {
if($this->isOwnerOfCategory($category, $request->company)) {
$category->fill($request->all());
$category->created_by = $request->company->id;
return $category->save();
}
throw new \Exception('Not the owner of the category');
}
private function isOwnerOfCategory(Category $category, Company $company): bool
{
return $category->created_by === $company->id;
}
}
The create function/ flow feels good. But the update function feels like properties are coming from everywhere and the code is a lot less readable. Are there any suggestions to improve this?

Laravel 5.6 - Eloquent relation create fail (type error)

This is the error I am getting the at moment:
Type error: Argument 1 passed to Illuminate\Database\Eloquent\Relations\BelongsToMany::save()
must be an instance of Illuminate\Database\Eloquent\Model,
integer given,
called in /home/sasha/Documents/OffProjects/vetnearme/vetnearme/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php on line 814
The create user method, where I call the giveRole() method:
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
// On registration user will be given the default role of user
$user->giveRole();
$verifyUser = VerifyUser::create([
'user_id' => $user->id,
'token' => str_random(40)
]);
Mail::to($user->email)->send(new VerifyMail($user));
return $user;
}
HasPermissionsTrait:
<?php
namespace App\App\Permissions;
use App\{Role, Permission};
/**
*
*/
trait HasPermissionsTrait
{
public function giveRole($role = 'user')
{
$role = \DB::table('roles')->where('name', '=', $role)->first();
$this->roles()->saveMany([$role->id]);
return $this;
}
public function givePermission(...$permissions)
{
$permissions = $this->getPermissions(\array_flatten($permissions));
if($permissions === null)
return $this;
$this->permissions()->saveMany($permissions);
return $this;
}
public function widrawPermission(...$permissions)
{
$permissions = $this->getPermissions(\array_flatten($permissions));
$this->permissions()->detach($permissions);
return $this;
}
public function updatePermissions(...$permissions)
{
$this->permissions()->detach();
return $this->givePermission($permissions);
}
public function hasRole(...$roles)
{
foreach ($roles as $role) {
if($this->roles->contains('name', $role))
return true;
}
return false;
}
public function hasPermissionTo($permission)
{
return $this->hasPermissionThroughRole($permission) || $this->hasPermission($permission);
}
protected function hasPermission($permission)
{
return (bool) $this->permissions->where('name', $permission->name)->count();
}
protected function hasPermissionThroughRole($permission)
{
foreach ($permission->roles as $role) {
if($this->role->contains($role))
return true;
}
return false;
}
protected function getPermissions(array $permissions)
{
return Permissions::whereIn('name', $permissions)->get();
}
public function roles()
{
return $this->belongsToMany(Role::class, 'users_roles', 'user_id', 'role_id');
}
public function permissions()
{
return $this->belongsToMany(Permissions::class, 'users_permissions');
}
}
Role model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function permissions()
{
return $this->belongsToMany(Permissions::class, 'roles_permissions');
}
}
User model:
namespace App;
use App\App\Permissions\HasPermissionsTrait;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable, HasPermissionsTrait;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function clinic()
{
return $this->hasOne(Clinic::class, 'owner_id');
}
public function files()
{
return $this->hasMany('App/Media');
}
public function verifyUser()
{
return $this->hasOne('App\VerifyUser');
}
}
What am I doing wrong here?
Have you tried passing in the role model instead of the id? Also, on a separate note, it looks as if you might as well just call save as you are not actually ever utilizing an array in this instance.
trait HasPermissionsTrait
{
public function giveRole($role = 'user')
{
$role = \DB::table('roles')->where('name', '=', $role)->first();
$this->roles()->saveMany([$role]);
return $this;
}
}
saveMany calls save:
public function saveMany($models, array $joinings = [])
{
foreach ($models as $key => $model) {
$this->save($model, (array) Arr::get($joinings, $key), false);
}
$this->touchIfTouching();
return $models;
}
and save has typecasted Model, not int:
/**
* Save a new model and attach it to the parent model.
*
* #param \Illuminate\Database\Eloquent\Model $model
* #param array $joining
* #param bool $touch
* #return \Illuminate\Database\Eloquent\Model
*/
public function save(Model $model, array $joining = [], $touch = true)
{
$model->save(['touch' => false]);
$this->attach($model->getKey(), $joining, $touch);
return $model;
}

yii2 get activerecords by relation column

I need to select ActiveRecord's that have related AR's with specific column value.
Situation: 'User' may have many 'Branches' - via junction table, and Branch is related to Department. I have department_id, and I want to select Users, that have branches from this single Department.
Department:
... $this->hasMany(Branch::className(), ['department_id' => 'id']);
Branch:
... $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('{{%user_to_branch}}',['branch_id' => 'id']);
The thing is, that I do not want to access this from Department in any way (e.g. $department->getUsers()....), but i want to define this in ActiveQuery.
So i could select Users like:
User::find()->fromDepartment(5)->all();
THANK YOU in advance !
In ActiveRecord:
/**
* #inheritdoc
* #return MyActiveRecordModelQuery the active query used by this AR class.
*/
public static function find()
{
return new MyActiveRecordModelQuery(get_called_class());
}
MyActiveRecordModelQuery:
/**
* #method MyActiveRecordModelQuery one($db = null)
* #method MyActiveRecordModelQuery[] all($db = null)
*/
class MyActiveRecordModelQuery extends ActiveQuery
{
/**
* #return $this
*/
public function fromDepartment($id)
{
$this->andWhere(['departament_id' => $id]); //or use relation
return $this;
}
}
Usage:
MyActiveRecordModelQuery::find()->fromDepartment(5)->all();
User model method
public function getBranch()
{
return $this->hasMany(Branch::className(), ['id' => 'branch_id'])
->viaTable('{{%user_to_branch}}', ['user_id' => 'id']);
}
public static function fromDepartment($id)
{
$query = self::find();
$query->joinWith(['branch'])
->andWhere(['department_id'=>$id]);
return $query->all();
}
Usage:
User::fromDepartment(5);

Laravel 5 FormRequest validator with multiple scenarios

I would like to ask how should I handle validation on multiple scenarios using FormRequest in L5? I know and I was told that I can create saparate FormRequest files to handle different validations but it is very redundant and also noted that I would need to inject it into the controller manually using the use FormRequest; keyword. What did previously in L4.2 is that I can define a new function inside my customValidator.php which then being called during controller validation via trycatch and then the data is being validated by service using the below implementation.
class somethingFormValidator extends \Core\Validators\LaravelValidator
{
protected $rules = array(
'title' => 'required',
'fullname' => 'required',
// and many more
);
public function scenario($scene)
{
switch ($scene) {
case 'update':
$this->rules = array(
'title' => 'required',
'fullname' => 'required',
// and other update validated inputs
break;
}
return $this;
}
}
Which then in my LaravelValidator.php
<?php namespace Core\Validators;
use Validator;
abstract class LaravelValidator {
/**
* Validator
*
* #var \Illuminate\Validation\Factory
*/
protected $validator;
/**
* Validation data key => value array
*
* #var Array
*/
protected $data = array();
/**
* Validation errors
*
* #var Array
*/
protected $errors = array();
/**
* Validation rules
*
* #var Array
*/
protected $rules = array();
/**
* Custom validation messages
*
* #var Array
*/
protected $messages = array();
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
/**
* Set data to validate
*
* #return \Services\Validations\AbstractLaravelValidator
*/
public function with(array $data)
{
$this->data = $data;
return $this;
}
/**
* Validation passes or fails
*
* #return Boolean
*/
public function passes()
{
$validator = Validator::make(
$this->data,
$this->rules,
$this->messages
);
if ($validator->fails())
{
$this->errors = $validator->messages();
return false;
}
return true;
}
/**
* Return errors, if any
*
* #return array
*/
public function errors()
{
return $this->errors;
}
}
and then finally this is how i call the scenarios inside services like this
public function __construct(somethingFormValidator $v)
{
$this->v = $v;
}
public function updateSomething($array)
{
if($this->v->scenario('update')->with($array)->passes())
{
//do something
else
{
throw new ValidationFailedException(
'Validation Fail',
null,
$this->v->errors()
);
}
}
So the problem is now since i have migrated to L5 and L5 uses FormRequest, how should I use scenario validation in my codes?
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class ResetpasswordRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'login_email' => 'required',
'g-recaptcha-response' => 'required|captcha',
];
}
public function messages()
{
return [
'login_email.required' => 'Email cannot be blank',
'g-recaptcha-response.required' => 'Are you a robot?',
'g-recaptcha-response.captcha' => 'Captcha session timeout'
];
}
public function scenario($scene)
{
switch ($scene) {
case 'scene1':
$this->rules = array(
//scenario rules
);
break;
}
return $this;
}
}
also how should I call it in the controller?
public function postReset(ResetpasswordRequest $request)
{
$profile = ProfileService::getProfileByEmail(Request::input('login_email'));
if($profile == null)
{
$e = array('login_email' => 'This email address is not registered');
return redirect()->route('reset')->withInput()->withErrors($e);
}
else
{
//$hash = ProfileService::createResetHash($profile->profile_id);
$time = strtotime('now');
$ip = Determinator::getClientIP();
MailProcessor::sendResetEmail(array('email' => $profile->email,
'ip' => $ip, 'time' => $time,));
}
}
I believe the real issue at hand is everything is validated through the form request object before it reaches your controller and you were unable to set the appropriate validation rules.
The best solution I can come up with for that is to set the validation rules in the form request object's constructor. Unfortunately, I am not sure how or where you are able to come up with the $scene var as it seems to be hard-coded in your example as 'update'.
I did come up with this though. Hopefully reading my comments in the constructor will help further.
namespace App\Http\Requests;
use App\Http\Requests\Request;
class TestFormRequest extends Request
{
protected $rules = [
'title' => 'required',
'fullname' => 'required',
// and many more
];
public function __construct()
{
call_user_func_array(array($this, 'parent::__construct'), func_get_args());
// Not sure how to come up with the scenario. It would be easiest to add/set a hidden form field
// and set it to 'scene1' etc...
$this->scenario($this->get('scenario'));
// Could also inspect the route to set the correct scenario if that would be helpful?
// $this->route()->getUri();
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
public function scenario($scene)
{
switch ($scene) {
case 'scene1':
$this->rules = [
//scenario rules
];
break;
}
}
}
You can use laratalks/validator package for validation with multiple scenarios in laravel. see this repo

laravel 4 - inserting of multiple fields in array

The following function in laravel stores my form input. I can't get it to store anything other than the author id and the title. It just won't store the keywords.
Below is the function in my Postcontroller.php
public function store()
{
$input = Input::all();
$rules = array(
'title' => 'required',
'text' => 'required',
);
$validation = Validator::make($input, $rules);
if ($validation->fails()) {
return Redirect::back()->withErrors($validation)->withInput();
} else {
// create new Post instance
$post = Post::create(array(
'title' => $input['title'],
'keywords' => $input['keywords'],
));
// create Text instance w/ text body
$text = Text::create(array('text' => $input['text']));
// save new Text and associate w/ new post
$post->text()->save($text);
if (isset($input['tags'])) {
foreach ($input['tags'] as $tagId) {
$tag = Tag::find($tagId);
$post->tags()->save($tag);
}
}
// associate the post with user
$post->author()->associate(Auth::user())->save();
return Redirect::to('question/'.$post->id);
}
}
Post.php (model)
<?php
class Post extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'posts';
/**
* Whitelisted model properties for mass assignment.
*
* #var array
*/
protected $fillable = array('title');
/**
* Defines a one-to-one relationship.
*
* #see http://laravel.com/docs/eloquent#one-to-one
*/
public function text()
{
return $this->hasOne('Text');
}
/**
* Defines an inverse one-to-many relationship.
*
* #see http://laravel.com/docs/eloquent#one-to-many
*/
public function author()
{
return $this->belongsTo('User', 'author_id');
}
/**
* Defines a many-to-many relationship.
*
* #see http://laravel.com/docs/eloquent#many-to-many
*/
public function tags()
{
return $this->belongsToMany('Tag');
}
/**
* Defines an inverse one-to-many relationship.
*
* #see http://laravel.com/docs/eloquent#one-to-many
*/
public function category()
{
return $this->belongsTo('Category');
}
/**
* Defines a polymorphic one-to-one relationship.
*
* #see http://laravel.com/docs/eloquent#polymorphic-relations
*/
public function image()
{
return $this->morphOne('Image', 'imageable');
}
/**
* Defines a one-to-many relationship.
*
* #see http://laravel.com/docs/eloquent#one-to-many
*/
public function comments()
{
return $this->hasMany('Comment');
}
}
You are stopping the mass assignment of keywords with your model settings.
Change
protected $fillable = array('title');
to
protected $fillable = array('title', 'keywords');

Resources