Our Black Friday sale is now live! All individual subscriptions are 50% OFF. This week only!

rodrigo.pedra

rodrigo.pedra

Lead Developer at avaliadora.com.br

Member Since 6 Years Ago

São Carlos, Brazil

Experience Points
346,000
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
1097
Lessons
Completed
Best Reply Awards
355
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.

Level 50
346,000 XP
Nov
25
50 minutes ago
Activity icon

Replied to How To Pass Laravel Echo Event To Livewire Method

I guess this might work:

window.Echo.private('orders')
        .listen('OrderShipped', (e) => {
            console.log(e.order.name);
            Livewire.emit('notifyNewOrder', e.order.name);
        });

Reference: https://laravel-livewire.com/docs/2.x/events#from-js

Activity icon

Replied to HTTP Request In Laravel

You're welcome! Have a nice day =)

Activity icon

Replied to 405 Http Error On Post Request

Thanks! But without you asking the code first I wouldn't think on suggesting to check the request's headers.

it is a team effort =)

Best part is @mganjkhani was able to find the issue.

Nov
24
1 day ago
Activity icon

Replied to Standalone Markdown View

Where are you using the results in $participantsTable?

How are you converting the markdown output to HTML?

Are you using any package for that? If so which one?

Can't you add <link rel="stylesheet" href="..."> to the CSS where you are outputting the processed markdown table?

Activity icon

Replied to Get Results Where Smallest Child Value Is X

I would add a hasOne alternative to constraint the related records from seminar_data with the least date.

There are a few alternatives on how to do it, I added some comments outlining the pros/cons of each one.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\JoinClause;

class Seminar extends Model
{
    use HasFactory;

    // regular relation, use to get all seminar_data records
    // add/update/remove new seminar_data records, etc.
    public function seminarData()
    {
        return $this->hasMany(SeminarData::class);
    }

    // First alternative on custom relation to constraint
    // only the related seminar_date record with the least date
    //
    // ***IMPORTANT:*** won't work on MySQL 5.*, as MySQL previous
    // to version 8 didn't support multiple columns comparision
    // with the IN operator in WHERE clauses
    //
    // Will work with `->whereHas()` and eager loading
    public function seminarStartInSub()
    {
        return $this->hasOne(SeminarData::class)
            ->whereRaw('(seminar_data.seminar_id, seminar_data.date) IN (SELECT seminar_id, MIN(date) FROM seminar_data GROUP BY seminar_id)');
    }

    // Second alternative on custom relation to constraint
    // only the related seminar_date record with the least date
    //
    // Should work on MySQL 5.*, but I didn't test against it
    //
    // Will work with `->whereHas()` and eager loading
    public function seminarStartExists()
    {
        return $this->hasOne(SeminarData::class)
            ->selectRaw('seminar_data.*')
            ->whereExists(function (Builder $query) {
                $query
                    ->from('seminar_data', 'inner')
                    ->selectRaw('inner.seminar_id, MIN(inner.date) AS date')
                    ->groupBy('inner.seminar_id')
                    ->whereColumn('inner.seminar_id', 'seminar_data.seminar_id')
                    ->havingRaw('date = seminar_data.date');
            });
    }

    // Third alternative on custom relation to constraint
    // only the related seminar_date record with the least date
    //
    // Should work on MySQL 5.*, but I didn't test against it
    //
    // ***IMPORTANT:*** will work with eager loading, but
    // WON'T work with ->whereHas(...) as ->whereHas(...)
    // will remove the JOIN clause that constraint the records
    public function seminarStartJoinSub()
    {
        return $this->hasOne(SeminarData::class)
            ->joinSub(
                SeminarData::query()
                    ->selectRaw('seminar_id, MIN(date) AS date')
                    ->groupBy('seminar_id'),
                'inner', // sub-query alias
                function (JoinClause $join) {
                    $join->on('inner.seminar_id', 'seminar_data.seminar_id');
                    $join->on('inner.date', 'seminar_data.date');
                }
            );
    }
}

Note you only need one of these ->hasOne(...) relations, I listed three alternatives so you can assess which works better for you.

The third alternative won't work with ->whereHas(...) due to how Laravel changes the relation constraints prior to checking for existence.

I added that alternative to address a "why not using a join to constraint?" question beforehand.

I assume you also have a SeminarData model like this:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class SeminarData extends Model
{
    use HasFactory;

    public function seminar()
    {
        return $this->belongsTo(Seminar::class);
    }
}

I tested it out using MySQL 8 with these data:

select `id`, `name` from `seminars`;

+--+---------+
|id|name     |
+--+---------+
|1 |Seminar A|
|2 |Seminar B|
|3 |Seminar C|
+--+---------+
select `id`, `seminar_id`, `date` from `seminar_data`;

+--+----------+----------+
|id|seminar_id|date      |
+--+----------+----------+
|1 |1         |2020-11-01|
|2 |1         |2020-11-25|
|3 |2         |2020-11-03|
|4 |2         |2020-11-04|
|5 |3         |2020-11-25|
|6 |3         |2020-11-26|
+--+----------+----------+

And with these test routes:

<?php

// ./routes/web.php

use Illuminate\Support\Facades\Route;

Route::get('/in-sub', function () {
    return \App\Models\Seminar::query()
        ->with(['seminarStartInSub'])
        ->whereHas('seminarStartInSub', function ($query) {
            $query->whereDate('date', today()); // 2020-11-25
        })
        ->get();
});

Route::get('/exists', function () {
    return \App\Models\Seminar::query()
        ->with(['seminarStartExists'])
        ->whereHas('seminarStartExists', function ($query) {
            $query->whereDate('date', today()); // 2020-11-25
        })
        ->get();
});

Route::get('/join-sub', function () {
    return \App\Models\Seminar::query()
        ->with(['seminarStartJoinSub'])
        // ->whereHas(...) WON'T WORK AS EXPECTED
        ->whereHas('seminarStartJoinSub', function ($query) {
            $query->whereDate('date', today()); // 2020-11-25
        })
        ->get();
});

With these results:

GET /in-sub

[
  {
    "id": 3,
    "name": "Seminar C",
    "created_at": "2020-11-25T00:23:13.000000Z",
    "updated_at": "2020-11-25T00:23:17.000000Z",
    "seminar_start_in_sub": {
      "id": 5,
      "seminar_id": 3,
      "date": "2020-11-25",
      "created_at": "2020-11-25T00:24:14.000000Z",
      "updated_at": "2020-11-25T00:24:14.000000Z"
    }
  }
]

