bwrigley

bwrigley

Member Since 1 Year Ago

Experience Points 5,750
Experience Level 2

4,250 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 12
Lessons
Completed
Best Reply Awards 0
Best Reply
Awards
  • Start Your Engines Achievement

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • First Thousand Achievement

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • One Year Member Achievement

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • Two Year Member Achievement

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • Three Year Member Achievement

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • Four Year Member Achievement

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • Five Year Member Achievement

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • School In Session Achievement

    School In Session

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

  • Welcome To The Community Achievement

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • Full Time Learner Achievement

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • Pay It Forward Achievement

    Pay It Forward

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

  • Subscriber Achievement

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • Lifer Achievement

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • Laracasts Evangelist Achievement

    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 Achievement

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • Laracasts Veteran Achievement

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • Ten Thousand Strong Achievement

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • Laracasts Master Achievement

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • Laracasts Tutor Achievement

    Laracasts Tutor

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

  • Laracasts Sensei Achievement

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • Top 50 Achievement

    Top 50

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

19 Jun
1 week ago

bwrigley started a new conversation Counting Grandchildren

I'm looking through the eloquent documentation and I'm not quite finding what I woud like. hasManyThrough seems to be the closest but doesn't quite cut it.

So in my app a User owns many ForumThreads

class User extends Model
{
    public function forumThreads(): HasMany
    {
        return $this->hasMany('App\Forum\ForumThread');
    }
}

and a ForumThread can have many ForumLikes

class ForumThread extends Model
{
    public function forumLikes(): MorphMany
    {
        return $this->morphMany('App\Forum\ForumLike', 'likeable');
    }
}

What I had naively hoped was that I could say

$userLikes = $user->forumThreads->forumLikes->count();

is there an eloquent way to do this or do I need to simply loop through?

Thanks!

18 Jun
1 week ago

bwrigley started a new conversation Scout ShouldBeSearchable() Called Before Saved?

I have a ForumThread model and a FormCategory model.

Relationship is defined on ForumThread model as:

    public function forumCategory(): BelongsTo
    {
        return $this->belongsTo('App\Forum\ForumCategory');
    }

I am now wanting to add these ForumThreads to a new Algolia index but only if the 'ForumCategoryis not defined as admin only. So I am usingshouldBeSearchable()' :

    public function shouldBeSearchable(): bool
    {
        return $this->forumCategory->is_admin_only === false;
    }

When I now save any thread (ie with any category) I get the error Trying to get property 'is_admin_only' of non-object

I have the feeling that this is run before the thread is actually saved and so does not have this relationship yet?

Any thoughts would be gratefully received. Thanks

14 Jun
1 week ago

bwrigley left a reply on Query Relationship Model

ah! contains is the magic word. Thank you so much!

bwrigley started a new conversation Query Relationship Model

I'm sure this is much simpler than I am making it but I don't seem to be able to get it to work.

I have a forum which has ForumThreads ForumSubscribers.

As this is a many-to-many relationship I have set it up like this:

        Schema::create('forum_subscriber_forum_thread', function (Blueprint $table) {
            $table->integer('forum_subscriber_id')->unsigned()->nullable();
            $table->foreign('forum_subscriber_id')->references('id')
                                ->on('users')->onDelete('cascade');

            $table->integer('forum_thread_id')->unsigned()->nullable();
            $table->foreign('forum_thread_id')->references('id')
                                ->on('forum_threads')->onDelete('cascade');

            $table->timestamps();
            $table->unique(['forum_subscriber_id', 'forum_thread_id'], 'subscriber_thread_unique');
        });

and in my ForumThread model I have the relationship defined like this:

    public function forumSubscribers(): BelongsToMany
    {
        return $this->belongsToMany('App\User', 'forum_subscriber_forum_thread', 'forum_thread_id', 'forum_subscriber_id');
    }

All working fine, great!

Now I want to check if a User is subscribed to a ForumThread

I thought this would be:

 public function isUserSubscribed(User $user): bool
 {
    return $this->forumSubscribers->where(['forum_subscriber_id',$user->id])->count() > 0;
 }

and then I tried

    public function isUserSubscribed(User $user): bool
    {
        return $this->forumSubscribers()->with(['Users' => function ($q) use ($user): void {
            $q->where('id', $user->id);
        }])->count() > 0;
    }

Neither of these work, I'm sure this must be simpler than I am making it?

Thanks!

