PHP / Laravel - Combine result (Model relationship) - php

Right now I have below data:
Now the problem with above is, that it's creating a new array for each entry in my database.
I would very much like to get below output:
The difference is here that:
All the routes that are the same, should be grouped. So if the Route is the same, it should be under the same route list.
If the Carrier under the route is the same, then it should be under that route and carrier
If the route is different, it should start a new list (**Route: XYZ to XYZ)
Current Code:
Controller.php:
public function show()
{
$routes = Routes::where('id', '>=', 1)
->with('routeStops', 'getVessel')
->get()
->toArray();
foreach ($routes as $key => $value) {
echo '<h2>Route: ' . $value['origin'] . ' to ' . $value['destination'] . '</h2>';
$carrier = Carriers::where('id', $value['get_vessel']['carriers_id'])->first()->name;
echo 'Carrier: ' . $carrier . '';
dump($value);
}
}
This is my models:
Routes.php
/**
* Get the route stop associated with this route.
*/
public function routeStops()
{
return $this->hasMany('App\RouteStops', 'route_id');
}
/**
* Get the vessel record associated with this route.
*/
public function getVessel()
{
return $this->belongsTo('App\Vessels', 'vessel_id');
}
How can I do so I achieve the 2nd desired output?

Related

Count active Cache sessions Laravel. (Advice Needed)

Running into a problem here and been trying to find a solution for days now.
I want to be able to count the number of Online users in my Laravel application.
I already have build in a system for showing the online and offline user status in my application in the admin panel.
However i am trying to get a counter to just show the numerical value of online users.
Under is the code i used for showing the online offline status.
But now im trying to call while using the count(Cache::has('user-is-online')
However i cant get it working.
Hope somebody can help the way i can call this.
For what i did manage to build i used the following:
Created middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Auth;
use Cache;
use Carbon\Carbon;
class LastUserActivity
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()) {
$expiresAt = Carbon::now()->addMinutes(1);
Cache::put('user-is-online-' . Auth::user()->id, true, $expiresAt);
}
return $next($request);
}
}
Created controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use DB;
use Cache;
class UserController extends Controller
{
/**
* Show user online status.
*
*/
public function userOnlineStatus()
{
$users = DB::table('users')->get();
foreach ($users as $user) {
if (Cache::has('user-is-online-' . $user->id))
echo "User " . $user->name . " is online.";
else
echo "User " . $user->name . " is offline.";
}
}
}
Created route:
Route::get('/check', 'StatusController#userOnlineStatus');
And i call it with:
<td>
#if(Cache::has('user-is-online-' . $user->id))
<span class="badge badge-warning rounded-0 w-100">Online</span>
#else
<span class="badge badge-danger rounded-0 w-100">Offline</span>
#endif
</td>
Because you've unique key for the each user in the cache. You can't find it by partial key in your case. You can keep a counter like you're doing in the userOnlineStatus function and print the results after that. Look below.
/**
* Show number of online users.
*
*/
public function numberOfOnlineUsers()
{
$users = DB::table('users')->get();
$counter = 0;
foreach ($users as $user) {
if (Cache::has('user-is-online-' . $user->id)) {
$counter++;
}
}
//Passing $counter to the view
return view("your_blade_file_name", ['counter' => $counter]);
}
To access in blade file
The number of online users are {{ $counter }}

Laravel 5 make routing with unique parameter