GET /exists

[
  {
    "id": 3,
    "name": "Seminar C",
    "created_at": "2020-11-25T00:23:13.000000Z",
    "updated_at": "2020-11-25T00:23:17.000000Z",
    "seminar_start_exists": {
      "id": 5,
      "seminar_id": 3,
      "date": "2020-11-25",
      "created_at": "2020-11-25T00:24:14.000000Z",
      "updated_at": "2020-11-25T00:24:14.000000Z"
    }
  }
]

GET /join-sub (won't work as expected)

[
  {
    "id": 1,
    "name": "Seminar A",
    "created_at": "2020-11-25T00:23:06.000000Z",
    "updated_at": "2020-11-25T00:23:07.000000Z",
    "seminar_start_join_sub": {
      "id": 1,
      "seminar_id": 1,
      "date": "2020-11-01",
      "created_at": "2020-11-25T00:24:03.000000Z",
      "updated_at": "2020-11-25T00:24:03.000000Z"
    }
  },
  {
    "id": 3,
    "name": "Seminar C",
    "created_at": "2020-11-25T00:23:13.000000Z",
    "updated_at": "2020-11-25T00:23:17.000000Z",
    "seminar_start_join_sub": {
      "id": 5,
      "seminar_id": 3,
      "date": "2020-11-25",
      "created_at": "2020-11-25T00:24:14.000000Z",
      "updated_at": "2020-11-25T00:24:14.000000Z"
    }
  }
]

Hope it helps

Activity icon

Replied to Anonymous Component Props Always Equal To Default Value

You’re welcome! Glad you got it working. Have a nice day =)

Activity icon

Awarded Best Reply on Anonymous Component Props Always Equal To Default Value

When you first created the input component did you use the artisan command to do it?

Maybe it generated the accompanying class, and as it seems you are not using it the props are not set because, if there is a class, the props should be listed as constructor parameters.

Can you check in your./app/Views/Components folder if there is a matching class to this component?

Activity icon

Replied to Anonymous Component Props Always Equal To Default Value

When you first created the input component did you use the artisan command to do it?

Maybe it generated the accompanying class, and as it seems you are not using it the props are not set because, if there is a class, the props should be listed as constructor parameters.

Can you check in your./app/Views/Components folder if there is a matching class to this component?

Activity icon

Replied to Cleave.js Deletes More Characters Than Intended When Using Vue And Delimiters

Não há de que =)

Glad it worked out and that the response helped you better understand it.

Dealing with DOM events is a bit tricky, I guess this one of the reasons JavaScript frameworks got so popular, to abstract away this details.

As you said you are learning, this article is great to understand how to better integrate third-party libraries with Vue:

https://www.smashingmagazine.com/2019/02/vue-framework-third-party-javascript/

And the Cookbook and Example section on the Vue site have an un-measurable value on deep understanding Vue:

https://vuejs.org/v2/cookbook/index.html

https://vuejs.org/v2/examples/

Hope it helps.

Have a nice day =)

Activity icon

Awarded Best Reply on Cleave.js Deletes More Characters Than Intended When Using Vue And Delimiters

First of all, the Vue directive example on cleave.js repository is meant to be used directly on an <input> element, not on an wrapping component:

import Vue from 'vue'
import Cleave from 'cleave.js';

Vue.directive('cleave', {
    inserted: (el, binding) => {
        el.cleave = new Cleave(el, binding.value || {})
    },
    update: (el) => {
        const event = new Event('input', {bubbles: true});
        setTimeout(function () {
            el.value = el.cleave.properties.result
            el.dispatchEvent(event)
        }, 100);
    }
})

Reference: https://github.com/nosir/cleave.js/blob/master/doc/vue.md

Let's see your implementation:

// Vue directive as showed in official documentation:
// https://github.com/nosir/cleave.js/blob/master/doc/vue.md
Vue.directive('cleave', {
 inserted: (el, binding) => {
  let input = el.querySelector('input');
  input.cleave = new Cleave(input, binding.value || {})
 },
 update: (el) => {
  const input = el.querySelector('input');
  const event = new Event('input', {bubbles: true});

  setTimeout(function () {
   input.value = input.cleave.properties.result;
   input.dispatchEvent(event);
  }, 100);
 }
});

// Simple vue component
Vue.component('custom-input', {
	props: {value:{}},
  template: '<div> <input :value="value" @input="$emit(\'input\', $event.target.value)"> {{ value }}</div>'
});

// Root 
new Vue({
	props: ['foo'],
  el: '#app'
});
<div id="app">
  <custom-input v-cleave="{
        delimiters: ['.', '.', '-'],
        blocks: [3, 3, 3, 2],
        numericOnly: true
    }" v-model="foo"></custom-input>
</div>

Reference: https://jsfiddle.net/87e4hfc0/1/

Your first comment:

// Vue directive as showed in official documentation...

Is not accurate.

You changed the code sample from cleave.js' docs to query for a inner <input> element inside a component, whereas, as already said, the sample in the official docs is meant to be applied directly to an <input> element, not to an wrapping element.

What happens with your variant is as you expect the v-model to be applied in the component's outer <div>, and v-model listens for input events either from a form component, or a bubbling event from within a DOM element.

So when a user presses a key, the v-model catches the native input event. Upon updating the bound value (foo) it then calls the updated hook in your v-cleave directive variant. You directive implementation then dispatches a new input event from the component's inner <input> DOM element, which is captured again by the outer element v-model method again, leading to circular call-chain until the value stabilizes.

As you want to bind to an outer component you can try this:

Vue.directive('cleave', {
 inserted: (el, binding) => {
  let input = el.querySelector('input');
  input.cleave = new Cleave(input, binding.value || {})
 },
 update: (el, bindings, vnode) => {
  const input = el.querySelector('input');
  
  setTimeout(function () {
   input.value = input.cleave.properties.result;
   
   if (vnode.data.model) vnode.data.model.callback(input.value);
   if (vnode.data.on && vnode.data.on.input) vnode.data.on.input(input.value);
  }, 100);
 }
});

Instead of dispatching a new input event from the DOM, which would lead to the circular call-chain we already outlined, it checks if the vnode (the outer component) has any model handlers (v-model) or any input listeners bound to it. If there are any handlers it calls them directly.

