rodrigo.pedra

Developer at WebMagic

Member Since 6 Years Ago

São Carlos, Brazil

Experience Points
390,690
Total
Experience

0 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
1147
Lessons
Completed
Best Reply Awards
414
Best Reply
Awards
  • start your 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-in-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 Created with Sketch.

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • lifer Created with Sketch.

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • evangelist 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.

  • Community Pillar

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

Level 50
390,690 XP
May
08
9 hours ago
Activity icon

Replied to Laravel 8 Shopping Cart Quantity Display Issue

If dd($request->quantity) is right, shouldn't this line be:

$qty = $request->post("quantity");

instead of:

$qty = $request->post("qty");
Activity icon

Replied to Optimising View Composer To Avoid N+1

You can try sharing the variable instead:

View::composer(['layouts.nav', 'store.basket'], function () {
    if (View::shared('basket')) {
        return; // already shared
    }

    if (Basket::cookieExists()) {
        $basket = Basket::retrieve();
    }

    View::share('basket', $basket);
});
Activity icon

Replied to Ways To Optimize CORS Handshake/preflight Request

As you can see below

Where?

Also, if you trust the API your frontend is consuming (it seems you own both the frontend and backend code base), you can try configuring your nginx/apache as a reverse proxy on those API requests to serve those requests from a path on the same domain which the frontend is served, and skip those OPTIONS requests altogether.

Activity icon

Replied to How To Add Cache Control Header To CORS Response

Now I see.

Couple of things here:

1 - The shipped cache.headers middleware only works for GET and HEAD methods

You can see in the middleware's source code the first thing it does is to skip if the method is not "cacheable":

if (! $request->isMethodCacheable() || ! $response->getContent()) {
    return $response;
}

reference: https://github.com/laravel/framework/blob/29b019774b746c90534465f1b949767707d49245/src/Illuminate/Http/Middleware/SetCacheHeaders.php#L24-L26

The ->isMethodCacheable() is part of the underlying Symfony\Component\HttpFoundation\Request which Illuminate\Http\Request extends from:

/**
 * Checks whether the method is cacheable or not.
 *
 * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
 *
 * @return bool True for GET and HEAD, false otherwise
 */
public function isMethodCacheable()
{
    return \in_array($this->getMethod(), ['GET', 'HEAD']);
}

reference: https://github.com/symfony/http-foundation/blob/aa02fc063bc72f2505d90623665c866691acb70f/Request.php#L1484-L1494

So even if next point in this response was not true, applying this middleware wouldn't make a change for OPTIONS requests.

2 - The shipped \Fruitcake\Cors\HandleCors::class middleware bails out OPTIONS requests for undefined routes

A route is defined a pair of:

  • Request method
  • Request path

As you don't have a defined OPTIONS route for every API path on your app, route middleware won't run for OPTIONS requests because there is no matching route.

To make it more clear, assume you have the following route defined:

Route::get('/foo', function () {
    // ...
});

If you make a POST request to /api/foo you would get a 404, as no route is defined for the pair [POST, /api/foo].

So no further middleware is run for undefined routes. The OPTIONS requests works for defined routes, because the \Fruitcake\Cors\HandleCors::class middleware handle these preflight requests for defined routes, and it is defined as a global HTTP middleware on your app's HTTP Kernel.

But Laravel itself won't run any route middleware on undefined routes.

Workaround

So is there a workaround?

Well, if you consider the comment on Symfony's Request ->isMethodCacheable() as an indicative the only GET and HEAD requests should be cached, based on the RFC mentioned on its comment, I am not sure if a browser or CloudFlare would cache a response from an OPTIONS request even if correct headers, or just ignore it.

But you can add a catch-all OPTIONS route to your ./routes/api.php file that adds the desired cache manually and check if CloudFlare and browsers behave well.

I tried adding the route below, and testing from a HTTP client, like postman or curl, the headers are added by Laravel correctly:

// Add a Cache-Control header for any OPTIONS  request
Route::options('{any}', function () {
    return response()->noContent()->withHeaders([
        'Cache-Control' => 'public, max_age=3600',
    ]);
})->where('any', '.*');

But again, I can't tell you if either CloudFlare or any browsers will respect that for a request that is not a GET or a HEAD request.

If CloudFlare does respect it, you have the benefit of it not reaching your app for every OPTIONS request.

But if a browser just ignore it, it will still make an OPTIONS request for every pre-flight request. Although CloudFlare response should be very fast if it has the response cached.

Hope this helps.

May
07
1 day ago
Activity icon

Awarded Best Reply on Extracting Style CSS From Vue Components

https://laravel-mix.com/docs/6.0/examples#extract-vue-single-file-component-css-to-its-own-file

mix.js('src/app.js', 'js')
   .vue({ extractStyles: 'css/vue-styles.css' });

If you are using Vue 2 don't forget to tell mix about that:

mix.js('src/app.js', 'js')
   .vue({
    version: 2,
    extractStyles: 'css/vue-styles.css'
  });

Note: assuming Mix version 6 is being used.

Activity icon

Replied to Replace Shortcode With Html Of Specific Data

You're welcome! Have a nice day =)

Activity icon

Awarded Best Reply on Replace Shortcode With Html Of Specific Data

You're welcome.

What is $placeholders

When you use preg_match_all, the $matches variable is populated with:

  • all matches from the full pattern on index 0
  • all matches from the first capture group on index 1
  • all matches from the second capture group on index 2
  • and so on

In our regular expression we only have one capture group (\d+) so the $matches variable will be populated with:

  • all matches from the full pattern on index 0
  • all matches from the (\d+) capture group on index 1