10 Jun
2 weeks ago

bwrigley left a reply on Picking Up Implicit Model In Middleware

Thank you both!

I swear I had tried both of these before I posted, but they both seem to be working now. Huge apologies and thank you.

bwrigley started a new conversation Picking Up Implicit Model In Middleware

Hello,

I'm building a simple forum and I want to write some middleware that inspects a ForumThread to make sure it's not locked/archived/etc. before accepting route requests for ForumReply.

But I'm not sure how to pick up the implicitly bound thread that i'm passing in.

So my routes look like this:

             Route::middleware(['forum.thread.unlocked'])->group(function (): void {
                Route::group(['prefix' => 'forum_reply', 'as' => 'forum_reply.'], function (): void {
                    Route::get('create/{forum_thread}', 'Forum\[email protected]')->name('create');
                    Route::post('store/{forum_thread}', 'Forum\[email protected]')->name('store');
                    Route::post('resolve/{forum_thread}', 'Forum\[email protected]')->name('resolve');
                });
            });

In my controller I pick up the ForumThread from the Request in the usual way:

    public function create(ForumThread $thread) : View
    {
    //
    }

How can I also do the same in my middleware as the Request attributes appear to be empty:

https://domain.test/forum/forum_reply/create/215

    public function handle(Request $request, Closure $next)
    {
        dump($request->attributes);
    }