Note this new modified v-cleave directive variant won't work if applied directly to an <input> element. As it searches for an <input> from the directive's root element on both inserted and updated hooks (el.querySelector('input')).

Hope it is clearer.

Activity icon

Replied to Getting The Votes In Eloquent To A Page

First I would fix the Vote/User relation and add a Vote/Choice relation:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Vote extends Model
{
    use HasFactory;

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function choice()
    {
        return $this->belongsTo(Choice::class);
    }
}

As a Vote record has a user_id and a choice_id the relations are a belongsTo ( a vote belongs to a user, a vote belongs to to a choice)

Then it you can eager load both relations from the Poll model:

<?php

// ./routes/web.php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    $polls = \App\Models\Poll::query()
        ->with([
            'votes' => function ($relation) {
                return $relation->with(['user', 'choice']);
            },
        ])
        ->get();

    return $polls;
});

You'll get results such as:

[
  {
    "id": 1,
    "question": "Poll A",
    "ended_at": null,
    "created_at": "2020-11-24T14:02:31.000000Z",
    "updated_at": "2020-11-24T14:02:32.000000Z",
    "votes": [
      {
        "id": 1,
        "user_id": 1,
        "poll_id": 1,
        "choice_id": 1,
        "created_at": "2020-11-24T14:03:53.000000Z",
        "updated_at": "2020-11-24T14:03:53.000000Z",
        "user": {
          "id": 1,
          "name": "foo",
          "email": "[email protected]",
          "email_verified_at": null,
          "created_at": "2020-11-24T14:02:15.000000Z",
          "updated_at": "2020-11-24T14:02:16.000000Z"
        },
        "choice": {
          "id": 1,
          "poll_id": 1,
          "text": "Choice A.1",
          "created_at": "2020-11-24T14:03:05.000000Z",
          "updated_at": "2020-11-24T14:03:06.000000Z"
        }
      },
      {
        "id": 3,
        "user_id": 2,
        "poll_id": 1,
        "choice_id": 2,
        "created_at": "2020-11-24T14:03:56.000000Z",
        "updated_at": "2020-11-24T14:03:59.000000Z",
        "user": {
          "id": 2,
          "name": "bar",
          "email": "[email protected]",
          "email_verified_at": null,
          "created_at": "2020-11-24T14:02:18.000000Z",
          "updated_at": "2020-11-24T14:02:19.000000Z"
        },
        "choice": {
          "id": 2,
          "poll_id": 1,
          "text": "Choice A.2",
          "created_at": "2020-11-24T14:03:07.000000Z",
          "updated_at": "2020-11-24T14:03:12.000000Z"
        }
      }
    ]
  },
  {
    "id": 2,
    "question": "Poll B",
    "ended_at": null,
    "created_at": "2020-11-24T14:02:33.000000Z",
    "updated_at": "2020-11-24T14:02:33.000000Z",
    "votes": [
      {
        "id": 2,
        "user_id": 1,
        "poll_id": 2,
        "choice_id": 4,
        "created_at": "2020-11-24T14:03:55.000000Z",
        "updated_at": "2020-11-24T14:03:55.000000Z",
        "user": {
          "id": 1,
          "name": "foo",
          "email": "[email protected]",
          "email_verified_at": null,
          "created_at": "2020-11-24T14:02:15.000000Z",
          "updated_at": "2020-11-24T14:02:16.000000Z"
        },
        "choice": {
          "id": 4,
          "poll_id": 2,
          "text": "Choice B.1",
          "created_at": "2020-11-24T14:03:16.000000Z",
          "updated_at": "2020-11-24T14:03:18.000000Z"
        }
      },
      {
        "id": 4,
        "user_id": 2,
        "poll_id": 2,
        "choice_id": 5,
        "created_at": "2020-11-24T14:04:00.000000Z",
        "updated_at": "2020-11-24T14:04:00.000000Z",
        "user": {
          "id": 2,
          "name": "bar",
          "email": "[email protected]",
          "email_verified_at": null,
          "created_at": "2020-11-24T14:02:18.000000Z",
          "updated_at": "2020-11-24T14:02:19.000000Z"
        },
        "choice": {
          "id": 5,
          "poll_id": 2,
          "text": "Choice B.2",
          "created_at": "2020-11-24T14:03:20.000000Z",
          "updated_at": "2020-11-24T14:03:21.000000Z"
        }
      }
    ]
  }
]

-=-=-=-=-=-=-=-=-=-=-

One another thing I would be sure, is to have a composite foreign key index between the votes and choices tables:

Schema::create('votes', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained('users');
    $table->foreignId('poll_id');
    $table->foreignId('choice_id');
    $table->timestamps();

    $table->foreign(['poll_id', 'choice_id'])
        ->references(['poll_id', 'id'])
        ->on('choices');
});

This way you avoid ending up with a record in your votes table where its choice_id and poll_id references a Choice that doesn't belong to the corresponding Poll.

Of course you can enforce it on the app-level, but having it enforced on the database level brings an extra consistency layer.

Hope it helps.

Activity icon

Replied to Register Component In Vue.js

To register it locally within a component use the components option in the component's definition

import CardUi from './components/UI/CardUi';

export default {
    name: 'MyOtherComponent',

    components: {'card-ui': CardUi},

    // other options...
}

Reference: https://vuejs.org/v2/guide/components-registration.html#Local-Registration

Activity icon

Replied to OrderBy And GroupBy With Another OrderBy

You're welcome! Have a nice day =)

Activity icon

Awarded Best Reply on OrderBy And GroupBy With Another OrderBy

You can call the collection sort by in the @\foreach

@foreach($chunk->sortBy('order') as $scan)

But I think if you get the results sorted from the DB group by would preserve its sorting

public function index()
    {
        $collection = CustomerScan::where('user_id', Auth::id())
        ->orderByRaw('CAST(created_at AS DATE) DESC') // order by future grouped by
        ->orderBy('order') // order within each group
        ->get()
        ->groupBy(function ($item) {
            return $item->created_at->format('Y-m-d');
        });

        return view('customer.scan-viewer.index')->with([
            'collection' => $collection,
        ]);
    }

But depending on how many records you have, using a CAST(...) within your order by clause can decrease your query's performance as it would not use any indices on that column.

Activity icon

Awarded Best Reply on Register Component In Vue.js

In your app.js file you need to register it globally

import Vue from 'vue';
import CardUi from './components/UI/CardUi';

Vue.component('card-ui', CardUi);