Assume we have this HTML input:

<p>Some text goes here, lorem ipsum lorem ipsum</p>
[Figures 3]
[Figures 10]

So when running this line:

$results = preg_match_all('/\[Figures (\d+)]/', $contentget, $matches);

The $matches variable will have this content:

[
  0 => [
    0 => "[Figures 3]"
    1 => "[Figures 10]"
  ]
  1 => [
    0 => "3"
    1 => "10"
  ]
]

So we can use the results on the index 0 to replace the short codes more easily.

This line just de-structure the $matches array elements into two variables:

[$placeholders, $figureids] = $matches;

So:

  • $placeholders will be assigned to the array in $matches index 0 (the full short codes), and
  • $figureids will be assigned to the array in $matches index 1 (the figure ids)

This notation of auto assigning is somewhat recent in PHP, but you could do this "multi-assignment" from an array before (I think even in PHP 4, not sure now) using the list() operator:

list($placeholders, $figureids) = $matches;

If i store html data directly in db for each figure id then i can directly fetch right ? instead of this?

Yes.

Activity icon

Replied to Replace Shortcode With Html Of Specific Data

You're welcome.

What is $placeholders

When you use preg_match_all, the $matches variable is populated with:

  • all matches from the full pattern on index 0
  • all matches from the first capture group on index 1
  • all matches from the second capture group on index 2
  • and so on

In our regular expression we only have one capture group (\d+) so the $matches variable will be populated with:

  • all matches from the full pattern on index 0
  • all matches from the (\d+) capture group on index 1

Assume we have this HTML input:

<p>Some text goes here, lorem ipsum lorem ipsum</p>
[Figures 3]
[Figures 10]

So when running this line:

$results = preg_match_all('/\[Figures (\d+)]/', $contentget, $matches);

The $matches variable will have this content:

[
  0 => [
    0 => "[Figures 3]"
    1 => "[Figures 10]"
  ]
  1 => [
    0 => "3"
    1 => "10"
  ]
]

So we can use the results on the index 0 to replace the short codes more easily.

This line just de-structure the $matches array elements into two variables:

[$placeholders, $figureids] = $matches;

So:

  • $placeholders will be assigned to the array in $matches index 0 (the full short codes), and
  • $figureids will be assigned to the array in $matches index 1 (the figure ids)

This notation of auto assigning is somewhat recent in PHP, but you could do this "multi-assignment" from an array before (I think even in PHP 4, not sure now) using the list() operator:

list($placeholders, $figureids) = $matches;

If i store html data directly in db for each figure id then i can directly fetch right ? instead of this?

Yes.

Activity icon

Replied to Extracting Style CSS From Vue Components

@piljac1 I see that, but as the workflow he is describing is:

  • use mix to extract component CSS
  • remove hashes from generated CSS
  • walk through each Vue file to remove <style> tags

Removing the scoped attribute by careful search and replace, seems easier than the second step by a lot.

At least in my opinion.

Activity icon

Replied to Extracting Style CSS From Vue Components

Saw your last response after sending mine.

If you are using tailwind, why you have those style tags anyway?

Isn't easier to just replace the classes generated in the <style> tags by tailwind built-in utility classes?

I remember Adam Whatan talking about being reluctant on adding @\apply , but ended up adding it fearing people would be reluctant from the mindset shift needed to embrace utility classes.

Activity icon

Replied to Extracting Style CSS From Vue Components

I got it first time.

As it seems you don't conflicting class names you can remove the scoped attribute, to avoid manually removing the hashes on your first generated css file from style extraction.

What I was referring to is this scenario:

Component Foo

<template>
    <div class="green">
        I am a green!
    </div>
</template>

<style lang="scss">
.green {
    @apply text-green-600;
}
</style>

Component Bar

<template>
    <div class="green">
        I am also green! But lighter!
    </div>
</template>

<style lang="scss">
.green {
    @apply text-green-100; /* different shade of green */
}
</style>

Now when extracting you are going to get this CSS:

.green {
    color: #000055;
}

.green {
    color: #0000FF;
}

note: sample color values

And as those classes share the same name, and same selector specificity, the latter will override the former.

Reference: https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity

So be sure to either have different class names, or to lightly scope every component with a root class-name, for example:

Component Foo:

<template>
    <div class="foo">
        <div class="green">
            I am green!
        </div>
    </div>
</template>

<style lang="scss">
.foo {
    .green {
        @apply text-green-600;
    }
}
</style>

Component Bar

<template>
    <div class="bar">
        <div class="green">
            I am also green! But lighter!
        </div>
    </div>
</template>

<style lang="scss">
.bar {
    .green {
        @apply text-green-100;
    }
}
</style>

So you get an output without class names conflicts:

.foo .green {
    color: #000055;
}

.bar .green {
    color: #0000FF;
}

As I said before, scoped styles and component hashes are for this reason.

If your concern is to avoid having Vue generating the hashes in runtime to increase performance, don't worry, those hashes are calculated at compilation time, not at runtime (when using Vue Single File Components).

Otherwise CSS extraction would not work at all as the hashes would never match.

Activity icon

Replied to Extracting Style CSS From Vue Components

Random ids are added if @vkronlein used the scoped attribute (which I recommend)

if you use only:

<style lang="scss">
/* ... */
</style>

Random ids are not added.

So if you choose to not scope your styles be sure to choose distinct class names to avoid conflicts between components (which is what scoped styles tries to prevent).

I would take @piljac1 advice and keep the styles into their components. But I can see where you are heading and depending on the size/complexity of your project it can be manageable from there.

Activity icon

