bwrigley

bwrigley

Member Since 2 Years Ago

Experience Points
6,010
Total
Experience

3,990 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-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 2
6,010 XP
11 Sep
1 week ago

bwrigley left a reply on Binding Interface To Service Container Not Resolving In Constructor

@sti3bas yeah, I think that's the bit I'm misunderstanding.

So I thought by binding the Interface in the way I have...

        $this->app->bind(
            '/App/Contracts/AnalyticsClient',
            '/App/Services//GoogleAnalyticsClient'
        );

...that was enough for automatic injection. But perhaps there is more I need to add to the container than this to resolve the class?

bwrigley left a reply on Binding Interface To Service Container Not Resolving In Constructor

@sti3bas Sorry, I'm not explaining myself well.

I'm sure there is a way that once a class is bound in the container, I can just type-hint it in the arguments of the constructor of any class and it would bring it into existence. Similar to Route Model Binding.

10 Sep
1 week ago

bwrigley left a reply on Binding Interface To Service Container Not Resolving In Constructor

@sti3bas Thanks again

In that case I have misunderstood the docs here

https://laravel.com/docs/5.8/container#binding-interfaces-to-implementations

Do I need to add something else to the container that this doesn't specify? Sorry for the dumb questions

bwrigley left a reply on Binding Interface To Service Container Not Resolving In Constructor

@sti3bas thanks again for your reply.

in tinker I am simply typing $report = new \App\Reports\ProfileViewReport;

If I understand correctly I the constructor should create AnalyticsClient implicitly so I do not need to pass one as an argument?

bwrigley left a reply on Binding Interface To Service Container Not Resolving In Constructor

Thank you for your reply, and apologies, that was just a typo when writing up my question.

In my application they are correct.

bwrigley started a new conversation Binding Interface To Service Container Not Resolving In Constructor

Hello. I'm sure I'm doing something dumb here and not understanding the docs properly.

I'm trying to write some classes to manage reporting over the Google Analytics API, but want to make sure I can swap it out later if needs be.

So I have my interface:

namespace App\Contracts;


interface AnalyticsClient
{

    public function run();
    public function buildQuery();
    // etc
}

And I have a Google Analytics class which implements this:

namespace App\Services;

use App\Contracts\AnalyticsClient;

class GoogleAnalyticsClient implements AnalyticsClient
{

    public function run()
    {
        //
    }

    public function buildQuery()
    {
        //
    }
}

And a service provider:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ReportServiceProvider extends ServiceProvider
{

    public function register()
    {
        $this->app->bind(
            '/App/Contracts/AnalyticsClient',
            '/App/Services//GoogleAnalyticsClient'
        );
    }
}

...which is registered in app.php