I am currently using a route with a parameter nameto query the profile of a user in my application.
So for example: /members/johnwill show the profile of John.
You see the problem here, if there are 2 John's then it's gonna be a problem.
I know I could do something like this /members/idsince id is unique but I want the url to look pretty with the user's name and not a random number.
So my question is if there is a way to use the id to make it unique but to display the name in url?
my route:
Route::get('/members/{name}', 'UserController#usersProfile');
My usersProfile method:
/**
* Returns a users profile.
*
* #param string $name
*
* #return \Illuminate\Http\Response
*/
public function usersProfile($name)
{
$profile = $this->userService->getProfile($name);
if ($profile == null) {
return redirect('members')->with('status', 'Whoops, looks like that member does not exist (yet).');
}
return view('members/profile', ['profile' => $profile]);
}
you can use some package for slug your model name :
https://github.com/spatie/laravel-sluggable
https://github.com/cviebrock/eloquent-sluggable
these packages automatically doing that for you.
if you have same name this will happen:
http://example.com/post/my-dinner-with-andre-francois
http://example.com/post/my-dinner-with-andre-francois-1
http://example.com/post/my-dinner-with-andre-francois-2
just in production i have some issue with scopes...
when you apply global scope on model , it may create duplicate slug for that ... you can fix that by add this to your model :
public function scopeFindSimilarSlugs(Builder $query, Model $model, $attribute, $config, $slug)
{
$separator = $config['separator'];
return $query->withoutGlobalScopes()->where(function (Builder $q) use ($attribute, $slug, $separator) {
$q->where($attribute, '=', $slug)
->orWhere($attribute, 'LIKE', $slug . $separator . '%');
});
}
You should add another column to your user table with name 'slug' or 'username' and map that column in your route.
In your users model, make a static function to generate unique slug with a number attached and save it in that new column to provide users with public/private profile URLs.
This below sample will stop returning slug/username after 100 users with the same name.
public static function getUniqueSlug($name) {
$original_slug = getSlug($name); // Default getter to get value in model
$slug = str_slug($original_slug); //Convert a string into url friendly string with hyphens
while(true) {
$count = User::where('slug', $slug)->count();
if($count > 0) {
$slug = $original_slug."-".rand(1,99);
} else {
return $slug;
}
}
}

Passing variables from one controller to another with laravel