Replied to Replace Shortcode With Html Of Specific Data

I had a similar task last week. This is how I solved it (simplified):

<?php

use App\Models\Figure;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;

Route::get('/', function () {
    $contentget = <<<HTML
<p>Some text goes here, lorem ipsum lorem ipsum</p>
[Figures 10]
HTML;

    $results = preg_match_all('/\[Figures (\d+)]/', $contentget, $matches);

    if ($results === false || $results === 0) {
        return $contentget;
    }

    [$placeholders, $figureids] = $matches;

    $figures = Figure::query()
        ->whereKey($figureids)
        ->get();

    if ($figures->isEmpty()) {
        return $contentget;
    }

    foreach ($placeholders as $index => $placeholder) {
        $figure = $figures->find($figureids[$index] ?? 0);

        if ($figure) {
            $content = Str::of('<img src="##URL##" alt="##ALT##" class="img-responsive"><p>##TEXT##<p>')
                ->replace('##URL##', $figure->url)
                ->replace('##ALT##', $figure->text)
                ->replace('##TEXT##', $figure->text);

            $contentget = str_replace($placeholder, $content, $contentget);
        }
    }

    return $contentget;
});

Hope this helps!

Activity icon

Replied to Getting Only The Latest Record In Query

Thanks!

I spent around 10 years working on the financial industry, on business units not IT, so we had a lot of limitations from IT on what we could install in our computers.

So basically I only had VBA and SQL Server around, had to learn some tricks to get the work done =)

Activity icon

Replied to Extracting Style CSS From Vue Components

Thanks @piljac1 =)

@vkronlein if you are still using Mix 5 I found the docs for it too:

https://laravel-mix.com/docs/5.0/options#laravel-mix-options

But from memory I think you can't use a custom css output file name.

Activity icon

Replied to How To Add Cache Control Header To CORS Response

To increase cache time of your CORS OPTIONS requests change the max_age value on your app's ./config/cors.php file:

<?php

