LobeShare

LobeShare

Systems Architect at LobeShare (Pty) Ltd

Member Since 3 Weeks Ago

Somerset West

Experience Points
3,510
Total
Experience

1,490 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
32
Lessons
Completed
Best Reply Awards
0
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 1
3,510 XP
Jun
30
1 week ago
Activity icon

Replied to Mermaid For Laravel

The Vue way could work, but that being even further outside my current expertise, would render it just as hard to use as the raw javascript, and also exclude those who use bootstrap without Vue.

I'm using it to present data (which I don't want to send in bulk to the client) in a graphical manner, i.e. I generate the mermaid syntax from database records, and send only the mermaid text to the client.

The controller for it needs to manage the initialisation and configuration of the javascript such as:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
    er: {
        layoutDirection: 'LR',
        diagramPadding: 10,
        entityPadding: 8,
        minEntityHeight: 40,
        minEntityWidth: 65
    },
    startOnLoad:true
});</script>

and provide a way to generate the html

 <div class="mermaid>
    erDiagram
        A ||--|{ AB : has
        AB }|--|| B : has
</div/

using methods per diagram type which then also can interact with the settings for those diagrams which can only be set or changed from their defaults during initialisation.

Activity icon

Started a new Conversation Mermaid For Laravel

Hey,

I'm busy using mermaid-js in my project.

It's a javascript package which accepts a "markdown-ish" syntax such as below and renders that in the browser as the diagram below that. It is ~~quite useful~~ brilliant and I'd like to wrap it into my project the right way, but I'm too much of a laravel noob to know what the right way is.

Has it been done, or should I attempt it? And if I should, will there be others would also like to use it, or pitch in to get it done?

graph TD
	A[Christmas] -->|Get money| B(Go shopping)
	B --> C{Let me think}
	C -->|One| D[Laptop]
	C -->|Two| E[iPhone]
	C -->|Three| F[fa:fa-car Car]

Rendered as:

Jun
19
3 weeks ago
Activity icon

Started a new Conversation Morphing Self-Referencing, Many-To-Many Relationships

My direct exposure to the Laravel eco-system in use by all on this forum is new and very limited, so please forgive my ignorance.

The Situation

I’ve designed a few before though, so right now I’m busy defining a suitable backend for a website concept I’m developing. Don’t worry, I’ll leave the actual coding to experts, but it’s an intricate yet potentially powerful data model so I undertook to build all my ideas about what it can do right into the heart of it – the data model.

The model features several many-to-many relationships, some controlled by meta-data and others using more standard pivot table. All went well until I developed the need to morph one of the central tables. That meant I needed a self-referencing, many-to-many morphing relationship.

Searching for a Solution

I could find very few references to such possible examples or discussions beyond an unanswered request from 2014 and a wink-faced remark in answer to another post by some level 50 contributor suggesting the OP was looking for some holy grail.

I spent many cups of coffee and a lot of clunky code on making some progress towards the results I wanted. Eventually I saw some light. I was able to simplify it right down to a single trait containing two single-line relationships, and 2 small functions to be used in each of the morphing models, and a custom model with 2 simple relationships for the pivoting.

Sharing my Solution

I’d like to share and explain my implementation for three reasons:

  1. It’s the right thing to do,
  2. It could help somebody solve a complex problem in a simple way and
  3. I’d love insights on how my solution will break using live/large data.

Morphing ICE

In my application, I have three persona types Individual, Collective and External. By -able convention I should have (and at one stage did) call the morph name ‘personable’ or ‘persona’. It works but did me no favours in keeping my ducks in a row when defining the self-referencing relationship. My approach is to pick two unambiguous terms that fit my use case. I was dealing with personas (either Individual, Collective or External) and how they are organised into groups with members. That helps me keep it straight when defining (and debugging) the relationships and is priceless when it comes to using it. As far as I’m concerned, that’s the missing element in the current Laravel/Eloquent framework which is built around a single new term like likeable.

Even when defining single-model (i.e. not morphed) models, it helps to remember that the only place the two sets of names meet, is in the data of the pivot model. Everywhere else you are essentially dealing with two one-to-many relationships. One for "group" (or whatever term you choose) and similarly the other for "member". There’s never a need to compare values of the group-side with values from the member-side. They are separate and independent except that for every combination that exist, there would be a row in the pivot table where both sets of values are stored.

"SelfMorphship"