Is it possible when redirect from one controller to another also to pass variables? What I mean is that I have submit function and after submit I make redirect to url. Then in other controller trying to display some info based on variables. This is the redirect from first controller
return Redirect::to('/cart/payment/order/' . $order->order_id . '+' . $order->user_id);
then in second controller this
public function paymentView() {
$order = Order::where('order_id', $orderId)->first();
if (!$order) {
App::abort(404);
}
$userID = $order['user_id'];
$orderID = $order['order_id'];
}
Question is how to get $user_id and $order_id now?
First your route declaration should accept them, something like this, depending on your route:
Route::get('cart/payment/order/{orderID}/{userID}'...
Then Laravel will automatically inject them in your controller method:
public function paymentView( $orderID, $userID, ) {
And I recommend your URL to be with slash not with plus sign:
return Redirect::to('/cart/payment/order/' . $order->order_id . '/' . $order->user_id);

Laravel 5.1 Modify input before form request validation

Is there a way to modify input fields inside a form request class before the validation takes place?
I want to modify some input date fields as follows but it doesn't seem to work.
When I set $this->start_dt input field to 2016-02-06 12:00:00 and $this->end_dt to 2016-02-06 13:00:00 I still get validation error "end_dt must be after start_dt". Which means the input request values aren't getting changed when you update $this->start_dt and $this->end_dt inside the rules() function.
public function rules()
{
if ($this->start_dt){
$this->start_dt = Carbon::createFromFormat('d M Y H:i:s', $this->start_dt . ' ' . $this->start_hr . ':'. $this->start_min . ':00');
}
if ($this->end_dt){
$this->end_dt = Carbon::createFromFormat('d M Y H:i:s', $this->end_dt . ' ' . $this->end_hr . ':'. $this->end_min . ':00');
}
return [
'start_dt' => 'required|date|after:yesterday',
'end_dt' => 'required|date|after:start_dt|before:' . Carbon::parse($this->start_dt)->addDays(30)
];
}
Note: start_dt and end_dt are date picker fields and the start_hr, start_min are drop down fields. Hence I need to create a datetime by combining all the fields so that I can compare.
As of laravel 5.4 you can override the prepareForValidation method of the ValidatesWhenResolvedTrait to modify any input. Something similar should be possible for laravel 5.1.
Example in your Request
/**
* Modify the input values
*
* #return void
*/
protected function prepareForValidation() {
// get the input
$input = array_map('trim', $this->all());
// check newsletter
if (!isset($input['newsletter'])) {
$input['newsletter'] = false;
}
// replace old input with new input
$this->replace($input);
}
The FormRequest has a method validationData() that return the data to validate, so i'm overriding it in my form request:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyClassRequest extends FormRequest
{
...
/**
* Get data to be validated from the request.
*
* #return array
*/
public function validationData() {
return array_merge(
$this->all(),
[
'number' => preg_replace("/[^0-9]/", "", $this->number)
]
);
}
...
}
It work fine i'm using Laravel 5.4 :)
Try these steps:
1- Middleware
first of all you should create a middleware in app/Http/Middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TransformsRequest;
class DateTimeMiddleware extends TransformsRequest
{
protected $fields = [
'birth_date' => 'toGregorian',
'created_at' => 'toDateTime',
];
protected function transform($key, $value)
{
if (!array_key_exists($key, $this->fields)) {
return $value;
}
$function = $this->fields[$key];
return call_user_func($function, $value);
}
}
with this middleware you can define fields that you want to manipulate them before calling validation and call a specific function to manipulate them.
note: i defined toGregorian and toDateTime in my own helper functions. you can handle it with your own functions
2- Kernel
then modify Http/Kernel.php like bellow:
protected $middlewareGroups = [
'web' => [
...
\App\Http\Middleware\EnglishStrings::class,
],
'api' => [
...
],
];
You can do something like the following:
public function rules(Request $request)
{
if ($request->has('start_dt')){
$request->replace('start_dt', Carbon::createFromFormat('d M Y H:i:s', $request->start_dt . ' ' . $request->start_hr . ':'. $request->start_min . ':00'));
}
if ($request->has('end_dt')){
$request->replace('end_dt' ,Carbon::createFromFormat('d M Y H:i:s', $request->end_dt . ' ' . $request->end_hr . ':'. $request->end_min . ':00'));
}
return [
'start_dt' => 'required|date|after:yesterday',
'end_dt' => 'required|date|after:start_dt|before:' . Carbon::parse($request->start_dt)->addDays(30)
];
}
I took an alternative approach to this, as I want to be able to use $model->fill($validated); in my controllers. As such, I need to ensure that checkboxes have been included as false where they would otherwise be excluded from the array.
So, I created a trait, in app\Traits\ConvertBoolean.php, as follows:
<?php
namespace App\Traits;
trait ConvertBoolean
{
// protected $boolean_attributes = [];
/**
* Override the FormRequest prepareForValidation() method to
* add boolean attributes specified to the request data, setting
* their value to the presence of the data in the original request.
*
* #return void
*/
protected function prepareForValidation() {
if (isset($this->boolean_attributes) && is_array($this->boolean_attributes)) {
$attrs_to_add = [];
foreach ($this->boolean_attributes as $attribute) {
$attrs_to_add[$attribute] = $this->has($attribute);
}
$this->merge($attrs_to_add);
}
}
}
This trait looks for the existence of an array $this->boolean_attributes in the request. If it finds it, it goes through each one and adds the attribute to the request data with the value set to the presence of the attribute in the original request data.
It disregards the value of the HTML form's checkbox value. In most cases, this won't be a problem, but you could change the logic in the trait to look for specific values, if required.
Now that I have this trait, I can then use it in any request, like so:
use App\Traits\ConvertBoolean;
class MyUpdateRequest extends FormRequest
{
use ConvertBoolean;
protected $boolean_attributes = ['my_checkbox'];
// ... other class code
public function rules()
{
// Note that the data is added to the request data,
// so you still need a validation rule for it, in
// order to receive it in the validated data.
return [
'my_checkbox' => 'boolean',
// other rules
];
}
}
If you want to use $this->prepareForValidation() in your request, this is still possible.
Change MyRequest, as follows:
use App\Traits\ConvertBoolean;
class MyUpdateRequest extends FormRequest
{
use ConvertBoolean {
prepareForValidation as traitPrepareForValidation;
}
protected function prepareForValidation() {
// the stuff you want to do in MyRequest
// ...
$this->traitPrepareForValidation();
}
// ...
}

Laravel orderBy on a relationship

I am looping over all comments posted by the Author of a particular post.
foreach($post->user->comments as $comment)
{
echo "<li>" . $comment->title . " (" . $comment->post->id . ")</li>";
}
This gives me
I love this post (3)
This is a comment (5)
This is the second Comment (3)
How would I order by the post_id so that the above list is ordered as 3,3,5
It is possible to extend the relation with query functions:
<?php
public function comments()
{
return $this->hasMany('Comment')->orderBy('column');
}
[edit after comment]
<?php
class User
{
public function comments()
{
return $this->hasMany('Comment');
}
}
class Controller
{
public function index()
{
$column = Input::get('orderBy', 'defaultColumn');
$comments = User::find(1)->comments()->orderBy($column)->get();
// use $comments in the template
}
}
default User model + simple Controller example; when getting the list of comments, just apply the orderBy() based on Input::get().
(be sure to do some input-checking ;) )
I believe you can also do:
$sortDirection = 'desc';
$user->with(['comments' => function ($query) use ($sortDirection) {
$query->orderBy('column', $sortDirection);
}]);
That allows you to run arbitrary logic on each related comment record. You could have stuff in there like:
$query->where('timestamp', '<', $someTime)->orderBy('timestamp', $sortDirection);
Using sortBy... could help.
$users = User::all()->with('rated')->get()->sortByDesc('rated.rating');
Try this solution.
$mainModelData = mainModel::where('column', $value)
->join('relationModal', 'main_table_name.relation_table_column', '=', 'relation_table.id')
->orderBy('relation_table.title', 'ASC')
->with(['relationModal' => function ($q) {
$q->where('column', 'value');
}])->get();
Example:
$user = User::where('city', 'kullu')
->join('salaries', 'users.id', '=', 'salaries.user_id')
->orderBy('salaries.amount', 'ASC')
->with(['salaries' => function ($q) {
$q->where('amount', '>', '500000');
}])->get();
You can change the column name in join() as per your database structure.
I made a trait to order on a relation field. I had this issues with webshop orders that have a status relation, and the status has a name field.
Example of the situation
Ordering on with "joins" of eloquent models is not possible since they are not joins. They are query's that are running after the first query is completed. So what i did is made a lil hack to read the eloquent relation data (like table, joining keys and additional wheres if included) and joined it on the main query. This only works with one to one relationships.
The first step is to create a trait and use it on a model. In that trait you have 2 functions. The first one:
/**
* #param string $relation - The relation to create the query for
* #param string|null $overwrite_table - In case if you want to overwrite the table (join as)
* #return Builder
*/
public static function RelationToJoin(string $relation, $overwrite_table = false) {
$instance = (new self());
if(!method_exists($instance, $relation))
throw new \Error('Method ' . $relation . ' does not exists on class ' . self::class);
$relationData = $instance->{$relation}();
if(gettype($relationData) !== 'object')
throw new \Error('Method ' . $relation . ' is not a relation of class ' . self::class);
if(!is_subclass_of(get_class($relationData), Relation::class))
throw new \Error('Method ' . $relation . ' is not a relation of class ' . self::class);
$related = $relationData->getRelated();
$me = new self();
$query = $relationData->getQuery()->getQuery();
switch(get_class($relationData)) {
case HasOne::class:
$keys = [
'foreign' => $relationData->getForeignKeyName(),
'local' => $relationData->getLocalKeyName()
];
break;
case BelongsTo::class:
$keys = [
'foreign' => $relationData->getOwnerKeyName(),
'local' => $relationData->getForeignKeyName()
];
break;
default:
throw new \Error('Relation join only works with one to one relationships');
}
$checks = [];
$other_table = ($overwrite_table ? $overwrite_table : $related->getTable());
foreach($keys as $key) {
array_push($checks, $key);
array_push($checks, $related->getTable() . '.' . $key);
}
foreach($query->wheres as $key => $where)
if(in_array($where['type'], ['Null', 'NotNull']) && in_array($where['column'], $checks))
unset($query->wheres[$key]);
$query = $query->whereRaw('`' . $other_table . '`.`' . $keys['foreign'] . '` = `' . $me->getTable() . '`.`' . $keys['local'] . '`');
return (object) [
'query' => $query,
'table' => $related->getTable(),
'wheres' => $query->wheres,
'bindings' => $query->bindings
];
}
This is the "detection" function that reads the eloquent data.
The second one:
/**
* #param Builder $builder
* #param string $relation - The relation to join
*/
public function scopeJoinRelation(Builder $query, string $relation) {
$join_query = self::RelationToJoin($relation, $relation);
$query->join($join_query->table . ' AS ' . $relation, function(JoinClause $builder) use($join_query) {
return $builder->mergeWheres($join_query->wheres, $join_query->bindings);
});
return $query;
}
This is the function that adds a scope to the model to use within query's. Now just use the trait on your model and you can use it like this:
Order::joinRelation('status')->select([
'orders.*',
'status.name AS status_name'
])->orderBy('status_name')->get();

Categories