return [

    'paths' => ['api/*', 'sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 3600, // <<<<< CHANGED here

    'supports_credentials' => false,

];
Activity icon

Replied to Getting Only The Latest Record In Query

You're welcome! Have a nice day =)

Activity icon

Replied to Extracting Style CSS From Vue Components

https://laravel-mix.com/docs/6.0/examples#extract-vue-single-file-component-css-to-its-own-file

mix.js('src/app.js', 'js')
   .vue({ extractStyles: 'css/vue-styles.css' });

If you are using Vue 2 don't forget to tell mix about that:

mix.js('src/app.js', 'js')
   .vue({
    version: 2,
    extractStyles: 'css/vue-styles.css'
  });

Note: assuming Mix version 6 is being used.

Activity icon

Replied to Getting Only The Latest Record In Query

Example for MySQL 8, avoiding duplicates with matching criteria:

$date = Carbon::createFromDate(2021, 2, 8)->format('Y-m-d H:i:s');

$items = Model::query()
    ->select((new Model())->qualifyColumn('*'))
    ->joinSub(
        Model::query()
            ->select('id')
            ->selectRaw('ROW_NUMBER() OVER (PARTITION BY example_id ORDER BY published_at DESC) AS rowid')
            ->whereDate('published_at', '<', $date)
            ->whereNull('removed_at')
            ->whereNotNull('example_id')
            ->where('filled', false),
        'limiter',
        function (JoinClause $join) {
            $join->on((new Model())->getQualifiedKeyName(), '=', 'limiter.id');
            $join->where('limiter.rowid', 1);
        })
    ->orderBy('example_id')
    ->get();

You can replace:

  • (new Model())->qualifyColumn('*') by table_name.*
  • (new Model())->getQualifiedKeyName() by table_name.id

I used these functions as I am assuming your model is actually named something else, and as such I can't guess its table name.

This should also work for other RDBMS that support window functions (tested with SQLite and it also works).

Activity icon

Replied to Getting Only The Latest Record In Query

You're welcome!

After looking at @dhakalsandeep solution, the proposed solution is very similar, albeit their solution is using two queries whereas mine is using a single one.

But the idea is exactly the same.

Therefore, if you want to mark that response as the best one, be my guest.

One thing to note: if two, or more, records match all the criteria (example_id, filled, removed_at) and share the exact same published_at date and time, you could have duplicate records for the same example_id if this published_at value is the most recent one.

Depending of the database you are using you can mitigate that. If you are using MySQL 8, I can show you an example.

Activity icon

Awarded Best Reply on Getting Only The Latest Record In Query

Try this:

    $date = Carbon::createFromDate(2021, 2, 8)->format('Y-m-d H:i:s');

    $items = Model::query()
        ->whereIn(
            DB::raw('(example_id, published_at)'),
            Model::query()
                ->select('example_id')
                ->selectRaw('MAX(published_at)')
                ->groupBy('example_id')
                ->whereDate('published_at', '<', $date)
                ->whereNull('removed_at')
                ->whereNotNull('example_id')
                ->where('filled', false)
        )
        ->orderBy('example_id')
        ->get();

If you don't want to use the DB façade, use this:

    $date = Carbon::createFromDate(2021, 2, 8)->format('Y-m-d H:i:s');

    $items = Model::query()
        ->whereIn(
            new Expression('(example_id, published_at)'),
            Model::query()
                ->select('example_id')
                ->selectRaw('MAX(published_at)')
                ->groupBy('example_id')
                ->whereDate('published_at', '<', $date)
                ->whereNull('removed_at')
                ->whereNotNull('example_id')
                ->where('filled', false)
        )
        ->orderBy('example_id')
        ->get();

And add this import:

use Illuminate\Database\Query\Expression;

There was a recent PR to add a ->whereTupleIn() method that would avoid using the raw expression in WHERE IN, but it got rejected,

Activity icon

Replied to Getting Only The Latest Record In Query

Try this:

    $date = Carbon::createFromDate(2021, 2, 8)->format('Y-m-d H:i:s');

    $items = Model::query()
        ->whereIn(
            DB::raw('(example_id, published_at)'),
            Model::query()
                ->select('example_id')
                ->selectRaw('MAX(published_at)')
                ->groupBy('example_id')
                ->whereDate('published_at', '<', $date)
                ->whereNull('removed_at')
                ->whereNotNull('example_id')
                ->where('filled', false)
        )
        ->orderBy('example_id')
        ->get();

If you don't want to use the DB façade, use this:

    $date = Carbon::createFromDate(2021, 2, 8)->format('Y-m-d H:i:s');

    $items = Model::query()
        ->whereIn(
            new Expression('(example_id, published_at)'),
            Model::query()
                ->select('example_id')
                ->selectRaw('MAX(published_at)')
                ->groupBy('example_id')
                ->whereDate('published_at', '<', $date)
                ->whereNull('removed_at')
                ->whereNotNull('example_id')
                ->where('filled', false)
        )
        ->orderBy('example_id')
        ->get();

And add this import:

use Illuminate\Database\Query\Expression;

There was a recent PR to add a ->whereTupleIn() method that would avoid using the raw expression in WHERE IN, but it got rejected,

Apr
17
3 weeks ago
Activity icon

Replied to Dynamic X-data And X-on Variables For Reusable Components

Thanks for the heads up, here it is updated:

@props(['toggle'])
<div x-data="{ '{{ $toggle }}': sessionStorage.getItem('{{ $toggle }}') }">
    <button x-on:click="{{ $toggle }} = !{{ $toggle }}" type="button" {{ $attributes->merge(['class' => 'flex-shrink-0 group relative rounded-full inline-flex items-center justify-center h-5 w-10 cursor-pointer focus:outline-none  focus:ring-offset-2 focus:ring-white" role="switch" aria-checked="false']) }}>
        <span class="sr-only">Use setting</span>
        <span aria-hidden="true" class="pointer-events-none absolute bg-white w-full h-full rounded-md"></span>
        <span aria-hidden="true" class="{{ session($toggle) === false ? 'bg-gray-200' : 'bg-lh-yellow' }}  pointer-events-none absolute h-4 w-9 mx-auto rounded-full transition-colors ease-in-out duration-200"></span>
        <span aria-hidden="true" class="{{ session($toggle) === false ? 'translate-x-0' : 'translate-x-5' }} pointer-events-none absolute left-0 inline-block h-5 w-5 border border-gray-200 rounded-full bg-white shadow transform ring-0 transition-transform ease-in-out duration-200"></span>
    </button>
</div>

What results this gives you? Which errors does it trigger?

Apr
16
3 weeks ago
Activity icon

Replied to Dynamic X-data And X-on Variables For Reusable Components

@props(['toggle'])
<div x-data="{ '{{ $toggle }}': sessionStorage.getItem('{{ $toggle }}') }">
    <button x-on:click="{{ $toggle }} = !{{ $toggle }}" type="button" {{ $attributes->merge(['class' => 'flex-shrink-0 group relative rounded-full inline-flex items-center justify-center h-5 w-10 cursor-pointer focus:outline-none  focus:ring-offset-2 focus:ring-white" role="switch" aria-checked="false']) }}>
        <span class="sr-only">Use setting</span>
        <span aria-hidden="true" class="pointer-events-none absolute bg-white w-full h-full rounded-md"></span>
        <span aria-hidden="true" class="{{ session('REPLACE_ME') === false ? 'bg-gray-200' : 'bg-lh-yellow' }}  pointer-events-none absolute h-4 w-9 mx-auto rounded-full transition-colors ease-in-out duration-200"></span>
        <span aria-hidden="true" class="{{ session('REPLACE_ME') === false ? 'translate-x-0' : 'translate-x-5' }} pointer-events-none absolute left-0 inline-block h-5 w-5 border border-gray-200 rounded-full bg-white shadow transform ring-0 transition-transform ease-in-out duration-200"></span>
    </button>
</div>
Activity icon

Replied to Issue With Laravel Paginator

How is your APP_URL set on your production .env file?

It should be using you domain, instead of localhost.

Also it seems you have a trailing forward slash on your APP_URL configuration. Don't use that to avoid double forward slashes on the first path segment.

For example, instead of

APP_URL=https://example.com/

Use this:

APP_URL=https://example.com

Third, add these to your nginx configuration:

location / {
    proxy_redirect                      off;
    proxy_set_header Host               $host;
    proxy_set_header X-Real-IP          $remote_addr;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto  https;
    proxy_read_timeout                  60;
    proxy_connect_timeout               60;
    proxy_pass                          http://127.0.0.1:7000;
    proxy_set_header X-VerifiedViaNginx yes;
}

The last option is optional

Apr
15
3 weeks ago
Activity icon

Replied to Does Laravel Support Different Types Of Database Systems?

It sounds you are after an Enitity-Attribute-Value data-model

https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model

From a Google search (keywords: "laravel EAV") I found these results:

Apr
14
3 weeks ago
Activity icon

Replied to Pass Down Blade Props Anonymous Component To Its Children

You're welcome! Have a nice day =)

Activity icon

Awarded Best Reply on Pass Down Blade Props Anonymous Component To Its Children

Try using class based components, so you can call view()->share(...)

Tabs component

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Tabs extends Component
{
    public string $tabs;
    public string $active;

    public function __construct(string $active)
    {
        $this->tabs = \uniqid('tabs');
        $this->active = $active;

        \view()->share('tabs', $this->tabs);
    }

    public function render()
    {
        return \view('components.tabs');
    }
}
<div x-data="{active: '{{ $active }}'}">
    <ul class="nav nav-tabs">
        @stack($tabs)
    </ul>

    <div class="tab-content">
        {{ $slot }}
    </div>
</div>

Tab component

@props(['id', 'label'])

@push(\view()->shared('tabs'))
    <li class="nav-item" role="presentation">
        <a class="nav-link" x-bind:class="{'active': active === '{{ $id }}'}"
           x-on:click.prevent="active = '{{ $id }}'"
           href="#"
           id="{{ $id }}-tab"
           role="tab"
           aria-controls="{{ $id }}"
           x-bind:aria-selected="active === '{{ $id }}' ? 'true' : 'false'">
            {{ $label }}
        </a>
    </li>
@endpush

<div class="tab-pane" x-bind:class="{'active': active === '{{ $id }}'}"
     id="{{ $id }}"
     role="tabpanel"
     aria-labelledby="{{ $id }}-tab">
    {{ $slot }}
</div>

Usage

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Sample</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.js" defer></script>
</head>

<body>
<div class="container">
    <x-tabs active="home">
        <x-tab id="home" label="Home">
            Home
        </x-tab>

        <x-tab id="about" label="About">
            About
        </x-tab>
    </x-tabs>
</div>
</body>
</html>

EDIT simplified the Tab component to be a anonymous component

Activity icon

Replied to Composer Keeps Reverting To V1 On Digital Ocean Droplet

You’re welcome! Have a nice day

Apr
13
3 weeks ago
Activity icon

Awarded Best Reply on Composer Keeps Reverting To V1 On Digital Ocean Droplet

On forge, navigate to your server (not the site/app, but the server) and click the "Scheduler" option on the sidebar.

URL is something like this:

https://forge.laravel.com/servers/XXXXXX#/scheduler

Where XXXXXX is your forge's server ID

You'll see a scheduled cron task to self-update composer. This task is added by default by forge to every server.

Probably yours is set like this:

/usr/local/bin/composer self-update --1

Create a new scheduled task to update to version , and delete this one.

Activity icon

Replied to Composer Keeps Reverting To V1 On Digital Ocean Droplet

On forge, navigate to your server (not the site/app, but the server) and click the "Scheduler" option on the sidebar.

URL is something like this:

https://forge.laravel.com/servers/XXXXXX#/scheduler

Where XXXXXX is your forge's server ID

You'll see a scheduled cron task to self-update composer. This task is added by default by forge to every server.

Probably yours is set like this:

/usr/local/bin/composer self-update --1

Create a new scheduled task to update to version , and delete this one.

Apr
12
3 weeks ago
Activity icon

Replied to Custom User Provider

Tha is because GenericUser has a hardcoded remember_token reference.

Either extend this class to remove that reference and use the extended class instead of GenericUser, or add an empty remember_token field to the payload like this:

<?php

namespace App;

use Illuminate\Auth\GenericUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Support\Facades\Http;

class ExternalApiUserProvider implements UserProvider
{
    private Encrypter $encrypter;

    public function __construct(Encrypter $encrypter)
    {
        $this->encrypter = $encrypter;
    }

    public function retrieveById($identifier)
    {
        try {
            $payload = $this->encrypter->decrypt($identifier);
            $payload = \json_decode($payload, true, \JSON_THROW_ON_ERROR);
        } catch (DecryptException $exception) {
            return null;
        } catch (\JsonException $exception) {
            return null;
        }

        $payload['id'] = $identifier;

        /////////////////////////////////////////////////////
        // ADDED HERE
        /////////////////////////////////////////////////////
        $payload['remember_token'] = '';

        return new GenericUser($payload);
    }

    public function retrieveByToken($identifier, $token)
    {
        return null;
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
    }

    public function retrieveByCredentials(array $credentials)
    {
        if (! array_key_exists('email', $credentials)) {
            return null;
        }

        if (! array_key_exists('password', $credentials)) {
            return null;
        }

        $response = Http::post('https://example.com/authenticate', [
            'email' => $credentials['email'],
            'password' => $credentials['password'],
        ]);

        if (! $response->ok()) {
            return null;
        }

        $payload = $response->json();

        try {
            $identifier = \json_encode($payload, \JSON_THROW_ON_ERROR);

            $payload['id'] = $this->encrypter->encrypt($identifier);
        } catch (\JsonException $exception) {
            return null;
        } catch (EncryptException $exception) {
            return null;
        }

        /////////////////////////////////////////////////////
        // ADDED HERE
        /////////////////////////////////////////////////////
        $payload['remember_token'] = '';

        return new GenericUser($payload);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        return true;
    }
}
Activity icon

Replied to Custom User Provider

Yes, this is how GenericUser is implemented.

Also Models are implemented like this too in Laravel.

They keep the attributes in an array and allow to access they properties through the __get() magic method.

Activity icon

Replied to Custom User Provider

Try thie code below. And as always, read the comments in it.

<?php

namespace App;

use Illuminate\Auth\GenericUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Support\Facades\Http;

class ExternalApiUserProvider implements UserProvider
{
    private Encrypter $encrypter;

    public function __construct(Encrypter $encrypter)
    {
        $this->encrypter = $encrypter;
    }

    public function retrieveById($identifier)
    {
        try {
            $payload = $this->encrypter->decrypt($identifier);
            $payload = \json_decode($payload, true, \JSON_THROW_ON_ERROR);
        } catch (DecryptException $exception) {
            return null;
        } catch (\JsonException $exception) {
            return null;
        }

        $payload['id'] = $identifier;
        
        // Another strategy, more secure, is to use the encrypted credentials
        // and retrieve the user from the external API on every request.
        // This way if the users gets unauthorized on the external API
        // you don't need to wait on their session to expire to reflect
        // this on your local system

        return new GenericUser($payload);
    }

    public function retrieveByToken($identifier, $token)
    {
        return null;
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
    }

    public function retrieveByCredentials(array $credentials)
    {
        // I broke into two if clauses to avoid line breaks here
        // you can use an "or" operator if you prefer to use
        // a single if
        if (! array_key_exists('email', $credentials)) {
            return null;
        }

        if (! array_key_exists('password', $credentials)) {
            return null;
        }

        // moved this check here as you want to use the response
        // as the user data
        $response = Http::post('https://example.com/authenticate', [
            'email' => $credentials['email'],
            'password' => $credentials['password'],
        ]);

        if (! $response->ok()) {
            return null;
        }

        // gets user data
        $payload = $response->json();

        // we'll use the encrypted user data as the GenericUser identifier
        // this will be saved into session and used in retrieveById()
        // method to hydrate the user on a next request
        try {
            $identifier = \json_encode($payload, \JSON_THROW_ON_ERROR);
            
            // You can skip ecnrypting and decrypting as the session
            // is already encrypted. So effectively this identifier will
            // be double encrypted.
            $payload['id'] = $this->encrypter->encrypt($identifier);
        } catch (\JsonException $exception) {
            return null;
        } catch (EncryptException $exception) {
            return null;
        }

        return new GenericUser($payload);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        // as if retrieveByCredentials() returns null this method never gets called
        // and as we moved the authentication to the external provider there
        // we can assume if this method gets called it is because a user
        // was successfully retrieved from the external API

        return true;
    }
}
Activity icon

Awarded Best Reply on Custom User Provider

Ok, I might have been carried over by my response on the other thread that was posted on a very close time.

So let's add a custom user provider.

Step 1 - Create a custom user provider

Create the class below in your project.

Please read the comments in the code carefully.

<?php

namespace App;

use Illuminate\Auth\GenericUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Facades\Http;

class ExternalApiUserProvider implements UserProvider
{
    public function retrieveById($identifier)
    {
        // This method is called from subsequent calls until the session expires.
        //
        // As you don't have a local users database we are going
        // to assume the identifier saved into the session is fine.
        //
        // Session cookies are encrypted by default
        //
        // This avoid calling the external service on every navigation.
        //
        // The downside is that if the user is not authorized anymore
        // in the external service, you won't know until their session expires.
        //
        // Ideally you should set a lower session duration so user
        // gets logged out quickier.
        //
        // An alternative is to save encrypted the user's credentials
        // and call the external service every time.
        //
        // But that would make a external API call on every request,
        // making your app slower. But is the most secure way.
        //
        // If you want I can make an modified version exemplifying 
        // how you could do this.
        return new GenericUser([
            'id' => $identifier,
            'email' => $identifier,
        ]);
    }

    public function retrieveByToken($identifier, $token)
    {
        return null;
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
    }

    public function retrieveByCredentials(array $credentials)
    {
        if (! array_key_exists('email', $credentials)) {
            return null;
        }

        // GenericUser is a class from Laravel Auth System
        return new GenericUser([
            'id' => $credentials['email'],
            'email' => $credentials['email'],
        ]);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        if (! array_key_exists('password', $credentials)) {
            return false;
        }

        // This is a simplified usage of Laravel's HTTP Client to call the external API
        // You might need to send more info to the external service.
        // Please refer to the HTTP Client docs to learn how to use it properly.
        $response = Http::post('https://example.com/authenticate', [
            // $user is the GenericUser instance created in
            // the retrieveByCredentials() method above.
            'email' => $user->email,
            'password' => $credentials['password'],
        ]);

        return $response->ok();
    }
}

HTTP Client docs: https://laravel.com/docs/8.x/http-client

Step 2 - Register the User Provider with the Auth Manager

Add this to your app's AuthServiceProvider's boot method:

<?php

namespace App\Providers;

use App\ExternalApiUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();
        
        // you can choose a different name
        Auth::provider('external', function ($app, array $config) {
            return new ExternalApiUserProvider();
        });
    }
}