The key to the solution is a trait I called SelfMorphship (combining self-referencing, morphing and Membership into one name)

SelfMorphship.php

namespace App;
 
trait SelfMorphship
{
    public function addMember($member)
    {
        return Membership::firstOrCreate([
            'group_type'  => $this->getMorphClass(), 
            'group_id'    => $this->id, 
            'member_type' => $member->getMorphClass(), 
            'member_id'   => $member->id
        ]);
    }
 
    public function addToGroup($group)
    {
        return Membership::firstOrCreate([
            'group_type'  => $group->getMorphClass(), 
            'group_id'    => $group->id,
            'member_type' => $this->getMorphClass(), 
            'member_id'   => $this->id
        ]);
    }
 
    public function groups()
    {   
        return $this->morphMany(Membership::class, 'member')->with('group');
    }
 
    public function members()
    {
        return $this->morphMany(Membership::class, 'group')->with('member');
    }
}

And Membership model it refers to.

Membership.php

namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Membership extends Model
{
    protected $fillable = [
        'group_type',   'group_id', 
        'member_type',  'member_id'
    ];
    
    protected $casts = [
        'id'        => 'integer',
        'group_id'  => 'integer',
        'member_id' => 'integer'
    ];
 
    public function member()
    {   //MorphTo     morphTo($name,    $type,         $id,         $ownerKey)
        return $this->morphTo('member', 'member_type', 'member_id', 'id');
    }
 
    public function group()
    {   //MorphTo     morphTo($name,    $type,         $id,         $ownerKey)
        return $this->morphTo('group',  'group_type',  'group_id',  'id');
    }
}

You’ll notice above in the member and group relationship functions, I’ve written out the values that would normally be guessed by the platform if all works well. By rights, the morphTo() would be called without parameters and take it’s cues from the function name, but I wanted to show how those end up being the field names involved and how the group-side and the member-side never refer to each other as explained above.

From …_create_memberships_table.php

Schema::create('memberships', function (Blueprint $table) {
    $table->primary(['group_type',  'group_id', 
                     'member_type', 'member_id'])
          ->index('unique_memberships');// shorter name
    $table->char('group_type');
    $table->unsignedInteger('group_id');
    $table->string('member_type');
    $table->unsignedInteger('member_id');
    $table->index(['group_type', 'group_id' ]);
    $table->index(['member_type','member_id']);
    $table->timestamps();

Using SelfMorphship

I use three cool’ Models - Individual, Collective and External (ICE, thus cool) but apart from their specialised contents like Individual being the only one foreignId’d to the users table, they become relatable in a many-to-many group and member relationship with use SelfMorphship; like such:

From Individual.php, Collective.php and External.php

use Illuminate\Database\Eloquent\Model;
 
class Individual extends Model
{
    use SelfMorphship;
    //
}
 
class Collective extends Model
{
    use SelfMorphship;
    //
}
 
class External extends Model
{
    use SelfMorphship;
    //
}

Notes

  1. I use a Custom morphMap to reduce the long names for classes and keep them out of my database, so my actual ‘-_type’ fields are typed as char(4).
  2. Using this code unchanged could result in every set of models you’d like to relate to itself in a group and member arrangement would storing their pivot data in the same table. It might be what you prefer, or it could lead to unexpected performance issues where your small datasets are being impacted by the size of your big datasets. It is easily corrected by cloning the trait which refers to the Membership class directly. If I knew how, I would want to make the pivot entity a parameter to the trait, but this is sufficient for my purposes right now.

Points to ponder

  • There exists a notion in all I’ve read about Eloquent on this and other forums like Stack Exchange, that when morphing relationships you ‘need’ to define a morphMany() relationship for each morphed model. I nearly gave up because of that, as it would have absolutely ruined the morph concept for me. The whole point for me was that I can refer to any and all types in the same way when I want to and separately when I want to.
  • The contains(‘id’, $collection) tests for these relationships work, but requires that the programmer using it remembers that the actual collection is a pivot entity with the morphed models eagerly loaded into those. When referring to the actual member or group model, you need to prefix their values. In text it would be ‘group.id’ or ‘member.moniker’ and in model references it would be $members->group->id and $groups->member->moniker. The real issue and reason I mention it here is that at best these tests fails silently and at worst they yield incorrect results as, by my implementation, ‘id’ refers to the id of the pivot model if you have one.