Reference: https://vuejs.org/v2/guide/components-registration.html#Global-Registration

Activity icon

Replied to How To Eager Load The Queries Inside The "show" Page

Actually as in your show(...) method you already have a Course instance you will need to "lazy" eager load these relations:

public function show(Course $course)
{
    $course->load(['instructor.user', 'lessons.units', 'students.user']);

    return view('courses.show', ['course' => $course])
}

Reference: https://laravel.com/docs/8.x/eloquent-relationships#lazy-eager-loading

I am guessing most of the relation names. Also I removed the $instructorname assignment as you didn't use that variable anywhere.

Hope it helps.

Activity icon

Replied to OrderBy And GroupBy With Another OrderBy

You can call the collection sort by in the @\foreach

@foreach($chunk->sortBy('order') as $scan)

But I think if you get the results sorted from the DB group by would preserve its sorting

public function index()
    {
        $collection = CustomerScan::where('user_id', Auth::id())
        ->orderByRaw('CAST(created_at AS DATE) DESC') // order by future grouped by
        ->orderBy('order') // order within each group
        ->get()
        ->groupBy(function ($item) {
            return $item->created_at->format('Y-m-d');
        });

        return view('customer.scan-viewer.index')->with([
            'collection' => $collection,
        ]);
    }

But depending on how many records you have, using a CAST(...) within your order by clause can decrease your query's performance as it would not use any indices on that column.

Activity icon

Replied to Register Component In Vue.js

In your app.js file you need to register it globally

import Vue from 'vue';
import CardUi from './components/UI/CardUi';

Vue.component('card-ui', CardUi);

Reference: https://vuejs.org/v2/guide/components-registration.html#Global-Registration

Activity icon

Replied to Passing Arguments For Clauses Across Multiple Related Resources?

You're welcome! Have a nice day =)

Activity icon

Replied to Laravel 8.x - JetStream Prevent Auto Login After Registration

You’re welcome! Have a nice day =)

Activity icon

Awarded Best Reply on Passing Arguments For Clauses Across Multiple Related Resources?

Adding as a raw column to each query will add that column it to resulting models.

$employee->with([
    'employmentSchemes' => function ($relation) use ($yearsService) {
        $relation->addSelect($relation->raw($yearsService . ' AS years_service'));
        $relation->withCasts(['years_service' => 'integer']);

        $relation->with([
            'paySchemes' => function ($relation) use ($yearsService) {
                $relation->addSelect($relation->raw($yearsService . ' AS years_service'));
                $relation->withCasts(['years_service' => 'integer']);

                $relation->with([
                    'paySchemeRules' => function ($relation) use ($yearsService) {
                        $relation->addSelect($relation->raw($yearsService . ' AS years_service'));
                        $relation->withCasts(['years_service' => 'integer']);

                        $relation->where('years_service_lower', '<', $yearsService);
                        $relation->where('years_service_upper', '>', $yearsService);
                    },
                ]);
            },
        ]);
    },
])->whereKey($id)->get();
Activity icon

Awarded Best Reply on Laravel 8 With Existing DB

Has the encryption changed...

Passwords in Laravel are hashed, not encrypted. As Laravel defers to using PHP functions for password hashing/comparing, even if Laravel shipped with new defaults (which it didn't), PHP password_verify function would still be able to compare to an existing hash as it saves the hash parameters used in the hash itself.

Unless your L6 app uses a custom Illuminate\Contracts\Hashing\Hasher implementation. If that is the case you should use the same Hasher implementation in your L8 app.

Activity icon

Replied to Laravel 8.x - JetStream Prevent Auto Login After Registration

Indeed much better.

I really missed the part where the OP described the new account would need approval.

Thanks for the heads up!

Activity icon

Awarded Best Reply on How To Get Summary Of Related Table?

On your forumPostsViewedSum you need to also select the forum_thread_id column, so Laravel can reference it back when matching the results:

public function forumPostsViewedSum()
{
    return $this->hasMany(ForumPost::class)
        ->select('forum_thread_id') // <<<<< ADDED
        ->selectRaw('SUM(viewed) as forum_posts_viewed_sum')
        ->groupBy('forum_thread_id');
}

But if you want similar functionality as ->withCount(...) you can try doing this instead:

$forumThreads = ForumThread::query()
    ->with('latestForumPost.creator')
    ->withCount('forumPosts')
    ->with('creator')
    ->getByForumId($forum_id)
    ->orderBy($order_by, $order_direction)
    ->offset($limit_start)
    ->take($forum_threads_per_page)
    // ->with('forumPostsViewedSum') // <<<<< REMOVED
    ->addSelect([
        'forum_posts_viewed_sum' => ForumPost::query()
            ->selectRaw('SUM(viewed)')
            ->whereColumn('forum_thread_id', 'forum_threads.id'),
    ])
    ->withCasts(['forum_posts_viewed_sum' => 'integer'])
    ->get();

Hope it helps.

Activity icon

Replied to 405 Http Error On Post Request

can you try adding this header:

headers: {
    'X-Requested-With': 'XMLHttpRequest', // <<<<< add this one
    'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') // this you already have
},

Issue can be:

If authentication or validation fails, Laravel checks if the request is a regular request or a API request.

If it thinks it is a regular request it then redirects back or redirect to the login page with some error variables. If it is a Ajax request Laravel responds with a JSON payload.

If the request is made through Ajax but even though Laravel thinks it is a regular one, it will send a redirect response on failure, the browser will try to follow the redirect while keeping the same HTTP verb, there it could be the reason why you are getting this error.

One way Laravel tries to assess if a request is a Ajax request is by looking into the request headers and verifying the X-Requested-With header.

Activity icon

Replied to Laravel 8 With Existing DB

Has the encryption changed...