Step 3 - Configure ./config/auth.php

<?php

return [

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'external',  // <<< CHANGED HERE
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        /// ADDED here
        'external' => [
            'driver' => 'external',
        ],
    ],

// ... keep the other configs
];

Step 4 - [email protected]

On first login you should call Auth::attempt() instead of Auth::check().

Auth::check() is meant to be used after a user is already logged in.

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;

class CustomLoginController extends Controller
{
    // ... other methods

    public function doLogin()
    {
        if (Auth::attempt(request()->only('email', 'password'))) {
            return redirect('secret');
        }

        return back()->withErrors([
            'email' => 'invalid credentials',
        ]);
    }
}

Step 5 - Guard the secret route

Don't forget to guard the secret route:

Route::get('/secret', [SecretController::class, 'show'])->middeware('auth');

Hope this helps.

Apr
11
3 weeks ago
Activity icon

Replied to Pass Down Blade Props Anonymous Component To Its Children

Try using class based components, so you can call view()->share(...)

Tabs component

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Tabs extends Component
{
    public string $tabs;
    public string $active;

    public function __construct(string $active)
    {
        $this->tabs = \uniqid('tabs');
        $this->active = $active;

        \view()->share('tabs', $this->tabs);
    }

    public function render()
    {
        return \view('components.tabs');
    }
}
<div x-data="{active: '{{ $active }}'}">
    <ul class="nav nav-tabs">
        @stack($tabs)
    </ul>

    <div class="tab-content">
        {{ $slot }}
    </div>
