JohnBraun

JohnBraun

PhD candidate (Organic Chemistry) at VU University Amsterdam

Member Since 2 Years Ago

Netherlands

Experience Points
126,040
Total
Experience

3,960 experience to go until the next level!

In case you were wondering, you earn Laracasts experience when you:

  • Complete a lesson — 100pts
  • Create a forum thread — 50pts
  • Reply to a thread — 10pts
  • Leave a reply that is liked — 50pts
  • Receive a "Best Reply" award — 500pts
Lessons Completed
898
Lessons
Completed
Best Reply Awards
57
Best Reply
Awards
  • start-engines Created with Sketch.

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • first-thousand Created with Sketch.

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • 1-year Created with Sketch.

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • 2-years Created with Sketch.

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • 3-years Created with Sketch.

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • 4-years Created with Sketch.

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • 5-years Created with Sketch.

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • school-session Created with Sketch.

    School In Session

    Earned when at least one Laracasts series has been fully completed.

  • welcome-newcomer Created with Sketch.

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • full-time-student Created with Sketch.

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • pay-it-forward Created with Sketch.

    Pay It Forward

    Earned once you receive your first "Best Reply" award on the Laracasts forum.

  • subscriber-token Created with Sketch.

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • lifer-token Created with Sketch.

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • lara-evanghelist Created with Sketch.

    Laracasts Evangelist

    Earned if you share a link to Laracasts on social media. Please email [email protected] with your username and post URL to be awarded this badge.

  • chatty-cathy Created with Sketch.

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • lara-veteran Created with Sketch.

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • 10k-strong Created with Sketch.

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • lara-master Created with Sketch.

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • laracasts-tutor Created with Sketch.

    Laracasts Tutor

    Earned once your "Best Reply" award count is 100 or more.

  • laracasts-sensei Created with Sketch.

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • top-50 Created with Sketch.

    Top 50

    Earned once your experience points ranks in the top 50 of all Laracasts users.

Level 26
126,040 XP
Nov
12
13 hours ago
Activity icon

Awarded Best Reply on Async Await And Axios

Use this in await this.getNextThing( item.id ) and return the axios response from the getNextThing() method perhaps?

Activity icon

Replied to Vue Lodash Sort By Nested Array

Not the most elegant approach, but it appears to be working correctly.

First sort the lineStatusses by severity and then sort the railways by their first lineStatus (which is now the highest statusSeverity number).

lines.forEach(function (line) {

  let sorted = _.sortBy(line.lineStatus, function(status) {
    return status.statusSeverity;
  }).reverse();
  
  line.lineStatus = sorted;
});

let sorted = _.sortBy(lines, function(line) {
  return line.lineStatus[0].statusSeverity;
}).reverse();

console.log(sorted); // now shows the railways having the highest "severityStatus" first

You can play around with it on CodeSandbox: https://codesandbox.io/s/patient-smoke-gko6n

Activity icon

Replied to Async Await And Axios

Use this in await this.getNextThing( item.id ) and return the axios response from the getNextThing() method perhaps?

Activity icon

Awarded Best Reply on Broadcasting From Partner To User

I've setup a working demo project, which you can download from this repo: https://github.com/Jhnbrn90/laracasts-broadcasting-demo

While setting up this demo, I noticed we are comparing an integer to a string in channels.php, which you can solve by casting the passed in id to an integer.

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === (int) $userId; 
});

Demo app

The demo app has two main routes /home and /send/{userId}.

Create an account and leave one browser tab open on /home. The user you're currently logged in as, probably has the ID of 1. If you open a new tab and visit /send/1 you'll receive a notification on the /home tab.

Nov
10
2 days ago
Activity icon

Replied to Broadcasting From Partner To User

I've setup a working demo project, which you can download from this repo: https://github.com/Jhnbrn90/laracasts-broadcasting-demo

While setting up this demo, I noticed we are comparing an integer to a string in channels.php, which you can solve by casting the passed in id to an integer.

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === (int) $userId; 
});