'providers' => [
    
    //,

    App\Providers\ReportServiceProvider::class,

So my expectation is now that I can just call am analytics client into existence in my report classes:

namespace App\Reports;

use App\Contracts\AnalyticsClient;

class ProfileViewReport
{

    private $analyticsClient;

    public function __construct(AnalyticsClient $analyticsClient)
    {
        $this->analyticsClient = $analyticsClient;
    }

But sadly, when I run this in tinker:

TypeError: Too few arguments to function App/Reports/ProfileViewReport::__construct(), 0 passed in

I suspect I need to do something else to enable the Analytics client to be created implicitly?

Thank you in advance for helping with this.

13 Aug
1 month ago

bwrigley left a reply on Adding Items To An Array In A Collection

This works well, but actually I think that a Collection of Collections is probably more elegant. Is it bad form to mark my own solution as 'Best Answer' 😫

12 Aug
1 month ago

bwrigley left a reply on Adding Items To An Array In A Collection

Sorry our messages overlapped:

So I tried what you suggested in tinker:

$coll->put(
     'metrics', 
     array_push($coll->get('metrics'), 'something')
);

PHP Notice: Only variables should be passed by reference in /home/vagrant/Code/everyone-in-mindeval()'d code on line 1

bwrigley left a reply on Adding Items To An Array In A Collection

Some trial and error suggests that maybe I shouldn't be adding metrics as an array?

$coll = new Collection([
    'metrics' => collect(),
    'dimensions' => collect(),
]);

then I can push with:

$coll->get('metrics')->push('a new thing');

Would be grateful to know if this really is best practice?

bwrigley started a new conversation Adding Items To An Array In A Collection

I'm sure I'm missing something obvious here but I can't seem to find the answer in the docs anywhere.

If I create a new collection like :

$coll = new Collection([
    'metrics' => [],
    'dimensions' => [],
]);

how do I push new values into the metrics array?

10 Jul
2 months ago

bwrigley left a reply on Mockery Executing Original Method

hi @talinon,

Ah, thank you, that makes a lot of sense, and I am definitely guilty of being someone who thought there was some magic going on 😳, I assumed that at the point that classes were loaded into memory, the mock would override any methods with the same names.

Thanks for that link too, I had already spotted that and sadly he resolves without actually doing any mocking in the end.

Thanks again for your help.

bwrigley left a reply on Mockery Executing Original Method

hi @johnbraun

Thank you for your reply.

Actually yes I'm already doing that. I have a StripeGateway interface which I use in exactly that way and the mocking works fine.

The actual problem hits with the Cashier methods that are added to the User model.

So in my SubscriptionController my store method essentially subscribes the user to a new product and then sets up provisioning of any relevant permissions/dates etc.

For testing purposes the stripe subscription is mocked through the StripeGateway class as you suggest. When the provisioning happens I need to grab the correct start date for this new subscription. This happens on my User model:

   public function setProvisioningDate()
    {
    //

    $startPeriod = $this->subscription('plan')->asStripeSubscription()->current_period_start;
        $nextProvision = Carbon::now()->timestamp($startPeriod)->addMonth();
        $this->next_provisioning = $nextProvision;

    //
    }

the asStripeSubscription() line is a Cashier method which in turn calls the Stripe API.

so what I was actually trying to do was to mock $user->setProvisioningDate() so this line never executes in the test, but the method is being executed anyway.

Apologies, this sounds confusing now, but I hope it makes sense!

bwrigley left a reply on Mockery Executing Original Method

@talinon thank you for replying!

I'm not sure I fully get what you mean here sorry. The line:

$plan = Plan::whereNickname($request->plan)->first();

is just a line in my normal code base in a controller called SubscriptionController where I handle users subscribing to product through stripe. This is not a line in my test class.

My test replicates a user creating a new subscription by posting the correct form to a specific route, which then activates the controller.

In my test, I don't want it to actually hit Stripe's API so I'm mocking out various methods on a couple of controllers to emulate the Stripe responses.

After my controller gets the correct response, it provisions various permissions to the user depending on the package bought.

It's this provisioning that I want to test. However, I am still hitting Stripe each time.

I am just replicating the issue with this testMethod() example. I hope that makes sense?

09 Jul
2 months ago

bwrigley started a new conversation Mockery Executing Original Method

I'm struggling to understand why this isn't working as I'm sure I've had it working before.

In my feature test:

$product =  factory(Product::class)->create(['name' => 'Test', 'stripe_id' => 'Test']);
$plan =  factory(Plan::class)->create(['stripe_id' => 'Test', 'product_id' => $product->id]);

$mockPlan = Mockery::mock(Plan::class);
$this->app->instance(Plan::class, $mockPlan);
$mockPlan->shouldReceive('testMethod')
    ->andReturn('mock executed');

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

$this->fromUrl('/dashboard')
    ->post('/subscription/create', $subscriptionDetails)
    ->assertRedirect('/dashboard');

in my Plan class:

public function testMethod()
{
    return 'original method executed';
}

in a class that is executed during the test:

    $plan = Plan::whereNickname($request->plan)->first();

    dump($plan->testMethod());

the output is always: "original method executed"

I'm sure I'm missing something obvious, but can't see anything in the Laravel or Mockery docs that show to do anything different.

Thanks for your help in advance!

19 Jun
3 months 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
3 months 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 ForumCategory is not defined as "admin only". So I am using shouldBeSearchable() :

    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
3 months 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 I'm being dumb here and would appreciate some guidance.

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
3 months 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 ForumThread 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?

I call a url:

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

Middleware is invoked:

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

Dump results are:

ParameterBag {#44 ▼ #parameters: []

09 May
4 months 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
4 months 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
4 months 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 (`dev`.`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
4 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

Thank you for looking further, even after coming up with a solution, I really appreciate that.

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
4 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. It can be edited but for now I'm using ID with all of my bindings and would like to keep it like that just now.

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');
        });

$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
5 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
5 months ago

bwrigley started a new conversation Using Laravel Cashier How Can I Access User's Subscribed Plan?

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

I can get the plan id from the Subscription and look that up like this

$planId = $user->subscription('default')->stripe_plan;
$plan = Plan::where('stripe_id',$planId)->first();

but I'm curious why there isn't a relationship method

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

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

04 Apr
5 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
5 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. Thank you.

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 test passes fine, but still calls the original createUser() method in the IntercomGateway class, not the mocked one.

So when I run this test I now see the dump Original method executed as before. 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.

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

Thank you!

22 Mar
6 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

@talinon I'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 feature tests.

This is what I have in a test currently:

        $mock = Mockery::mock(IntercomGateway::class)->makePartial();
        $mock->shouldReceive('createUser')->andReturn('mocked');
        $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
6 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

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

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

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

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

followRedirects()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
6 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
6 months ago

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

@nash

Thanks for your reply, and sorry I wasn't clear. I specifically want to test the mail channel and whilst Notification::fake() is great for testing Notifications, 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
6 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('morning');
       $job->handle();
 })->dailyAt('8:00');