</div>

Tab component

@props(['id', 'label'])

@push(\view()->shared('tabs'))
    <li class="nav-item" role="presentation">
        <a class="nav-link" x-bind:class="{'active': active === '{{ $id }}'}"
           x-on:click.prevent="active = '{{ $id }}'"
           href="#"
           id="{{ $id }}-tab"
           role="tab"
           aria-controls="{{ $id }}"
           x-bind:aria-selected="active === '{{ $id }}' ? 'true' : 'false'">
            {{ $label }}
        </a>
    </li>
@endpush

<div class="tab-pane" x-bind:class="{'active': active === '{{ $id }}'}"
     id="{{ $id }}"
     role="tabpanel"
     aria-labelledby="{{ $id }}-tab">
    {{ $slot }}
</div>

Usage

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Sample</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.js" defer></script>
</head>

<body>
<div class="container">
    <x-tabs active="home">
        <x-tab id="home" label="Home">
            Home
        </x-tab>

        <x-tab id="about" label="About">
            About
        </x-tab>
    </x-tabs>
</div>
</body>
</html>

EDIT simplified the Tab component to be a anonymous component

Activity icon

Replied to Laravel DOMPDF

Is the error the same after changing that?

Apr
10
4 weeks ago
Activity icon

Awarded Best Reply on How To Get Condition Based Data Between Nested Relationsiop Hasmany In Laravel