Passwords in Laravel are hashed, not encrypted. As Laravel defers to using PHP functions for password hashing/comparing, even if Laravel shipped with new defaults (which it didn't), PHP password_verify function would still be able to compare to an existing hash as it saves the hash parameters used in the hash itself.

Unless your L6 app uses a custom Illuminate\Contracts\Hashing\Hasher implementation. If that is the case you should use the same Hasher implementation in your L8 app.

Activity icon

Replied to Retrieve Objects From Eloquent Collection

Sure:

$records = VisualLogin::all()
    // create a key from USER_ID and MACHINE_ID
    // so we can group records by both fields
    ->map(function ($record) {
        $record->group_key = implode('-', [
            $record->USER_ID, 
            $record->MACHINE_ID,
        ]);

        return $record;
    })

    // group by the calculated key
    ->groupBy('group_key')
    
    // only return the last activity from each group
    ->map(function (\Illuminate\Support\Collection $group) {
        $lastActivity = $group->max('LAST_ACTIVITY');

        return $group->firstWhere('LAST_ACTIVITY', $lastActivity);
    })

     // reindex the results
    ->values();

But if your table has many records I would keep filtering them in the database.

Or at least add some add where conditions to reduce the number of records returned to PHP.

Activity icon

Replied to How To Get Summary Of Related Table?

Another change you can do to shorten it further is changing the aggregate alias to something shorter, for example:

public function forumPostsViewedSum()
{
    return $this->hasOne(ForumPost::class)
        ->select('forum_thread_id')
        ->selectRaw('SUM(viewed) as value')
        ->groupBy('forum_thread_id')
        ->withDefault(['value' => 0]);
}

Then in your Vue component you would call it:

{{ forumThread.forum_posts_viewed_sum.value }}
Activity icon

Replied to How To Get Summary Of Related Table?

Did you try the second option?

On the second option I add a select subquery mostly to avoid this nested data structure. The select sub-query is how the ->withCount(...) already works.

Also, if you prefer to use the first option and keep the relation, you might want to change the ->hasMany(...) on the forumPostsViewedSum relation to a ->hasOne(...):

    public function forumPostsViewedSum()
    {
        return $this->hasOne(ForumPost::class)
            ->select('forum_thread_id') // <<<<< ADDED
            ->selectRaw('SUM(viewed) as forum_posts_viewed_sum')
            ->groupBy('forum_thread_id')
            ->withDefault(['forum_posts_viewed_sum' => 0]);
    }

So at least you would avoid the array around the result.

As it is grouped by there will always be only one record by forum thread.

Also the ->withDefault(...) will ensure that even forum threads without posts yet get a record.

Activity icon

Replied to Laravel 8.x - JetStream Prevent Auto Login After Registration

As you can see Fortify's RegisteredUserController return an implementation of the Laravel\Fortify\Contracts\RegisterResponse.

The default the implementation is provided by Laravel\Fortify\Http\Responses\RegisterResponse, but the idea of resolving a contract at runtime is to allow the developer to easily provide an alternative implementation with custom functionality.

Laracast's forum user @Snapey has a nice blog post about customizing the redirect response after login:

https://talltips.novate.co.uk/laravel/laravel-8-conditional-login-redirects

I would recommend you reading this as he gives better details about how this works.

For your use-case you can try this:

1 - Create a custom implementation of the RegisterResponse contract.

Create a class in your project, it can be located anywhere, but I would create it at ./app/Http/Responses folder. If that folder does not exists, create it, but mind the capitalization.

Then create a RegisterResponse class inside this folder with the following code:

<?php

namespace App\Http\Responses;

use Illuminate\Contracts\Auth\StatefulGuard;
use Laravel\Fortify\Http\Responses\RegisterResponse as FortifyRegisterResponse;

class RegisterResponse extends FortifyRegisterResponse
{
    protected $guard;

    public function __construct(StatefulGuard $guard)
    {
        $this->guard = $guard;
    }

    public function toResponse($request)
    {
        $this->guard->logout();

        return parent::toResponse($request);
    }
}

Here we just extend the RegisterResponse Fortify ships with and force a logout before deferring to the base class implementation.

We could have used the Auth facade for logging out, but as Fortify's RegisteredUserController accepts a StatefulGuard implementation on its constructor, I chose to use the same approach so your response object receives the same guard instance as that controller.

2 - Register the custom implementation of the RegisterResponse.

Now you just need to register this custom implementation on a Service Provider's boot method.

I would recommend placing this either on your project's FortifyServiceProvider or JetstreamServiceProvider.

public function boot()
{
    // other code
    
    $this->app->singleton(
        \Laravel\Fortify\Contracts\RegisterResponse::class,
        \App\Http\Responses\RegisterResponse::class,
    );
}

Now when Fortify's RegisteredUserController resolves the RegisterResponse when returning, it will use your implementation instead of the one Fortify ships with.

Hope it helps.

Nov
23
2 days ago
Activity icon

Replied to HTTP Request In Laravel

Laravel has a HTTP client that you could use:

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

Then you could make those requests with a code similar to this:

use Illuminate\Support\Facades\Http;

$url = $baseMicrosoftGraphsURL . '/me/calendarGroup/calendars/' . $calendarId;

$response = Http::get($url);

Where $baseMicrosoftGraphsURL is the base URL for the Microsoft Graphs API and $calendarId is the calendar id I guess you need to replace in the first sample URL from your post.

I also think you might need to send additional headers for authentication into the Microsoft Graphs API.

Please refer to both Microsoft Graphs API documentation and Laravel documentation on HTTP client to see how to send any needed headers, and which value you need to send to authenticate your request.

Hope it helps.

Activity icon

Replied to Implement Gate For User's Role Inside A User Table

You're welcome! Have a nice day =)

Activity icon

Awarded Best Reply on Implement Gate For User's Role Inside A User Table

As the User model has a role column you should change your hasRole(...) method in your User model to:

public function hasRole($role)
{
    return $this->role === $role;
}

Your previous implementation would query for any record with a matching role, not testing that particular method.

Also, as since Laravel 6 one can define Gates for guest users, you should account for the $user parameter inside your gate callback to be null if no user is logged in.

Reference: https://laravel.com/docs/6.x/authorization#guest-users

To fix that you could use the optional(...) helper:

Gate::define('teacher', function($user){
    return optional($user)->hasRole('teacher');
});

Or test if the $user parameter is null before calling the ->hasRole(...) method:

Gate::define('teacher', function($user){
    if (!$user) {
         return false;
    }

    return $user->hasRole('teacher');
});

Hope it helps.

Activity icon

Replied to Too Few Arguments To Function When Calling Job

Show the code where you dispatch the CreateBill job.

Somewhere in your controller or another part in your code where you call something like this:

$this->dispatch(CreateBill(...));

That is the class the error message refers to.

Show the code around it, not just this method call.

Activity icon

Replied to Implement Gate For User's Role Inside A User Table

As the User model has a role column you should change your hasRole(...) method in your User model to:

public function hasRole($role)
{
    return $this->role === $role;
}

Your previous implementation would query for any record with a matching role, not testing that particular method.

Also, as since Laravel 6 one can define Gates for guest users, you should account for the $user parameter inside your gate callback to be null if no user is logged in.

Reference: https://laravel.com/docs/6.x/authorization#guest-users

To fix that you could use the optional(...) helper:

Gate::define('teacher', function($user){
    return optional($user)->hasRole('teacher');
});

Or test if the $user parameter is null before calling the ->hasRole(...) method:

Gate::define('teacher', function($user){
    if (!$user) {
         return false;
    }

    return $user->hasRole('teacher');
});

Hope it helps.

Activity icon

Replied to Cleave.js Deletes More Characters Than Intended When Using Vue And Delimiters

First of all, the Vue directive example on cleave.js repository is meant to be used directly on an <input> element, not on an wrapping component:

import Vue from 'vue'
import Cleave from 'cleave.js';

Vue.directive('cleave', {
    inserted: (el, binding) => {
        el.cleave = new Cleave(el, binding.value || {})
    },
    update: (el) => {
        const event = new Event('input', {bubbles: true});
        setTimeout(function () {
            el.value = el.cleave.properties.result
            el.dispatchEvent(event)
        }, 100);
    }
})

Reference: https://github.com/nosir/cleave.js/blob/master/doc/vue.md

Let's see your implementation:

// Vue directive as showed in official documentation:
// https://github.com/nosir/cleave.js/blob/master/doc/vue.md
Vue.directive('cleave', {
 inserted: (el, binding) => {
  let input = el.querySelector('input');
  input.cleave = new Cleave(input, binding.value || {})
 },
 update: (el) => {
  const input = el.querySelector('input');
  const event = new Event('input', {bubbles: true});

  setTimeout(function () {
   input.value = input.cleave.properties.result;
   input.dispatchEvent(event);
  }, 100);
 }
});

// Simple vue component
Vue.component('custom-input', {
	props: {value:{}},
  template: '<div> <input :value="value" @input="$emit(\'input\', $event.target.value)"> {{ value }}</div>'
});

// Root 
new Vue({
	props: ['foo'],
  el: '#app'
});
<div id="app">
  <custom-input v-cleave="{
        delimiters: ['.', '.', '-'],
        blocks: [3, 3, 3, 2],
        numericOnly: true
    }" v-model="foo"></custom-input>