Demo app

The demo app has two main routes /home and /send/{userId}.

Create an account and leave one browser tab open on /home. The user you're currently logged in as, probably has the ID of 1. If you open a new tab and visit /send/1 you'll receive a notification on the /home tab.

Activity icon

Replied to Broadcasting From Partner To User

Hey @splendidkeen did you make any progress on the problem?

Just to be sure... did you uncomment the BroadcastServiceProvider in the config/app.php file?

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

    ],
Nov
05
1 week ago
Activity icon

Replied to Broadcasting From Partner To User

Do you see the event coming in over at Pusher? Could you provide some log details?

Activity icon

Replied to Broadcasting From Partner To User

Your socket should not be null. You mentioned before that you are using Pusher.

Did you recompile your assets with npm run dev (or npm run prod) already after updating your Pusher credentials in the .env file?

Activity icon

Replied to How To Test A Queued Notification?

How do you obtain the $user variable in your controller?

protected function registered(Request $request, $user)
{
    $user->notify(new WelcomeNotification());
    // other code
}

Couldn't you better use auth()->user()->notify(new WelcomeNotification()); ?

protected function registered(Request $request)
{
    auth()->user->notify(new WelcomeNotification());
    // other code
}
Nov
04
1 week ago
Activity icon

Replied to Broadcasting From Partner To User

It might be something very trivial that I can't know of as I am not working with your codebase.

You could try to debug your application using Laravel Telescope, which can give you more insight in the broadcasted events.

Other than that, I'm afraid this is all the help I could give you, since you're not getting any specific errors.

Activity icon

Replied to Broadcasting From Partner To User

What do you mean exactly?

You could - by the way - also maybe better use a mounted() callback instead of created()

Activity icon

Replied to Broadcasting From Partner To User

Your variables don't match up, adjust it to:

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === $userId; 
});
Activity icon

Replied to Broadcasting From Partner To User

console.log(e.booking)won't be triggered. Nothing is pushed to the console.

So, the event is broadcasted but the listener doesn't act upon it. Can you share the contents of the routes/channels.php file?

Activity icon

Replied to Broadcasting From Partner To User

Have you adjusted this.bookingId = e.booking; to this.booking = e.booking; as well?

  • If so, I would remove the v-if / v-show and see if the event is captured at all by the listener.

  • What do you see if you console.log(e.booking) in the .listen() method after broadcasting the event?

created() {
window.Echo.private(`Booking.${this.user.id}`)
  .listen("ConfirmBookingEvent", (e) => {
    console.log(e.booking);
  });
}
Activity icon

Replied to Broadcasting From Partner To User

I haven't tested the code and adjusted it on the fly, so I can't guarantee the code should work as I've provided. It might need some debugging on your side. To help with that, I need some more info.

I noticed that I had a typo in my code: this.bookingId = e.booking; should become this.booking = e.booking; (already updated in the abovementioned code).

Furthermore:

  • Maybe change the v-if to v-show.

  • Do you have any errors in the console?

  • Do you have a public $booking property in your ConfirmBookingEvent class? Otherwise, we can't get it off the event e in the listener of course.

In your ConfirmBookingEvent class:

class ConfirmBookingEvent extends ... 
{

  public $booking;

  public function __construct(Booking $booking)
  {
    $this->booking = $booking;
  }
}
Activity icon

Replied to Broadcasting From Partner To User

I hope I correctly understand your goal.

It sounds like you want to work with the authenticated user object, so pass that to your component in the view.

View

<booking-confirmation :user="{{ auth()->user() }}" />

Component

On your homepage you want to listen for incoming booking events that are associated with the currently logged in user.