Try this:

$enrol_info = Enrollments::with([
    'student_info',
    'schedule_weeks' => function ($query) {
        // add the nested relation here
        $query->with('week_classes');

        $query->where('enrollment_weeks.order', '>=', '1');
        $query->where('enrollment_weeks.order', '<=', '3');
    },
]);
Activity icon

Awarded Best Reply on Decimal Lenght

From MySQL docs:

The declaration syntax for a DECIMAL column is DECIMAL(M,D). The ranges of values for the arguments are as follows:

  • M is the maximum number of digits (the precision). It has a range of 1 to 65.
  • D is the number of digits to the right of the decimal point (the scale). It has a range of 0 to 30 and must be no larger than M.

https://dev.mysql.com/doc/refman/8.0/en/precision-math-decimal-characteristics.html

So in your case you could use: DECIMAL(18, 9)

I created a fiddle to test this:

create table `table`
(
    `number` DECIMAL(18, 9) NOT NULL
);

insert into `table` VALUES (000000000.000000000);
insert into `table` VALUES (100000000.000000001);
insert into `table` VALUES (999999999.999999999);

select * from `table`;

Results are:

number
0.000000000
100000000.000000001
999999999.999999999

link to fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e1a4161b54ea101c8290c9e998ea70c8

Activity icon

Replied to How To Get Condition Based Data Between Nested Relationsiop Hasmany In Laravel

Try this:

$enrol_info = Enrollments::with([
    'student_info',
    'schedule_weeks' => function ($query) {
        // add the nested relation here
        $query->with('week_classes');

        $query->where('enrollment_weeks.order', '>=', '1');
        $query->where('enrollment_weeks.order', '<=', '3');
    },
]);
Activity icon

Replied to Laravel DOMPDF

Try changing this:

$pdf = PDF::loadView('invoices.print', ['invoice' => $invoice] );
Activity icon

Replied to How To Style The Font Differently If It Is In Arabic Language?

You're welcome.

Now that I read again more carefully, your problem wasn't in the form input, was when rendering the saved contents.

In that case your solution is fine. No need to add JavaScript for that.

On the good side, you got one more trick on your tool belt.

Have a nice day!

Activity icon

Replied to Decimal Lenght

Not sure what you meant. The third parameter is the number of decimal places.

/**
 * Create a new decimal column on the table.
 *
 * @param  string  $column
 * @param  int  $total
 * @param  int  $places
 * @param  bool  $unsigned
 * @return \Illuminate\Database\Schema\ColumnDefinition
 */
public function decimal($column, $total = 8, $places = 2, $unsigned = false)
{
    return $this->addColumn('decimal', $column, compact('total', 'places', 'unsigned'));
}

The fourth parameter is to enforce the field to accept only unsigned numbers.

So set it to true if don't want negative numbers like this:

$table->decimal('number', 18, 9, true);

Or use the more expressive ->unsigned() method:

$table->decimal('number', 18, 9)->unsigned();

If your app allows negative numbers, you don't need to add more precision.

i added a example on the fiddle (it generates a new URL)

https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=c71bdd1694befed38f246e6322c680e4

Activity icon

Awarded Best Reply on How To Style The Font Differently If It Is In Arabic Language?

First answer from Google (query: "javascript check arabic characters")

https://stackoverflow.com/a/4446309

Based on that here is a sample:

<!DOCTYPE html>
<html>
<head>
    <style>
    .arabic {
        /* Don't know any arabic font name */
        font-family: 'Other', sans-serif;
        color: red;
    }
    </style>
</head>

<body>
<form action="">
    <input type="text">

    <button type="submit">send</button>
</form>