</div>

Reference: https://jsfiddle.net/87e4hfc0/1/

Your first comment:

// Vue directive as showed in official documentation...

Is not accurate.

You changed the code sample from cleave.js' docs to query for a inner <input> element inside a component, whereas, as already said, the sample in the official docs is meant to be applied directly to an <input> element, not to an wrapping element.

What happens with your variant is as you expect the v-model to be applied in the component's outer <div>, and v-model listens for input events either from a form component, or a bubbling event from within a DOM element.

So when a user presses a key, the v-model catches the native input event. Upon updating the bound value (foo) it then calls the updated hook in your v-cleave directive variant. You directive implementation then dispatches a new input event from the component's inner <input> DOM element, which is captured again by the outer element v-model method again, leading to circular call-chain until the value stabilizes.

As you want to bind to an outer component you can try this:

Vue.directive('cleave', {
 inserted: (el, binding) => {
  let input = el.querySelector('input');
  input.cleave = new Cleave(input, binding.value || {})
 },
 update: (el, bindings, vnode) => {
  const input = el.querySelector('input');
  
  setTimeout(function () {
   input.value = input.cleave.properties.result;
   
   if (vnode.data.model) vnode.data.model.callback(input.value);
   if (vnode.data.on && vnode.data.on.input) vnode.data.on.input(input.value);
  }, 100);
 }
});

Instead of dispatching a new input event from the DOM, which would lead to the circular call-chain we already outlined, it checks if the vnode (the outer component) has any model handlers (v-model) or any input listeners bound to it. If there are any handlers it calls them directly.

Note this new modified v-cleave directive variant won't work if applied directly to an <input> element. As it searches for an <input> from the directive's root element on both inserted and updated hooks (el.querySelector('input')).

Hope it is clearer.

Activity icon

Replied to How To Get Summary Of Related Table?

On your forumPostsViewedSum you need to also select the forum_thread_id column, so Laravel can reference it back when matching the results:

public function forumPostsViewedSum()
{
    return $this->hasMany(ForumPost::class)
        ->select('forum_thread_id') // <<<<< ADDED
        ->selectRaw('SUM(viewed) as forum_posts_viewed_sum')
        ->groupBy('forum_thread_id');
}

But if you want similar functionality as ->withCount(...) you can try doing this instead:

$forumThreads = ForumThread::query()
    ->with('latestForumPost.creator')
    ->withCount('forumPosts')
    ->with('creator')
    ->getByForumId($forum_id)
    ->orderBy($order_by, $order_direction)
    ->offset($limit_start)
    ->take($forum_threads_per_page)
    // ->with('forumPostsViewedSum') // <<<<< REMOVED
    ->addSelect([
        'forum_posts_viewed_sum' => ForumPost::query()
            ->selectRaw('SUM(viewed)')
            ->whereColumn('forum_thread_id', 'forum_threads.id'),
    ])
    ->withCasts(['forum_posts_viewed_sum' => 'integer'])
    ->get();

Hope it helps.

Activity icon

Awarded Best Reply on Subquery Join With Eloquent

Actually this is pretty similar.

First you'll need a model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Login extends Model
{
    protected $connection = 'vmfgtest';
    protected $table = 'LOGINS';
}

The in a route, for example:

<?php

// ./routes/web.php

use App\Models\Login;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    $logins = Login::query()
        ->select('LOGINS.ROWID', 'LOGINS.USER_ID', 'LOGINS.MACHINE_ID', 'LOGINS.LAST_ACTIVITY')
        ->joinSub(Login::query()
            ->select('USER_ID', 'MACHINE_ID')
            ->selectRaw('MAX(LAST_ACTIVITY) as LAST_ACTIVITY')
            ->groupBy('USER_ID', 'MACHINE_ID'), 'L2', function ($join) {
            $join->on('L2.USER_ID', '=', 'LOGINS.USER_ID');
            $join->on('L2.MACHINE_ID', '=', 'LOGINS.MACHINE_ID');
            $join->on('L2.LAST_ACTIVITY', '=', 'LOGINS.LAST_ACTIVITY');
        })
        ->get();

    return $logins;
});

Note as the join already requires LAST_ACTIVITY to be the same on both the outer and sub-queries you can use any of their aliases/name on the outer query select clause.