By default, we won't show the modal but whenever a new broadcast event comes in we'll show details of the booking and hide the modal after 3 seconds or something like that (with javascript's setTimeout()).

<template>
    <div v-if="showConfirmation">
           Confirmation of booking with ID {{ booking ? booking.id : 'N/A' }}
    </div>
</template>

<script>
    export default {
        props: ['user'],

        data() {
            return {
                showConfirmation: false,
                booking: null,
            }
        },

        created() {
            window.Echo.private(`Booking.${this.user.id}`)
                .listen("ConfirmBookingEvent", (e) => {
                    this.booking = e.booking; 
                    this.showConfirmation = true; 

                   // reset modal after 3 seconds
                    setTimeout(() => {
                       this.showConfirmation = false;
                       this.booking = null;
                   }, 3000);

            });
        },
    }
</script>
Activity icon

Replied to Broadcasting From Partner To User

I need to know more about what you're trying to achieve to be able to answer your question adequately, but you could pass the user (with a booking relationship eager loaded) to the Vue component instead of the booking itself.

But, it depends on what you actually want to do.

Activity icon

Awarded Best Reply on For Each For Columns In Eloquent Collection

Dear @dreadfulcthulhu

Laravel's one-to-many relationship seems the right fit for your scenario.

First, create models for a Location, School and Classroom. This means you'll get a separate database table for each.

You can use the following artisan command to generate a Location model with a migration. Repeat for School and Classroom

php artisan make:model Location -m

Next, you'll have to add a few columns to the generated migration files.

Migrations

  • create_locations_table migration:
    public function up()
    {
        Schema::create('locations', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->timestamps();
        });
    }
  • create_schools_table migration:
    public function up()
    {
        Schema::create('schools', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->bigUnsignedInteger('location_id');
            $table->timestamps();
        });
    }
  • create_classrooms_table migration:
    public function up()
    {
        Schema::create('schools', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->bigUnsignedInteger('school_id');
            $table->timestamps();
        });
    }

Models & Relations

Then, in your models you can define the proper relationships:

Location model:

class Location extends Model 
{
   // a Location has many schools (via the location_id)
    public function schools()
    {
        return $this->hasMany(School::class);
    }
}

School model:

class School extends Model 
{
   // a School has many classrooms (via the school_id)
    public function classrooms()
    {
        return $this->hasMany(Classroom::class);
    }
}

Controller

In your controller, you have access to the following code:

$locations = App\Location::with('schools.classrooms')->get();

return view('overview', compact('locations'));

View

In your view, you can now create your list as follows:

<ul>
  @foreach ($locations as $location)
    <li> 
      {{ $location->name }}
      <ul>
        @foreach ($location->schools as $school)
          <li> 
            {{ $school->name }}
            <ul>
              @foreach ($school->classrooms as $classroom)
                <li>
                  {{ $classroom->name }}
                </li>
              @endforeach
            </ul>
          </li>
        @endforeach
      </ul>
    </li>
  @endforeach
</ul>

Eager loading

You can choose to eager load all relationships or just load them when needed, by defining a $with property on your models or using the ->load() function ad-hoc. See the Eager loading section in the Laravel documentation.

Activity icon

Replied to Broadcasting From Partner To User

You're welcome, let me know if you got everything working.

Activity icon

Replied to Broadcasting From Partner To User

I recently wrote an article on WebSockets and took some stuff from my blog and adjusted it to your use case:

In your routes/channels.php:

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === $id; // or any other validation rule you want
});

Pass the Booking with user to the view from the controller.

In your view:

<html>
  <head>
    <title>Booking's page</title>
    <!-- always include your CSRF token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
  </head>

  <body>
    <div id="app">
      <!-- our Vue component -->
      <booking :booking="{{ $booking }}" />
    </div>

    <!-- include our compiled javascript -->
    <script src="{{ asset('js/app.js') }}"></script>
  </body>
</html>

In your Vue component:

<template>
    <div>
        Your booking with ID {{ booking.id }} is currently:
        <span v-if="processing">
           Available
        </span>
        <span v-else>
           Booked!
        </span>
    </div>
</template>