<script>
document.addEventListener('keyup', function (event) {
    if (!['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
        return; // ignore non-input elements
    }

    var text = event.target.value;

    // @see https://stackoverflow.com/a/4446309
    if (/[\u0600-\u06FF\u0750-\u077F]/.test(text)) {
        event.target.classList.add('arabic');
    } else {
        event.target.classList.remove('arabic');
    }
});
</script>
</body>
</html>
Activity icon

Replied to Decimal Lenght

On a laravel migration you could define it like this:

$table->decimal('number', 18, 9);
Activity icon

Replied to Decimal Lenght

From MySQL docs:

The declaration syntax for a DECIMAL column is DECIMAL(M,D). The ranges of values for the arguments are as follows:

  • M is the maximum number of digits (the precision). It has a range of 1 to 65.
  • D is the number of digits to the right of the decimal point (the scale). It has a range of 0 to 30 and must be no larger than M.

https://dev.mysql.com/doc/refman/8.0/en/precision-math-decimal-characteristics.html

So in your case you could use: DECIMAL(18, 9)

I created a fiddle to test this:

create table `table`
(
    `number` DECIMAL(18, 9) NOT NULL
);

insert into `table` VALUES (000000000.000000000);
insert into `table` VALUES (100000000.000000001);
insert into `table` VALUES (999999999.999999999);

select * from `table`;

Results are:

number
0.000000000
100000000.000000001
999999999.999999999

link to fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e1a4161b54ea101c8290c9e998ea70c8

Activity icon

Replied to How To Style The Font Differently If It Is In Arabic Language?

First answer from Google (query: "javascript check arabic characters")

https://stackoverflow.com/a/4446309

Based on that here is a sample:

<!DOCTYPE html>
<html>
<head>
    <style>
    .arabic {
        /* Don't know any arabic font name */
        font-family: 'Other', sans-serif;
        color: red;
    }
    </style>
</head>

<body>
<form action="">
    <input type="text">

    <button type="submit">send</button>
</form>

<script>
document.addEventListener('keyup', function (event) {
    if (!['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
        return; // ignore non-input elements
    }

    var text = event.target.value;

    // @see https://stackoverflow.com/a/4446309
    if (/[\u0600-\u06FF\u0750-\u077F]/.test(text)) {
        event.target.classList.add('arabic');
    } else {
        event.target.classList.remove('arabic');
    }
});
</script>
</body>
</html>
Activity icon

Replied to Custom User Provider

Ok, I might have been carried over by my response on the other thread that was posted on a very close time.

So let's add a custom user provider.

Step 1 - Create a custom user provider

Create the class below in your project.

Please read the comments in the code carefully.

<?php

namespace App;

use Illuminate\Auth\GenericUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Facades\Http;

class ExternalApiUserProvider implements UserProvider
{
    public function retrieveById($identifier)
    {
        // This method is called from subsequent calls until the session expires.
        //
        // As you don't have a local users database we are going
        // to assume the identifier saved into the session is fine.
        //
        // Session cookies are encrypted by default
        //
        // This avoid calling the external service on every navigation.
        //
        // The downside is that if the user is not authorized anymore
        // in the external service, you won't know until their session expires.
        //
        // Ideally you should set a lower session duration so user
        // gets logged out quickier.
        //
        // An alternative is to save encrypted the user's credentials
        // and call the external service every time.
        //
        // But that would make a external API call on every request,
        // making your app slower. But is the most secure way.
        //
        // If you want I can make an modified version exemplifying 
        // how you could do this.
        return new GenericUser([
            'id' => $identifier,
            'email' => $identifier,
        ]);
    }

    public function retrieveByToken($identifier, $token)
    {
        return null;
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
    }

    public function retrieveByCredentials(array $credentials)
    {
        if (! array_key_exists('email', $credentials)) {
            return null;
        }

        // GenericUser is a class from Laravel Auth System
        return new GenericUser([
            'id' => $credentials['email'],
            'email' => $credentials['email'],
        ]);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        if (! array_key_exists('password', $credentials)) {
            return false;
        }

        // This is a simplified usage of Laravel's HTTP Client to call the external API
        // You might need to send more info to the external service.
        // Please refer to the HTTP Client docs to learn how to use it properly.
        $response = Http::post('https://example.com/authenticate', [
            // $user is the GenericUser instance created in
            // the retrieveByCredentials() method above.
            'email' => $user->email,
            'password' => $credentials['password'],
        ]);

        return $response->ok();
    }
}

HTTP Client docs: https://laravel.com/docs/8.x/http-client

Step 2 - Register the User Provider with the Auth Manager

Add this to your app's AuthServiceProvider's boot method:

<?php

namespace App\Providers;

use App\ExternalApiUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();
        
        // you can choose a different name
        Auth::provider('external', function ($app, array $config) {
            return new ExternalApiUserProvider();
        });
    }
}

Step 3 - Configure ./config/auth.php

<?php

return [

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'external',  // <<< CHANGED HERE
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        /// ADDED here
        'external' => [
            'driver' => 'external',
        ],
    ],

// ... keep the other configs
];

Step 4 - [email protected]

On first login you should call Auth::attempt() instead of Auth::check().

Auth::check() is meant to be used after a user is already logged in.

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;

class CustomLoginController extends Controller
{
    // ... other methods

    public function doLogin()
    {
        if (Auth::attempt(request()->only('email', 'password'))) {
            return redirect('secret');
        }

        return back()->withErrors([
            'email' => 'invalid credentials',
        ]);
    }
}

Step 5 - Guard the secret route

Don't forget to guard the secret route:

Route::get('/secret', [SecretController::class, 'show'])->middeware('auth');

Hope this helps.

Apr
09
4 weeks ago
Activity icon

Replied to Connecting One Laravel App To Another Over API In Scheduled Task

You’re welcome! Have a nice day