Activity icon

Replied to Subquery Join With Eloquent

Actually this is pretty similar.

First you'll need a model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Login extends Model
{
    protected $connection = 'vmfgtest';
    protected $table = 'LOGINS';
}

The in a route, for example:

<?php

// ./routes/web.php

use App\Models\Login;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    $logins = Login::query()
        ->select('LOGINS.ROWID', 'LOGINS.USER_ID', 'LOGINS.MACHINE_ID', 'LOGINS.LAST_ACTIVITY')
        ->joinSub(Login::query()
            ->select('USER_ID', 'MACHINE_ID')
            ->selectRaw('MAX(LAST_ACTIVITY) as LAST_ACTIVITY')
            ->groupBy('USER_ID', 'MACHINE_ID'), 'L2', function ($join) {
            $join->on('L2.USER_ID', '=', 'LOGINS.USER_ID');
            $join->on('L2.MACHINE_ID', '=', 'LOGINS.MACHINE_ID');
            $join->on('L2.LAST_ACTIVITY', '=', 'LOGINS.LAST_ACTIVITY');
        })
        ->get();

    return $logins;
});

Note as the join already requires LAST_ACTIVITY to be the same on both the outer and sub-queries you can use any of their aliases/name on the outer query select clause.

Activity icon

Replied to Email Verification Using Jetstream

Do you still have the email column on your device table?

Asking because on the ApiLoginController I don't see you saving it for new devices.

As you are using the device as the Notifiable instance you'll need one there. Otherwise the notification won't know which email address to use.

If you don't have an email there anymore you can add an accessor to defer to the related user model:

class Device extends Model implements MustVerifyEmail {
    // ... other code

    public function getEmailAttribute() {
        return $this->user->email;
    }
}
Activity icon

Replied to Sanctum SPA Authentication Guard::attempt Error Cause?

But there is no session write support?

No. I was wrong. Sorry.

Just installed a fresh Laravel app, added Sanctum, configured some api and regular routes and played with sessions.

If a request is made by your first-party frontend, in other words if EnsureFrontendRequestsAreStateful's fromFrontend(...) method returns true, the session is set on the request and any changes to session are written back.

It was a misunderstanding from my side as before I tested the API routes directly and not from a SPA, so fromFrontend(...) returned false and no session was available.

In summary: When calling from your SPA a session is started and there is write support. Just as a regular endpoint.

But if you plan on providing third-party access to your API routes I wouldn't rely on session support on those routes.

I apologize again, hope it is clearer now.

Activity icon

Replied to Laracast Subscription And Black Friday

Hi @automica , I am in the "Forever" subscription plan for sometime now, but before taking this route I did one time signed up for a discounted yearly plan and had it added to the remaining time I had from the last billing cycle.

I guess you can try checking-out (as in a cart checkout) and confirm this before submitting the checkout page.

Activity icon

Replied to Laravel Add Amount To The Next Parent In Loop

Sure, no problem at all

Nov
22
3 days ago
Activity icon

Replied to Save Index To Collection After Grouping And On A Each

Not sure I understand what you need, but I think you want each group index to be 0-based.

Problem is when you call:

$group = $prods->groupBy('product_id');

The resulting collection will be indexed by each group's product_id and not by 0-based increasing indices.