<script>
    export default {
        props: ['booking'],

        data() {
            return {
                processing: true,
            }
        },

        created() {
            window.Echo.private(`Booking.${this.booking.user.id}`)
                .listen("ConfirmBookingEvent", (e) => {
                    this.processing = false;
            });
        },
    }
</script>
Activity icon

Replied to For Each For Columns In Eloquent Collection

@dreadfulcthulhu You might have made that clearer in your question. Especially since you've provided a classrooms table, which does not indicate you already had setup the separate Models.

Activity icon

Replied to Argument 1 Passed To Illuminate\Http\Request::Illuminate\Foundation\Providers\{closure}() Must Be Of The Type Array, String Given

Argument 1 passed to App\Http\Controllers\Auth\RegisterController::create() must be an instance of Illuminate\Http\Request, array given, called in /Users/makeba/Documents/Others_Projects/beugue_lire/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php on line 33

@omarsow94

Why did you change the typehint from array to Request?

Activity icon

Replied to For Each For Columns In Eloquent Collection

Dear @dreadfulcthulhu

Laravel's one-to-many relationship seems the right fit for your scenario.

First, create models for a Location, School and Classroom. This means you'll get a separate database table for each.

You can use the following artisan command to generate a Location model with a migration. Repeat for School and Classroom

php artisan make:model Location -m

Next, you'll have to add a few columns to the generated migration files.

Migrations

  • create_locations_table migration:
    public function up()
    {
        Schema::create('locations', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->timestamps();
        });
    }
  • create_schools_table migration:
    public function up()
    {
        Schema::create('schools', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->bigUnsignedInteger('location_id');
            $table->timestamps();
        });
    }
  • create_classrooms_table migration:
    public function up()
    {
        Schema::create('schools', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->bigUnsignedInteger('school_id');
            $table->timestamps();
        });
    }

Models & Relations

Then, in your models you can define the proper relationships:

Location model:

class Location extends Model 
{
   // a Location has many schools (via the location_id)
    public function schools()
    {
        return $this->hasMany(School::class);
    }
}

School model:

class School extends Model 
{
   // a School has many classrooms (via the school_id)
    public function classrooms()
    {
        return $this->hasMany(Classroom::class);
    }
}

Controller

In your controller, you have access to the following code:

$locations = App\Location::with('schools.classrooms')->get();

return view('overview', compact('locations'));

View

In your view, you can now create your list as follows:

<ul>
  @foreach ($locations as $location)
    <li> 
      {{ $location->name }}
      <ul>
        @foreach ($location->schools as $school)
          <li> 
            {{ $school->name }}
            <ul>
              @foreach ($school->classrooms as $classroom)
                <li>
                  {{ $classroom->name }}
                </li>
              @endforeach
            </ul>
          </li>
        @endforeach
      </ul>
    </li>
  @endforeach
</ul>

Eager loading

You can choose to eager load all relationships or just load them when needed, by defining a $with property on your models or using the ->load() function ad-hoc. See the Eager loading section in the Laravel documentation.

Activity icon

Replied to Redirect Authentication From API Controller To Web

Maybe I'm missing something, but couldn't you perform the redirect using React, whenever a certain json response is returned? Then the login() method of your API controller just returns json, and the caller (your React frontend) would decide which action to take.

Activity icon

Replied to Argument 1 Passed To Illuminate\Http\Request::Illuminate\Foundation\Providers\{closure}() Must Be Of The Type Array, String Given

The request()->validate() method expects an array, while you pass the string 'form'. You can use it as follows:

$validated = request()->validate([
  'username' => 'required',
  'email' => 'required|email',
   // etc.
]);


// Now you can use the $validated variable which holds an array of the validated data:

User::create([
  'username' => $validated['username'],
  'email' => $validated['email'],
  // etc.
]);
Oct
31
1 week ago
Activity icon

Replied to Retrieving Polymorphic Records

From an instance of OrderItem you should be able to call the 'lineitem' property which should resolve to a Plan model in your case.

$orderItem->lineitem; // should return an instance of App\Models\Plan
Oct
30
1 week ago
Activity icon