ParameterBag {#44 ▼ #parameters: []

09 May
1 month ago

bwrigley left a reply on Many To Many Relationship Creation

@CBACONNIER - ah yes! Thank you, I had completely forgotten about the naming convention. And thank you for the reminder about name of the ID field. Actually it turns out it needs both of them in this instance.

    public function forumSubscribers(): BelongsToMany
    {
        return $this->belongsToMany('App\User', 'forum_subscriber_forum_thread', 'forum_thread_id', 'forum_subscriber_id');
    }

Thank you again!

07 May
1 month ago

bwrigley started a new conversation Many To Many Relationship Creation

I have a model called ForumThread which has one-to-one relationship to the User model as the person that owns it. However, it also needs a many-to-many relationship to the User model as the people that subscribe to it.

I've defined these relationships in ForumThread:

    public function user(): BelongsTo
    {
        return $this->belongsTo('App\User');
    }

    public function forumSubscribers(): BelongsToMany
    {
        return $this->belongsToMany('App\User');
    }

and I've created the pivot table

        Schema::create('forum_subscriber_forum_thread', function (Blueprint $table) {
            $table->integer('forum_subscriber_id')->unsigned()->nullable();
            $table->foreign('forum_subscriber_id')->references('id')
                                ->on('users')->onDelete('cascade');

            $table->integer('forum_thread_id')->unsigned()->nullable();
            $table->foreign('forum_thread_id')->references('id')
                                ->on('forum_threads')->onDelete('cascade');

            $table->timestamps();
        });

I think I have done that correctly. However there is never a time I want to create ForumThread 'through' a Subscriber, nor do I want to create User 'through' the ForumThread. I think I should create the User then the ForumThread and then link them through the Subscriber relationship.

    public function toggleSubscription(string $userID): void
    {
       $this->forumSubscribers()->toggle([$userID]);
    }

however this gives me a SQL error:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'tcl.forum_thread_user' doesn't exist (SQL: select `user_id` from `forum_thread_user` where `forum_thread_id` = 5)

I'm not clear why it's looking for a table called forum_thread_user. I'm sure this will be a typo on part, but can anyone shed some light?

Thank you for reading.

30 Apr
1 month ago

bwrigley left a reply on Deleting Parent Model Without Cascade

Brilliant! Thank you for such a quick response.

p.s. where did you find it? I thought I had searched everywhere!

bwrigley started a new conversation Deleting Parent Model Without Cascade

This might be more of a SQL question but I'm curious how to handle this correctly in Laravel.

I have a model called ForumThread that I have linked to my User model

Schema::create('forum_threads', function (Blueprint $table) {
    //
    $table->integer('user_id')->nullable()->unsigned();
    
    $table->foreign('user_id')
             ->references('id')
            ->on('users');
}

I didn't set a cascading delete as I want to be able to delete a User without deleting the ForumThread but leaving it orphaned.

However, I get a constraint violation when I try to delete the User:

Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`tcl`.`forum_threads`, CONSTRAINT `forum_threads_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))

Please can someone tell me the correct way to achieve what I'm trying to acheive. Thank you for your help.

26 Apr
2 months ago

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

And this is exactly what I had forgotten I had done with all the others. Thank you!

Still unclear how the implicit binding should actually work, but will come back to that at another time...

Thanks!

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

Yeah I will, I have loads of these to do though so will try and get to the bottom of the naming.

Thanks!

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

I wondered if it was a naming issue too so I've tried forumCategory, forum_category and just category but all have the same effect.

However, what you have suggested above does work, so perhaps I should just stick with that.

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

@snapey

As always you are correct, and I get the logic, but I'm confused why this works on other models. Here is my admin model:

Routes:

    Route::group(['middleware' => 'admin', 'prefix' => 'admin', 'as' => 'admin.'], function (): void {
        Route::view('/', 'admin.dashboard')->name('dashboard');
        Route::resource('administrator', 'AdminController');
        Route::group(['prefix' => 'forum', 'as' => 'forum.'], function (): void {
            Route::resource('category', 'Forum\ForumCategoryController');
        });
|        | POST      | admin/administrator                      | admin.administrator.store    | App\Http\Controllers\[email protected]                                  | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator                      | admin.administrator.index    | App\Http\Controllers\[email protected]                                  | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator/create               | admin.administrator.create   | App\Http\Controllers\[email protected]                                 | web,authenticated,admin
                         |
|        | DELETE    | admin/administrator/{administrator}      | admin.administrator.destroy  | App\Http\Controllers\[email protected]                                | web,authenticated,admin
                         |
|        | PUT|PATCH | admin/administrator/{administrator}      | admin.administrator.update   | App\Http\Controllers\[email protected]                                 | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator/{administrator}      | admin.administrator.show     | App\Http\Controllers\[email protected]                                   | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator/{administrator}/edit | admin.administrator.edit     | App\Http\Controllers\[email protected]                                   | web,authenticated,admin

AdminController:

    public function update(AdminFormRequest $request): RedirectResponse
    {
        return $this->upsert($request);
    }

    public function upsert(AdminFormRequest $request): RedirectResponse
    {
        $admin = $request->route('administrator');
    dd($admin);
        //
    }

This does dump the model and not the ID.

25 Apr
2 months ago

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

Yup that all works fine and the edit form is populated with all the relevant information I'd expect to see.

The use of id is pretty standard with route model binding, and it's the primary key they demonstrate with in the docs:

https://laravel.com/docs/5.8/routing#route-model-binding

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

it's also implicit in the route model binding so my edit() method is actually:

    public function edit(ForumCategory $category): View
    {
        return view('forum.category.edit', compact('category'));
    }

which in turn is called from another index view with:

    {{ Form::model($category, array('route' => array('admin.forum.category.edit', $category->id)))}}
            @method('GET') 
            @csrf 
            <button id="submit" type="submit">View/Edit</button> 
    {{ Form::close() }}

bwrigley started a new conversation Problem Picking Up Model In Route Model Binding

I know I'm doing something stupid here as I have this working for other models, but can't figure out why it's not working for my ForumCategory model.

The problem is that in my edit() method in my controller I expect to implicitly collect the model from the Request, but I am just getting the id.

The route nesting is slightly complex for this model which may be part of the issue:

Routes:

    Route::group(['middleware' => 'admin', 'prefix' => 'admin', 'as' => 'admin.'], function (): void {
        Route::view('/', 'admin.dashboard')->name('dashboard');
        Route::resource('administrator', 'AdminController');
        Route::group(['prefix' => 'forum', 'as' => 'forum.'], function (): void {
            Route::resource('category', 'Forum\ForumCategoryController');
        });
        Route::post('impersonate', 'Admin\[email protected]')->name('impersonate.store');
    });

$php artisan route:list :

|        | POST      | admin/forum/category                     | admin.forum.category.store   | App\Http\Controllers\Forum\[email protected]                    | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category                     | admin.forum.category.index   | App\Http\Controllers\Forum\[email protected]                    | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category/create              | admin.forum.category.create  | App\Http\Controllers\Forum\[email protected]                   | web,authenticated,admin
                         |
|        | PUT|PATCH | admin/forum/category/{category}          | admin.forum.category.update  | App\Http\Controllers\Forum\[email protected]                   | web,authenticated,admin
                         |
|        | DELETE    | admin/forum/category/{category}          | admin.forum.category.destroy | App\Http\Controllers\Forum\[email protected]                  | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category/{category}          | admin.forum.category.show    | App\Http\Controllers\Forum\[email protected]                     | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category/{category}/edit     | admin.forum.category.edit    | App\Http\Controllers\Forum\[email protected]                     | web,authenticated,admin

Edit view:

{{ Form::model($category, array('route' => array('admin.forum.category.update', $category->id))) }}

    @method('PUT')

    @include ('forum.category.partials.categoryForm')

    <button id="submit" type="submit">Update</button>

{{ Form::close() }}

ForumCategoryController:

    public function update(ForumCategoryFormRequest $request): RedirectResponse
    {
        return $this->upsert($request);
    }

    public function upsert(ForumCategoryFormRequest $request): RedirectResponse
    {

    dd($request->route('category'));

//

this dumps the id, not the model as I was expecting.

Any thoughts appreciated.

12 Apr
2 months ago

bwrigley left a reply on Using Laravel Cashier How Can I Access User's Subscribed Plan?

@BRAUNSON - @braunson

Thanks for your reply. Yes I'm familiar with how to get the user's subscription but it's the plan I'm interested in and then the product associated to plan.

In my set up, different products grant the user different permissions, so when I set a user's permissions I need to access the product they have purchased which would ideally be:

$user->subscription->plan->product;

I can get to the product but through a more convoluted route and was wondering if there was a better way.

Thanks!

06 Apr
2 months ago

bwrigley started a new conversation Laravel Cashier Access Current Plan Model

Is there a way to access the plan a User is subscribed to directly from the description?

$user->subscription('default')->stripe_plan;

gives me the id and I can look up the plan from that, but I'm curious why there isn't

$plan = $user->subscription('default')->plan;

perhaps there is something similar that I haven't spotted?

04 Apr
2 months ago

bwrigley left a reply on Notifications Using UUID

Just stumbled across this whilst looking for something else.

UUID is far more scalable for larger applications potentially over distributed databases

25 Mar
3 months ago

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon - thank you again. I was just getting the elements of code together to show you and I'm embarrassed to admit that I found out that createUser() is actually getting called by a job which is basically running in realtime so I didn't spot it!

So sorry to waste your time! Now I just need to find out how I can fake this job in every test but not other jobs. But that's a different problem!

Thank you again. So grateful.

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon so sorry to come back to this, but now I have written the full test, I seem to be having the same issue again.

Firstly, I have updated Mockery as you suggested and that is now all working fine as we had it above.

Now the real test looks like this:

        $mock = Mockery::mock(IntercomGateway::class)->makePartial();
        $mock->shouldReceive('createUser')->andReturn('mocked');
        $this->app->instance(IntercomGateway::class, $mock);

        $user = $this->createTestUser();

        $loginCredentials = [
            'email' => $user->email,
            'password' => 'wrong_password',
        ];

        $this->fromUrl(route('login'))
            ->post('/login', $loginCredentials)
            ->assertEIMErrorExists('login_fail');

the createTestUser() method sets up a user just for the purposes of the test, but also creates a new Intercom user which is the bit I want to mock as it's not relevant to the test.

However, running the above produces Original method executed as before.

So sorry to ask you again, but would appreciate your thoughts on what I might be doing wrong here.

Thank you!

22 Mar
3 months ago

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

Thank you so much, so grateful for your time. No way I would have figured that one out!

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon you genius, it works! Thank you.

However I do not understand that at all!

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

my method looks like this at the moment:

    public function createUser(User $user): void
    {

        dump('Original method executed');
        try{
            Intercom::users()->create([
                'user_id' => $user->id,
                'name' => $user->fullName,
                'email' => $user->email,
                'phone' => optional($user->profile)->getPrimaryTelephone(),
                'signed_up_at' => $user->created_at,
            ]);
        } catch (\GuzzleHttp\Exception\ConnectException $e) {
            Log::Alert('Cannot connect to Intercom ' . $e->getCode() . ' ' . $e->getMessage());
        }
    }

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@TaI've tried yours and I get mocked too. So it's something to do with my class?

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon thanks for such a fast response!

I have tried that and now I get null but I think I should get mocked no?

bwrigley started a new conversation Using Mockery, But Original Method Still Being Called

I'm sure I'm missing something really obvious here as I have mocks with Mockery working elsewhere in my code.

This is what I have in a test currently:

        $mock = Mockery::mock(IntercomGateway::class)->makePartial();
        $mock->shouldReceive('createUser')->andReturn('nothing');
        $this->app->instance(IntercomGateway::class, $mock);

        $gw = new IntercomGateway;

        dump($gw->createUser(new \App\User));

I have also added this line to the original createUser()

dump('Original method executed');

When I run the test I see Original method executed

Any thoughts on what I might have missed? Thank you

21 Mar
3 months ago

bwrigley started a new conversation 'Risky' Test

I have a feature test which is giving me this error:

Test code or tested code did not (only) close its own output buffers

which I understand can be triggered by the buffer being larger/smaller than expected. But I have no idea who to begin tracing that.

I have isolated the issue to this call in my test calls followRedirects()

        $loginCredentials = [
            'email' => $this->email,
            'password' => $this->password,
        ];

        $this->fromUrl(route('dashboard'))
            ->post('/login', $loginCredentials)
            ->followRedirects()
            ->assertViewIs('subscription.editCard');

which is a macro that I use across ~150 tests with no problem

        TestResponse::macro('followRedirects', function ($testCase = null) use ($test) {
            $response = $this;
            $testCase = $testCase ?: $test;

            while ($response->isRedirect()) {
                $response = $testCase->get($response->headers->get('Location'));
            }

            return $response;
        });

if I dump the contents of the HTML produced by my post I get a pretty standard redirect:

<!DOCTYPE html>\n
<html>\n
    <head>\n
        <meta charset="UTF-8" />\n
        <meta http-equiv="refresh" content="0;url=http://domain.test/dashboard" />\n
\n
        <title>Redirecting to http://domain.test/dashboard</title>\n
    </head>\n
    <body>\n
        Redirecting to <a href="http://domain.test/dashboard">http://domain.test/dashboard</a>.\n
    </body>\n
</html>

if I dump after the followRedirects() I see the HTML output of the view I'm expecting.

Does anyone know how I can trace the issue here?

13 Mar
3 months ago

bwrigley left a reply on How To Test Mail Sent From Notification

@nash that's the one brilliant thank you! Yes I wasn't wanting to test the actual mail was sent but couldn't figure out how to do what you have just suggested.

Thanks again!

12 Mar
3 months ago

bwrigley left a reply on How To Test Mail Sent From Notification

@nash

Thanks for your reply, and that's great for testing Notifications but Notification::assertSentTo() is channel agnostic.

So if the user's preferred channel is nexmo then Notification::assertSentTo() will be true or if the channel is 'database' then 'Notification::assertSentTo()' is also true.

I specifically want to make sure that I am routing instant notifications via the mail channel if that's the channel the user has accepted, so I need to test if an email has been sent.

I hope that makes sense.

bwrigley left a reply on Testing Notifications

In the end I have opted to create a TestNotification Notification, which feels ugly, but it does the trick.

bwrigley started a new conversation How To Test Mail Sent From Notification

My users can specify if they have Notifications sent to them instantly or to be notified daily of any pending Notifications.

This all works fine with manual testing, so now I'd like to write a feature test around this. This is what I have so far:

    public function test_instant_notification_is_sent(): void
    {
        Mail::fake();

        $this->user = $user = factory(User::class)->create();

        $this->user->update(
            [
                'notification_frequency' => 'instant',
                'notification_method' => 'email',
                'notification_email' => $this->email,
            ]
        );

        $this->user->save();

        $email = ['subject' => 'subject', 'data' => 'data'];
        $sms = 'Test Notification';
        $database = ['some' => 'data'];

        $this->user->notify(new TestNotification($email, $sms, $database));

        Mail::assertSent(MailMessage::class);

and I've written the TestNotification to go with it:

class TestNotification extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @param string[] $email
     * @param string $sms
     * @param string[] $database
     * @return void
     */
    public function __construct(array $email, string $sms, array $database)
    {
        $this->email = $email;
        $this->sms = $sms;
        $this->database = $database;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function via($notifiable): array
    {
        return [$notifiable->preferredNotificationMethod()];
    }

    /**
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function toDatabase($notifiable): array
    {
        return $this->database;
    }

    /**
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function toNexmo($notifiable): array
    {
        return (new NexmoMessage)
            ->content($this->sms);
    }

    /**
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->greeting($this->email['subject'])
            ->line('Testing Notifications!')
            ->line('Thank you for using our application!');
    }
}

So my test fails because, as I now see, MailMessage is not a mailable and I think Mail::fake() and the mail assertions only work with mailables.

Incidentally, if I remove Mail::fake() the email is sent fine.

How have others managed to test this please without actually sending the email.

bwrigley left a reply on Scheduler Caching Job?

Solved! Embarrassingly I forgot:

protected $frequency;

11 Mar
3 months ago

bwrigley left a reply on Testing Notifications

@PAULCLARKE - No problem. Thanks again!

bwrigley left a reply on Scheduler Caching Job?

So I was doing this:

$schedule->job(new NotificationsPending('morning'))->dailyAt('8:00');

which is what the docs say, and causes the error above.

however, this works instead:

$schedule->call(function (): void {
       $job = new NotificationsPending('evening');
       $job->handle();
 })->dailyAt('8:00');

bwrigley left a reply on Testing Notifications

@paulclarke

Out of interest, how do you test instant notifications? As these don't touch the database DatabaseNotification can't be used.

Thanks!

bwrigley left a reply on Testing Notifications

@borisu

Thank you, but for the functionality to test, I want to make sure that it works with or without notifications and with different user settings.

Thanks though!

bwrigley left a reply on Testing Notifications

@PAULCLARKE - Brilliant! Thank you. It was the DatabaseNotification class I couldn't find I was trying 'Notification'.

Slightly cheeky to point out that this also needs:

'notifiable_type'  => 'App\User'

in the factory or passed in on creation.

Thank you again!

bwrigley started a new conversation Testing Notifications

I have set up my project so that a user can specify if they would like their notifications instantly, daily, weekly and via sms, email, or just in their dashboard.

So if a user has said 'instantly' then I fire off an sms/email right away.

If they have said daily/weekly, then I run a job with the Scheduler which checks (at the right time) if a user has any unread notifications and send them an email/sms alerting them to how many unread notifications they have.

All good. Now I want to write a test around this.

This means I need to seed the database with some notifications against a user's account, but I can't figure out a good, clean, reproducible way to do this. I'm sure I'm just being dumb. The options I've thought of are:

  1. In my test, create a number of instances of an existing Notification class. This feels wrong as my test shouldn't expect the existence of a certain Notification class to run.
  2. Create a TestNotification class. This is doable, but seems messy and under which namespace would it be stored? Along with the other Notification classes?
  3. Seed the database directly with raw queries. Again feels messy not to use the mechanisms that are built-in for notifications, and what would I store in the type field?
  4. Write a factory around the Notification model. I tried this but couldn't get it to work, but maybe this is the correct way?

I would really appreciate your thoughts.

Thanks

Ben

08 Mar
3 months ago

bwrigley left a reply on Scheduler Caching Job?

not sure if this helps but I've noticed something else.

here are my constructor and my handle:

    public function __construct(string $frequency)
    {
        $this->frequency = $frequency;
        Log::debug('here ' . $this->frequency);
    }

    public function handle(): void
    {
        $dbUsers = DB::table('users')
            ->leftJoin('notifications', 'users.id', '=', 'notifications.notifiable_id')
            ->selectRaw('users.id as id , count(*) as pending')
            ->where('notification_frequency', $this->frequency)
            ->where('read_at', null)
            ->groupby('users.id')
            ->get();

        Log::Info('SCHEDULE JOB: NotificationsPending:' . $this->frequency . ' sending to ' . count($dbUsers) . ' users');

        foreach ($dbUsers as $dbUser) {
            $user = User::find($dbUser->id);
            $user->notify(new PendingNotifications($dbUser->pending, $this->frequency));

        }
    }

I've cleared the cache by restarting homestead to I know this is the code that's running.

The Log in the constructor works fine, but the handle fails

Undefined property: App\Jobs\NotificationsPending::$frequency which refers to the line in the query.

bwrigley left a reply on Scheduler Caching Job?

hi @snapey,

Yes it terminates when I run in tinker.

07 Mar
3 months ago

bwrigley started a new conversation Scheduler Caching Job?

Please forgive me if this is an obvious question but does the scheduler cache classes? Or perhaps unique to using homestead?

The reason I ask is that my scheduler runs a job which has standard __construct() and handle() methods.

If I change something in the __construct() method like add a logging command, it logs fine next time the scheduler runs. However, if I change code in the handle() method nothing changes until I reboot homestead.

I can't find anyone else with this issue. Am I missing something obvious?

Thanks!

06 Mar
3 months ago

bwrigley left a reply on Selecting Model On Join With Count

@JLRDW - ah! I wasn't grouping! Thanks (which means I don't need to query pending)

bwrigley left a reply on Selecting Model On Join With Count

@ANONYMOUSE703 - no it's not but it sql you can usually query against an aliased field I thought

bwrigley left a reply on Selecting Model On Join With Count

@RDELORIER - no pending column, only the alias created in the statement

05 Mar
3 months ago

bwrigley started a new conversation Selecting Model On Join With Count

I'm sure I'm missing something obvious in eloquent here.

My users can set one of 5 notification frequencies instant,morning,afternoon,weekly,never

I want to set up a morning, afternoon and weekly job to notify the user of how many unread notifications they have to view.

so my morning job currently looks like this:

    public function handle(): void
    {
        $users = User::where('notification_frequency', 'morning');

        foreach ($users as $user) {
            if (count($user->unreadNotifications) !== 0) {
                //alert user to how many notifications are pending
            }
        }
    }

but I feel I should be able to do this efficiently in one query e.g.

    public function handle(): void
    {
        $users = DB::table('users')
            ->leftJoin('notifications', 'users.id', '=', 'notifications.notifiable_id')
            ->selectRaw('count(notifications.id) as pending')
            ->where('notification_frequency', 'morning')
            ->where('read_at', null)
            ->where('pending', '>', 0)
            ->get();

        foreach ($users as $user) {
            //notify the user of pending notifications
        }
    }

however this gives a SQL error of Unknown column 'pending' in 'where clause'

Can someone show me how to do this better in eloquent?

(and perhaps it's not more efficient to do it this way anyway and I should stick with first option which is more readable?)

01 Mar
3 months ago

bwrigley left a reply on Starting ChromeDriver Manually

I can't explain why, but it seems that if I use the full path, then everything works fine.

/home/vagrant/Code/mysite/vendor/laravel/dusk/bin/chromedriver-linux --port=9515
26 Feb
4 months ago

bwrigley started a new conversation Starting ChromeDriver Manually

I'm writing a little artisan command line tool that I need to browse my site with javascript execution.

Initially I was calling:

```\Laravel\Dusk\TestCase::startChromeDriver()````

This started the driver fine running on port 9515 running as user vagrant (I'm using homestead)

But when I tried to use it I would get an error:

Failed to connect to localhost port 9515: Connection refused

If I started it manually on the command line:

$ ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515

The driver started fine again running on port 9515 as user vagrant, but this time my tool runs fine.

I want to be able to start the ChromeDriver from within the tool. So I also tried:

         $process->start(); ```

Connection was refused again.

Any idea why this works when I start it from the command line but not within my artisan command
10 Feb
4 months ago

bwrigley started a new conversation Caching Issues During Feature Tests

I'm having an issue with what I think is caching during a unit test.

The block of my test that I'm struggling with is:

$subscriptionDetails = [
        'plan' => $plan->nickname,
        'stripeToken' => $token,
    ];

$this->post('/subscription/create', $subscriptionDetails)
        ->assertEIMSuccessExists('stripe_subscribe_success')
        ->followRedirects()
        ->assertViewIs('dashboard');

$this->assertTrue($this->user->currentPlan() === $plan->nickname);

assertEIMSuccessExists() is just a little macro that checks for a custom message in the session - this passes anyway.

the last two assertions fail:

 -'dashboard'
+'subscription.edit'

This is because some middleware checks if the user has a current subscription, if not redirects them to the subscription page.

Now if I alter the test thus:

$subscriptionDetails = [
        'plan' => $plan->nickname,
        'stripeToken' => $token,
    ];

$this->post('/subscription/create', $subscriptionDetails)
        ->assertEIMSuccessExists('stripe_subscribe_success');

$this->user = $this->user->fresh();

$this->assertTrue($this->user->currentPlan() === $plan->nickname);

everything passes fine. Presumable because it's cached. Ok I could just leave it like the latter, but then I can't test if the final view is the correct one, so what I really need is something like:

$this->post('/subscription/create', $subscriptionDetails)
        ->assertEIMSuccessExists('stripe_subscribe_success')
        ->clearCaches() //or similar
        ->followRedirects()
        ->assertViewIs('dashboard');

$this->assertTrue($this->user->currentPlan() === $plan->nickname);

but I have no idea how to write that macro, if it could even work that way.

Would love your thoughts!