So when you call $group->each(function ($tot, $key) use ($products) { the $key will match each group's product_id.

If you want to reset those indices you can call ->values() after grouping them by:

// EDIT: added missing ->values() call
$group = $prods->groupBy('product_id')->values();

But, as a suggestion, I think you could simplify your code to:

// sample data
$prods = collect([
    ['product_id' => 1, 'product' => 'Product 1', 'quantity_client' => 11, 'quantity_seller' => 15],
    ['product_id' => 1, 'product' => 'Product 1', 'quantity_client' => 12, 'quantity_seller' => 16],
    ['product_id' => 1, 'product' => 'Product 1', 'quantity_client' => 13, 'quantity_seller' => 17],
    ['product_id' => 2, 'product' => 'Product 2', 'quantity_client' => 21, 'quantity_seller' => 25],
    ['product_id' => 2, 'product' => 'Product 2', 'quantity_client' => 22, 'quantity_seller' => 26],
    ['product_id' => 2, 'product' => 'Product 2', 'quantity_client' => 23, 'quantity_seller' => 27],
]);

$products = $prods
    ->groupBy('product_id')
    ->values() // reset groups' indices
    ->map(function ($group, $index) {
        return [
            'product_id' => $group[0]['product_id'],
            'product' => $group[0]['product'],
            'index' => $index,
            'quantity_client' => $group->sum('quantity_client'),
            'quantity_seller' => $group->sum('quantity_seller'),
        ];
    });

return $products;

If you really think calling ->sum(...) twice is a no-go as it would iterate each group twice you can try using ->reduce(...)

$prods = collect([
    ['product_id' => 1, 'product' => 'Product 1', 'quantity_client' => 11, 'quantity_seller' => 15],
    ['product_id' => 1, 'product' => 'Product 1', 'quantity_client' => 12, 'quantity_seller' => 16],
    ['product_id' => 1, 'product' => 'Product 1', 'quantity_client' => 13, 'quantity_seller' => 17],
    ['product_id' => 2, 'product' => 'Product 2', 'quantity_client' => 21, 'quantity_seller' => 25],
    ['product_id' => 2, 'product' => 'Product 2', 'quantity_client' => 22, 'quantity_seller' => 26],
    ['product_id' => 2, 'product' => 'Product 2', 'quantity_client' => 23, 'quantity_seller' => 27],
]);

$products = $prods
    ->groupBy('product_id')
    ->values() // reset indices
    ->map(function ($group, $index) {
        $values = $group->reduce(function ($values, $record) {
            $values['quantity_client'] += $record['quantity_client'] ?? 0;
            $values['quantity_seller'] += $record['quantity_seller'] ?? 0;

            return $values;
        }, ['quantity_client' => 0, 'quantity_seller' => 0]);

        return [
            'product_id' => $group[0]['product_id'],
            'product' => $group[0]['product'],
            'index' => $index,
            'quantity_client' => $values['quantity_client'],
            'quantity_seller' => $values['quantity_seller'],
        ];
    });

return $products;

I personally don't think the performance impact is noticeable, unless you have a really huge dataset.

Hope it helps.

Activity icon

Replied to Laravel Add Amount To The Next Parent In Loop

Hey @whoami1509 ,

To be honest it is becoming very hard to follow up all different use-cases and requirements.

I asked before (some days ago) about a scenario where a user's parent chain could have a different levels than the one "ideal" flow. By then that scenario didn't draw your attention as a potential problem, and we moved on considering the happy path (no holes in the levels).

But that didn't last long, as you remembered you need to add guest users, and more recently that each level has sub-levels, among other changes/requirements.

The implementation has already become messy as for each new use-case we add an if clause or condition here and there, and then another use-case you "forgot" or wasn't aware comes up.

I would start over with a dedicated class to calculate this, and maybe I would consider storing the ranks (and likely rank groups/clusters) in a table for easier retrieval.

I really mean it, if I were the one hired for this job I would start from scratch at this point. The code we already have become hard to understand, and hard to modify. But I wouldn't start over from nowhere.

My suggestion is that you:

  • Write down all use-cases and requirements for this feature.
    • Really, do this. Write "ALL" use-case and requirements down. Be it on a paper, or text document.
    • Try to identify similar cases, cluster different cases, and then come up with a structure you think would fit all those use-cases
    • Problem-solving, specially solving complex problems, is more about planning and trying to understand the problem, break it into smaller pieces, than trying to rush and implementation and fix any missing requirements with "band-aids" or ingenious solutions fixes.
  • Consider adopting a design pattern to help building the solution
    • Although we are into this task for 2 weeks now, I don't feel I know sufficiently about on what you need to really solve, as every time a new use-case arises, but I think a Behavioral pattern, such as the State, Strategy, or Chain of responsibility pattern could help you into breaking this problem into smaller pieces easier to understand.
  • Use a TDD (Test Driven Development) approach with proper unit tests to implement and address each use-case and requirement listed above.
    • Start with unit tests for basic use-cases (the happy paths/ideal use cases).
    • Then add the more complex ones
    • Then start testing when things could break, and how your solution handles that.

Being honest again, I don't use TDD for every feature in the projects I work on. I do write tests for most features, but often I write them after I have some code working. And I often write feature tests instead of unit tests.

But I learned to reach for TDD when I have a larger feature with lots of conflicting use-cases as it seems to be your case there. It specially helps out when the client comes up with new features and requirements every time a use-case is solved.

Don't get me wrong, this a more common scenario than every one would like to, and doesn't mean someone his hiding information or doing a bad job on the requirements analysis phase for such a feature or project.

I recently had a project around a custom FFMpeg command builder. Every time we deployed a new version both the end-users come up with a new requirement. Mostly because they weren't aware beforehand of what was possible to do through a web interface regarding video editing, or because when using the app for editing more videos other than the ones they first thought on editing, they found those videos need additional processing requirements (cropping, color correction, resizing, etc.)

As FFMpeg has a very cumbersome and sometimes inconsistent way of composing parameters and building filters, the only way I could make progress was starting over from scratch, use a TDD approach, and ensure a new requirement wouldn't blow up an already implemented use-case, or if it did blow-up, having unit tests made clear where I need to focus on solving any problems.

As I already said, it become very hard to follow up all different use-cases and requirements that keeps changing, and the solution I built for you is already messy and hard to grasp.

Also sometimes when you have a problem your code sample have a different implementation with changes you made for reasons I am not aware. I feel there is always something you don't share, either due to hide commercial knowledge, or because you deem it is not relevant, or that you can handle that use-case later by yourself. But that often lead to a never-ending "we are almost there...", "can't make this work...", "I forgot about this..." and that is not being productive.

I am afraid I won't be able to help you further with this task. It grew very out-of-scope of what a forums assistance is expected to be.

I think I am largely responsible for letting this grow out of control, and I apologize for that. Really, it is mostly my fault we got to this point, sorry for that.

Please consider the advice on writing down use-cases and using unit test for re-building this feature from scratch. Of course the current implementation can be useful as a reference for some use-cases, but I would really start over.

Laracasts has some videos about using TDD, and there is a recent series about long refactorings that might help you. Here are some links on those series:

https://laracasts.com/series/refactoring-workshops

https://laracasts.com/series/build-a-laravel-app-with-tdd

I wish you the best luck and hope anything from this response helps you out. I also hope I could share anything useful on any of the responses in this thread.

If you read until here, thanks for your patience. Have a nice day =)

Activity icon

Replied to How Do You Handle Controllers With Partial Identical Business Logic?

You're welcome @lazos99 !

By reading @martinbean 's response to my feedback request I think you could consider using a service class similar to what he described there.

EDIT After reading your more recent response, I think you already figured out how to improve you solution.

Activity icon

Replied to How Do You Handle Controllers With Partial Identical Business Logic?

Thanks for the response @martinbean !

I usually don't reach for the repositories myself, but I used them when I wanted to share custom queries between several components in an app.

These days I prefer dedicated query classes for larger complicated queries (reports for example), or I usually return a custom eloquent query builder from a overridden newEloquentBuilder(...) method in a model, for stronger type hints in local scopes in an approach similar to the one described here:

https://timacdonald.me/dedicated-eloquent-model-query-builders/

But I thought using a repository would fit the issue described by OP of needing to share some logic between two similar controllers.

But after reading your approach of having a generic service class that accepts a specific store implementation by leveraging the container, I was like: how come I never thought of it before !?

It indeed looks like a better, and more flexible solution.

The conclusion I get is that I still have a lot to learn, and am glad I am able to share knowledge from folks like you.

Thanks for your time and feedback, and have a nice day =)

Activity icon

Replied to Email Verification Using Jetstream

At first it wasn't clear you would want to have many devices per user.

Considering that, what about adding a user_id column into the devices table?

id, user_id, email, device, device_verified_at 

I don't know if email would still be necessary with this change. If a user changes their email they should still be able to track their connected devices, wouldn't they? If so I would remove the email column from the devices table altogether.

If you make this change then you can add a hasMany relation between the User and Device models.

And then you could do something like this:

$device = $this->user()->devices()->firstWhere('device_id', $this->query('device'));

if (! $device) {
    return false;
}

Does that make sense?

Activity icon

Replied to How Do You Handle Controllers With Partial Identical Business Logic?

Hey @martinbean , nice idea!

Would love to hear your opinion about my last response, if you have time to do so.

I've learned a lot from your responses throughout the years in Laracasts forums, and on Discord, although I am not an active responder there.

Thanks in advance!