Awarded Best Reply on Implement Icheck Js

I got no problems, following these steps:

1) Install the package with npm

npm install icheck

2) In bootstrap.js, require the .min.js file after jQuery:

try {
    window.Popper = require('popper.js').default;
    window.$ = window.jQuery = require('jquery');

    require('bootstrap');
} catch (e) {}

require('icheck/icheck.min.js');

3) Recompile the javascript assets

npm run dev

Using the package as described without errors:

$('input').iCheck({
  labelHover: false,
  cursor: true
});
Oct
28
2 weeks ago
Activity icon

Replied to Implement Icheck Js

A try/catch block allows you to recover from a thrown exception.

In this case, the try/catch block handles any errors while importing popper / jquery. For example, when you try to require them while they're not intalled. The try/catch block allows the rest of the code to still be evaluated.

Activity icon

Replied to Implement Icheck Js

I got no problems, following these steps:

1) Install the package with npm

npm install icheck

2) In bootstrap.js, require the .min.js file after jQuery:

try {
    window.Popper = require('popper.js').default;
    window.$ = window.jQuery = require('jquery');

    require('bootstrap');
} catch (e) {}

require('icheck/icheck.min.js');

3) Recompile the javascript assets

npm run dev

Using the package as described without errors:

$('input').iCheck({
  labelHover: false,
  cursor: true
});
Activity icon

Awarded Best Reply on How Do I Use HTTP_Request2 In Class I Have Created In App

