Is there a way that the below code can be shortened? It's starting to look a bit messy and I wanted to know if there was a better way.
/**
* Update user.
*
* #param $request
* #param $id
* #return mixed
*/
public function updateUser($request, $id)
{
// Get user
$user = $this->user->find($id);
// Sync job titles
if($request->has('job_title'))
{
$user->jobTitles()->sync((array)$request->get('job_title'));
} else {
$user->jobTitles()->detach();
}
// Sync employee types
if($request->has('employee_type'))
{
$user->employeeTypes()->sync((array)$request->get('employee_type'));
} else {
$user->employeeTypes()->detach();
}
if($request->has('status')) {
$data = $request->only('first_name', 'last_name', 'email', 'status');
} else {
$data = $request->only('first_name', 'last_name', 'email');
}
// Save user changes
return $this->user->whereId($id)->update($data);
}
Any help would be appreciated.
Here is what I would have done:
Extracted the ManyToMany relationships to their own methods.
Initialized the $data variable and overridden it if necessary.
Removed the comments. The code is readable enough, no need for them
Code:
public function updateUser($request, $id)
{
$user = $this->user->find($id);
$data = $request->only('first_name', 'last_name', 'email');
$this->syncJobTitles($user);
$this->syncEmployeeTypes($user);
if($request->has('status')) {
$data['status'] = $request->status;
}
return $user->update($data);
}
private function syncJobTitles($user)
{
if(request()->has('job_title'))
{
$user->jobTitles()->sync((array) request()->get('job_title'));
} else {
$user->jobTitles()->detach();
}
}
private function syncEmployeeTypes($user)
{
if(request()->has('employee_type'))
{
$user->employeeTypes()->sync((array) request()->get('employee_type'));
} else {
$user->employeeTypes()->detach();
}
}
There are different ways to refactor that code:
If that code is only used on that part of your code you could leave it there or
Option 1
Move that business logic to the user model
class User extends Eloquent
{
...
public function applySomeRule($request)
{
if($request->has('job_title')) {
$this->jobTitles()->sync((array)$request->get('job_title'));
} else {
$this->jobTitles()->detach();
}
// Sync employee types
if($request->has('employee_type')) {
$this->employeeTypes()->sync((array)$request->get('employee_type'));
} else {
$this->employeeTypes()->detach();
}
}
}
And your controller could finish like
public function updateUser($request, $id)
{
// Get user
$user = $this->user->find($id);
$user->applySomeRule($request);
if($request->has('status')) {
$data = $request->only('first_name', 'last_name', 'email', 'status');
} else {
$data = $request->only('first_name', 'last_name', 'email');
}
// Save user changes
return $this->user->whereId($id)->update($data);
}
Option 2 If that business logic is used on different controller methods you can use Middlewares, so that you move that logic to middleware and in your route definition you use the middleware you created, let's say SomeRuleMiddleware.
Your routes would look like:
Route::put('user/{id}', [
'middleware' => 'SomeRuleMiddleware',
'uses' => 'YourController#updateUser'
]);
Option 3 You could move all your business logic to Repositories (read about Repository Pattern) and SOLID principles, that way your logic and rules will keep on Repositories and your controllers would keep clean, something like this:
class YourController extends Controller
{
protected $userRepo;
public function __construct(UserRepository $userRepo)
{
$this->userRepo = $userRepo;
}
public function updateUser($request, $id)
{
$data = $request->all();
$result = $this->userRepo->updateUser($id, $data);
return $result;
}
}
Related
I need to add a feature to my system, it's a stock control system.
The feature to be added is about the possibility to start chats in different models. I have Products, Sellers, ProductCollections and a lot of more objects and I need to start conversations within them.
So I thought about making a table with three polymorphic fields to make it fully reusable with any variants. This are the fields
sender_id
sender_type
topic_id
topic_type
receiver_id
receiver_type
And the proper fields to make the conversation possible (message, in_reply_of, etc...)
The sender and receiver needs to be polymorphic because a conversation could be made between SystemUsers and Customers.
Am I in the right way? Will this work in this way? Also I don't know how could I save the Chat entities.
If you want to set a chat for multiple sender, receiver and topics, I believe this relation is good to go.
Also, I was unable to to understand what you exactly mean by Chat entities but the below approach should clear out any doubts you might be having about this approach.
Below is how you can get things done!
Setting the Relations
Set the relations in the following manner
class Chat
{
public function sender()
{
return $this->morphTo();
}
public function topic()
{
return $this->morphTo();
}
public function receiver()
{
return $this->morphTo();
}
}
class SystemUser
{
public function sentChats()
{
return $this->morphMany('App\Chat', 'sender');
}
public function receivedChats()
{
return $this->morphMany('App\Chat', 'receiver');
}
}
class Customer
{
public function sentChats()
{
return $this->morphMany('App\Chat', 'sender');
}
public function receivedChats()
{
return $this->morphMany('App\Chat', 'receiver');
}
}
class Illustrate
{
public function illustrable()
{
return $this->morphTo();
}
public function chats()
{
return $this->morphMany('App\Chat', 'topic');
}
}
Creating A Chat
How to create a chat
public function create()
{
$inputs = request()->only([
'sender_id', 'sender_type', 'receiver_id', 'receiver_type', 'topic_id', 'topic_type',
'message', 'in_reply_of',
]);
$sender = $this->getSender($inputs);
$receiver = $this->getReceiver($inputs);
$topic = $this->getTopic($inputs);
if($sender && $receiver && $topic) {
$chat = $sender->sentChats()->create([
'receiver_id' => $receiver->id,
'receiver_type' => get_class($receiver),
'topic_id' => $topic->id,
'topic_type' => get_class($topic),
'message' => $inputs['message'],
'in_reply_of' => $inputs['in_reply_of'],
]);
}
}
private function getSender($inputs)
{
if(isset($inputs['sender_type'], $inputs['sender_id']) && is_numeric($inputs['sender_id'])) {
switch($inputs['sender_type']) {
case 'SystemUser':
return SystemUser::find($inputs['sender_id']);
case 'Customer':
return Customer::find($inputs['sender_id']);
default:
return null;
}
}
}
private function getReceiver($inputs)
{
if(isset($inputs['receiver_type'], $inputs['receiver_id']) && is_numeric($inputs['receiver_id'])) {
switch($inputs['receiver_type']) {
case 'SystemUser':
return SystemUser::find($inputs['receiver_id']);
case 'Customer':
return Customer::find($inputs['receiver_id']);
default:
return null;
}
}
}
private function getTopic($inputs)
{
if(isset($inputs['topic_type'], $inputs['topic_id']) && is_numeric($inputs['topic_id'])) {
switch($inputs['topic_type']) {
case 'Illustrate':
return Illustrate::find($inputs['topic_id']);
default:
return null;
}
}
}
Getting a chat
public function get($id) {
$chat = Chat::find($id);
$sender = $chat->sender;
// Inverse
// $systemUser = SystemUser::find($id);
// $systemUser->sentChats->where('id', $chatId);
$receiver = $chat->receiver;
// Inverse
// $customer = Customer::find($id);
// $customer->receivedChats->where('id', $chatId);
$topic = $chat->topic;
// Inverse
// $illustrate = Illustrate::find($id);
// $illustrate->chats;
}
Note :- Please understand I haven't tested any of this... This is just a small example on how you can get things done.
Let me know if you face any issues understanding this.
I am using a repository pattern in my Laravel 4 project but come across something which I think I am doing incorrectly.
I am doing user validation, before saving a new user.
I have one method in my controller for this:
public function addNewUser() {
$validation = $this->userCreator->validateUser($input);
if ( $validation['success'] === false )
{
return Redirect::back()
->withErrors($validation['errors'])
->withInput($input);
}
return $this->userCreator->saveUser($input);
}
Then the validateUser method is:
public function validate($input) {
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'email_address' => 'unique:users'
);
$messages = [
];
$validation = Validator::make($input, $rules, $messages);
if ($validation->fails())
{
$failed = $validation->messages();
$response = ['success' => false, 'errors' => $failed];
return $response;
}
$response = ['success' => true];
return $response;
}
This may be okay, but I dont like doing the if statement in my controller? I would rather be able to handle that in my validation class.
But to be able to redirect from the validation class, I need to return the method in the controller.
What if I then want to have 5 methods called, I cant return them all?
I would like to be able to simply call the methods in order, then in their respective class handle what I need to and if there is any errors redirect or deal with them. But if everything is okay, simply ignore it and move to the next function.
So example:
public function addNewUser()
{
$this->userCreator->validateUser($input);
$this->userCreator->formatInput($input);
$this->userCreator->sendEmails($input);
return $this->userCreator->saveUser($input);
}
If doing the if statement in the controller isn't as bad as I think then I can continue, but this seems incorrect?
For repository pattern, you can use this :-
setup your basemodel like this
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model{
protected static $rules=null;
protected $errors=null;
public function validateForCreation($data)
{
$validation=\Validator::make($data,static::$rules);
if($validation->fails())
{
$this->errors=$validation->messages();
return false;
}
return true;
}
/**
* #return errors
*/
public function getErrors() { return $this->errors; }
}
now in your repository, add these methods
protected $model;
protected $errors=null;
public function model(){ return $this->model; }
public function getErrors(){ return $this->errors; }
public function create($inputs)
{
if(!$this->model->validateForCreation($inputs))
{
$this->errors=$this->model->getErrors();
return false;
}
$new=$this->model->create($inputs);
return $new;
}
and the controller will look like this..
public function postCreate(Request $request)
{
$inputs=$request->all();
if($new=$this->repo->create($inputs))
{
return redirect()->back()
->with('flash_message','Created Successfully');
}
return redirect()->back()->withInput()->withErrors($this->repo->getErrors())
->with('flash_message','Whoops! there is some problem with your input.');
}
I'm trying to get to grips with Laravel and not finding the documentation any help at all. It seems to assume you know so much instead of actually walking new users through each section step by step.
I've got to the point where I need to make an internal call to another class, and sticking with MVC I can't seem to do what should be a simple thing.
My code:
class UsersController extends BaseController {
protected $layout = 'layouts.templates.page';
protected $messages;
public function getIndex()
{
$input = array('where', array('field' => 'email', 'operator' => '=', 'value' => 'tony#fluidstudiosltd.com'));
$request = Request::create('user/read', 'GET');
$users = json_decode(Route::dispatch($request)->getContent());
var_dump($users); exit;
$this->pageTitle = 'Fluid Customer Status :: Admin Users';
$this->content = View::make('layouts.admin.users');
}
}
Class UserController extends Base Controller
public function getRead()
{
$userId = (int) Request::segment(3);
if ($userId)
{
$user = User::findOrFail($userId);
return $user;
}
else
{
$users = new User;
var_dump(Input::all()); exit;
if (Input::has('where'))
{
var_dump(Input::get('where')); exit;
}
return $users->get();
}
}
Why isn't the input data from UsersController#getIndex available in UserController#getRead
I am am trying to save to my database, and as part of that save I am trying to sync my many to many relationship, however I am getting the following error from my API,
"BadMethodCallException","message":"Call to undefined method Illuminate\\Database\\Query\\Builder::sync()"
I would have thought that this is because the relationships I have in my model are not many to many so cant be synced, but they look correct to me,
class Organisation extends Eloquent {
//Organsiation __has_many__ users (members)
public function users()
{
return $this->belongsToMany('User')->withPivot('is_admin');
}
//Organisation __has_many__ clients
public function clients()
{
return $this->belongsToMany('Client');
}
//Organisation __has_many__ teams
public function teams()
{
return $this->belongsToMany('Team');
}
//Organisation __has_many__ projects
public function projects()
{
return $this->hasMany('Project');
}
}
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
public function organisations()
{
return $this->belongsToMany('Organisation')->withPivot('is_admin');
}
}
I am running the sync after a successful save,
if(isset($members)) {
$organisation->users()->sync($members);
}
and members is certainly set. The organsisation is created in the following way,
public function create()
{
//
$postData = Input::all();
$rules = array(
'name' => 'required',
);
$validation = Validator::make(Input::all(), $rules);
if($validation->fails()) {
return Response::json( $validation->messages()->first(), 500);
} else {
$organisation = new Organisation;
// Save the basic organistion data.
$organisation->name = $postData['name'];
$organisation->information = $postData['information'];
$organisation->type = 'organisation';
/*
* Create an array of users that can used for syncinng the many-to-many relationship
* Loop the array to assign admins to the organisation also.
*/
if(isset($postData['members'])) {
$members = array();
foreach($postData['members'] as $member) {
if(isset($postData['admin'][$member['id']]) && $postData['admin'][$member['id']] == "on") {
$members[$member['id']] = array(
'is_admin' => 1
);
} else {
$members[$member['id']] = array(
'is_admin' => 0
);
}
}
}
/*
* Create an array of clients so we can sync the relationship easily
*
*/
if(isset($postData['clients'])) {
$clients = array();
foreach($postData['clients'] as $client) {
$clients[] = $client['id'];
}
}
/*
* Create an array of teams so we can sync the relationship easily
*
*/
if(isset($postData['teams'])) {
$teams = array();
foreach($postData['teams'] as $team) {
$teams[] = $team['id'];
}
}
/*
* Create an array of projects so we can sync the relationship easily
*
*/
if(isset($postData['projects'])) {
$projects = array();
foreach($postData['projects'] as $project) {
$projects[] = $project['id'];
}
}
if( $organisation->save() ) {
if(isset($members)) {
$organisation->users()->sync($members);
}
if(isset($teams)) {
$organisation->teams()->sync($teams);
}
if(isset($teams)) {
$organisation->clients()->sync($clients);
}
if(isset($projects)) {
$organisation->projects()->sync($projects);
}
$organisation->load('users');
$organisation->load('teams');
$organisation->load('clients');
$organisation->load('projects');
return Response::make($organisation, 200);
} else {
return Response::make("Something has gone wrong", 500);
}
}
}
I was looking a while for the problem and I didn't see any (I was looking at first sync as you suggested) but I looked again and I think the problem is not syncing users here. Probably the problem is:
if(isset($projects)) {
$organisation->projects()->sync($projects);
}
You are trying to use sync on 1 to many relationship because you defined it this way:
return $this->hasMany('Project');
So either change hasMany here into belongsToMany if it's many to many relationship (that's probably the case) or don't use sync here for $projects because it works only for many to many relationship.
I hope I can explain this clearly, apologies in advance if it is confusing. I have a goals table which hasOne of each of bodyGoalDescs, strengthGoalDescs and distanceGoalDescs as shown below
goals.php
class Goal extends BaseModel
{
protected $guarded = array();
public static $rules = array();
//define relationships
public function user()
{
return $this->belongsTo('User', 'id', 'userId');
}
public function goalStatus()
{
return $this->hasOne('GoalStatus', 'id', 'goalStatus');
}
public function bodyGoalDesc()
{
return $this->hasOne('BodyGoalDesc', 'id', 'bodyGoalId');
}
public function distanceGoalDesc()
{
return $this->hasOne('DistanceGoalDesc', 'id', 'distanceGoalId');
}
public function strengthGoalDesc()
{
return $this->hasOne('StrengthGoalDesc', 'id', 'strengthGoalId');
}
//goal specific functions
public static function yourGoals()
{
return static::where('userId', '=', Auth::user()->id)->paginate();
}
}
each of the three tables looks like this with the function details changed
class BodyGoalDesc extends BaseModel
{
protected $guarded = array();
public static $rules = array();
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'bodyGoalDescs';
//define relationships
public function goal()
{
return $this->belongsTo('Goal', 'bodyGoalId', 'id');
}
}
a goal has either a body goal, a strength goal, or a distance goal. I am having a problem with this method in the controller function
<?php
class GoalsController extends BaseController
{
protected $goal;
public function __construct(Goal $goal)
{
$this->goal = $goal;
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
$thisgoal = $this->goal->find($id);
foreach ($this->goal->with('distanceGoalDesc')->get() as $distancegoaldesc) {
dd($distancegoaldesc->DistanceGoalDesc);
}
}
}
when I pass through goal 1 which has a distance goal the above method dies and dumps the Goal object with the details of goal 1 and an array of its relations including an object with DistanceGoalDes.
when I pass through goal 2 it passes through exactly the same as if I had passed through goal 1
if I dd() $thisgoal i get the goal that was passed through
what I want ultimately is a method that returns the goal object with its relevant goal description object to the view but this wont even show me the correct goal details not too mind with the correct relations
this function is now doing what I want it to do, I am sure there is a better way (besides the fact that its happening in the controller right now) and I would love to hear it.
public function show($id)
{
$thisgoal = $this->goal->find($id);
if (!$thisgoal->bodyGoalDesc == null) {
$goaldesc = $thisgoal->bodyGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('bodygoaldesc', $goaldesc);
} elseif (!$thisgoal->strengthGoalDesc == null) {
$goaldesc = $thisgoal->strengthGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('strengthgoaldesc', $goaldesc);
} elseif (!$thisgoal->distanceGoalDesc == null) {
$goaldesc = $thisgoal->distanceGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('distancegoaldesc', $goaldesc);
}
}