If your Tokens class and your HTTP_Request2 class live in the same namespace, you can omit the use statement. (You also don't need to prefix the use statement with a backslash).

Further more: why did you use require_once of that file?

If I assume that your two classes live in the same namespace, you should be able to use:

<?php 

namespace App\Helpers;

Class Tokens
{
    public static function tokens()
    {
        $request = new Http_Request2('https://sandbox.momodeveloper.mtn.com/collection/token/'); // we imported this class, so no need to prefix to the root namespace

        $url = $request->getUrl();
    }
}
Activity icon

Replied to Music Upload And Streaming Package In Laravel

I found some packages on packagist that might help you, searching for "laravel audio": https://packagist.org/?query=laravel%20audio

Activity icon

Replied to Deploying Laravel On Shared Hosting

I can only recommend you to just go with a VPS (virtual private server). The two providers I would recommend are linode or DigitalOcean.

Activity icon

Replied to How Do I Use HTTP_Request2 In Class I Have Created In App

If your Tokens class and your HTTP_Request2 class live in the same namespace, you can omit the use statement. (You also don't need to prefix the use statement with a backslash).

Further more: why did you use require_once of that file?

If I assume that your two classes live in the same namespace, you should be able to use:

<?php 

namespace App\Helpers;

Class Tokens
{
    public static function tokens()
    {
        $request = new Http_Request2('https://sandbox.momodeveloper.mtn.com/collection/token/'); // we imported this class, so no need to prefix to the root namespace

        $url = $request->getUrl();
    }
}
Oct
16
3 weeks ago
Activity icon

Replied to How To Store Polymorphic Related Model Data

You dont have to save the morphable_id and morphable_type fields yourself. These are taken care of automatically upon saving the relation as specified in the documentation. Youll only need to make sure you have a migration for you pivot table containing these columns.

Oct
12
1 month ago
Activity icon

Replied to Contributing To A Package

i see that you change the name to make sure composer gets the dependency from your file system rather than the one of packaging.

That was my reasoning indeed, but it might also be that composer first checks your defined local repositories before checking packagist. I didnt try that yet actually.

Is there anything wrong with simply cloneing the package into a packages folder in the laravel project root does your solution have any advantages over this?

Well, yes and no. I dont think it matters much what the exact location is of where the package lives, but if you include it in a packages directory in your Laravel app, you should make sure to exclude it from git.

If you take my suggested approach, the package is added to the vendor directory like any package you require by composer, therefore giving a better representation. The vendor directory is by default automatically excluded from git by Laravel, because of this convention.

Activity icon

Replied to How To Access To Laravel User Model From My Custom Package ?

One option is to use a polymorphic relationship. I have written a section on the interaction with Laravel's user model on my blog: Creating a Laravel specific package (part 3) (look for the "Models related to App\User" heading)

Activity icon

Replied to Contributing To A Package

I recently wrote an extensive blog on creating Laravel specific packages on my blog at johnbraun.blog

Generally, my approach is as follows:

Step 1: clone the repo into a packages folder (separate from where I store my Laravel projects)

Step 2: in a (new) Laravel project, I add the code mentioned by @sti3bas to my composer.json, where I use the relative path to the package path (where you just cloned it)

"repositories": [
   {
      "type": "path",
      "url": "../../packages/Laravel-activity",
   }
],

Step 3: in the compose.json file of the package I change the vendor name to my own name (this is a change you dont commit! And Ill explain later why we do this) for example spatie/Laravel-activity would become jhnbrn90/Laravel-activity

Step 4: back in the Laravel project I require the package by with composer require jhnbrn90/Laravel-activity. Although the package is not on Packagist, since we added the local path as a repository to the composer.json file it will still find the package. By using your own name, youre sure that youre using the local package.

Now you have included the package locally and whenever you make a change to the package, that will be reflected in your Laravel app directly.

Hope this helps.

Oct
10
1 month ago
Activity icon

Replied to Testing Process In Laravel Application Development

I am a big supporter of TDD, but I'm not a purist when it comes to the hard separation of what a unit test and a feature/integration test should be. For example, many of my unit tests touch the database (which is considered bad practice by many).

I use a feature test as a way to test the cascading events upon a user's action in the system. Let's say a user registers for a calendar event which should eventually do X, Y and Z. This would fall in my category of feature tests. This test would involve a call to $this->post('some-endpoint', $payload) and make assertions about dispatched Events / Jobs / etc; your desired outcome.

In the unit tests I mostly test that a model has all attributes I expect (making sure the database migrations and model factories are in order) and make sure that certain methods on that model are doing what I expect (yes, even when it needs to interact with the database). For example, when you want to "star" a post, I would test a $post->star() method as a_post_can_be_starred test which checks if a certain column ("starred" for example) was changed from "false" to "true". My apologies for this corny example.

I think it comes down to your personal needs and preference. Do whatever helps the development of your applications most. And above all: just get started with testing and see what you feel comfortable with.

Activity icon

Replied to Why Is Asset() Not Secure?

Aha!

I don't have experience with load balancers, but I am glad that you've solved it 👍

Activity icon

Replied to Laravel Echo, Pusher Not Authenticating On Private Channel

Did you already recompile your assets with npm run dev?

Activity icon

Replied to MorthTo With Multiple Columns

Could you please try to explain what you want to achieve more clearly?

Activity icon

Replied to Why Is Asset() Not Secure?

Thats strange, as I cant reproduce your problem. When using asset() on https I also see that the asset() url is prefixed with https. Did you provide the https prefix in the APP_URL parameter (in .env)?

Are you using Lets encrypt?

I see you could also use the secure_asset() function from the Laravel docs: https://laravel.com/docs/master/helpers#method-secure-asset

Activity icon

Replied to Polymorphic Help

Yes, you are free to update the column types to your needs. I would also not opt for a bigIncrements for the id field, unless youre developing an application with many many users. But these are implementation details.

Good luck!

Oct
08
1 month ago
Activity icon

Replied to Polymorphic Help

@movepixels In your specific use case, we could also get around this polymorphic many-to-many relationship by defining an addItem(), removeItem() and getItems() method on your Order model which manually interact with a item_order pivot table (see my suggested migration below). If you also need to reference back from your Subscription / Product / Upgrades model you can do so by adding a orders() method that makes use of the morphToMany() method as provided by Laravel (see below).

Order model

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class Order extends Model
{
    public function addItem(Model $model)
    {
        DB::table('item_order')->insert([
            'order_id'     => $this->id,
            'item_id'      => $model->id,
            'item_type'    => get_class($model),
        ]);
    }

    public function removeItem(Model $model)
    {
        $order = DB::table('item_order')
            ->where('item_type', get_class($model))
            ->where('item_id', $model->id)
            ->first();
        
        if (! $order) {
            throw new \Exception
                ('Given model was not associated with this order.');
        }

        return DB::table('item_order')->delete($order->id);
    }

    public function getItems()
    {
        $orderItems = collect(
            DB::table('item_order')
                ->where('order_id', $this->id)->get()
            );

        return $orderItems->map(function ($orderItem) {
            return resolve($orderItem->item_type)::findOrFail($orderItem->item_id);
        });
    }
}

And then set up your pivot table as follows:

"item_order" pivot table

public function up()
{
    Schema::create('item_order', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->unsignedBigInteger('order_id');
        $table->morphs('item');
        $table->timestamps();
    });
}

Reference to an Order

// In your Subscription / Product / Upgrade model
class Upgrade extends Model
{
    public function orders()
    {
        return $this->morphToMany('App\Order', 'item', 'item_order');
    }
}
Activity icon

Replied to SoftDelete On Polymorphic Relationships

Are you using the laravel-activitylog package from Spatie?

If so, looking at the Activity model of the package, you see a subject() method which reads a configuration variable called subject_returns_soft_deleted_models to check if it should include any soft deleted subjects:

// Spatie\Activitylog\Models\Activity

    public function subject(): MorphTo
    {
        if (config('activitylog.subject_returns_soft_deleted_models')) {
            return $this->morphTo()->withTrashed();
        }

        return $this->morphTo();
    }

So, if you set this variable to true (it is set to false by default) in your config/activitylog.php file (which you get after publishing the config file, see their readme on github).


    /*
     * If set to true, the subject returns soft deleted models.
     */
    'subject_returns_soft_deleted_models' => true,
Oct
07
1 month ago
Activity icon

Replied to Polymorphic Help

hey @movepixels

Idea is I want to have a Model OrderItem with table "order_items" that will hold all the items for an Invoice. Since the items can come from Subscription, Product, Upgrade models.

I would leave out the intermediate OrderItem model, and just set-up a many-to-many polymorphic relationship between an Order and a Subscription, Product and Upgrade model.

To start, create a new migration to set-up a pivot table for 'orderables':

php artisan make:migration create_orderables_table --create=orderables

In the migration, make sure to add the 'order_id' and 'orderable_id' and 'orderable_type' columns, which we can do via the morphs() helper function:

public function up()
{
    Schema::create('orderables', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->unsignedBigInteger('order_id');
        $table->morphs('orderable');
        $table->timestamps();
    });
}

Ideally, we would like to use $order->items and retrieve all related models. Unfortunately, it is not possible (yet) to gather all underlying models in a many-to-many relationship in Laravel.

Therefore, following the documentation, we need to register all relationships we can have with an Order model.

In your Order model:

public function subscriptions()
{
    return $this->morphedByMany('App\Subscription', 'orderable');
}

public function products()
{
    return $this->morphedByMany('App\Product', 'orderable');
}

public function upgrades()
{
    return $this->morphedByMany('App\Upgrade', 'orderable');
} 

And in the Subscription/Product/Upgrade models:

public function orders()
{
    return $this->morphToMany('App\Order', 'orderable');
}

Now, you can fetch all subscriptions with $order->subscriptions, upgrades with $order->upgrades etc.

And you can fetch all associated orders where an item is included using $subscription->orders, etc.

I hope this helps.

Oct
03
1 month ago
Activity icon

Replied to SoftDelete On Polymorphic Relationships

You could check if the model uses soft deletes with the following check:

method_exists($model, 'bootSoftDeletes')

If a model uses soft deletes, you load the withTrashed() version and otherwise you don't.