Relationships

NexaUI framework menyediakan cara yang fleksibel untuk mengelola hubungan antar tabel database melalui model relationships.

Pengenalan Relationships

Dalam pengembangan aplikasi, data sering memiliki hubungan dengan data lain. Misalnya, seorang pengguna memiliki banyak posting, sebuah posting milik satu pengguna, dan sebagainya. NexaModel memungkinkan Anda untuk mendefinisikan dan bekerja dengan hubungan ini secara efisien.

Jenis Relationships

  • One-to-One: Satu record berhubungan dengan tepat satu record di tabel lain
  • One-to-Many: Satu record berhubungan dengan banyak record di tabel lain
  • Many-to-Many: Banyak record berhubungan dengan banyak record di tabel lain
  • Has-Many-Through: Hubungan one-to-many melalui tabel perantara
  • Polymorphic: Hubungan dimana model dapat milik lebih dari satu jenis model

Implementasi Relationships

Di NexaUI, relationships diimplementasikan sebagai metode dalam model yang mengembalikan data terkait.

One-to-One Relationship


// Model User
class User extends NexaModel
{
    protected $table = 'users';
    
    // User memiliki satu profile
    public function profile()
    {
        return $this->Storage('profiles')
                    ->where('user_id', $this->id)
                    ->first();
    }
}

// Model Profile
class Profile extends NexaModel
{
    protected $table = 'profiles';
    
    // Profile milik satu user
    public function user()
    {
        return $this->Storage('users')
                    ->where('id', $this->user_id)
                    ->first();
    }
}

// Penggunaan
$user = (new User())->Storage('users')->where('id', 1)->first();
$profile = $user ? $user->profile() : null;

// Atau sebaliknya
$profile = (new Profile())->Storage('profiles')->where('id', 1)->first();
$user = $profile ? $profile->user() : null;
        

One-to-Many Relationship

Hubungan one-to-many memungkinkan satu model memiliki banyak model lain.


// Model User
class User extends NexaModel
{
    protected $table = 'users';
    
    // User memiliki banyak posts
    public function posts()
    {
        return $this->Storage('posts')
                    ->where('user_id', $this->id)
                    ->get();
    }
    
    // User memiliki banyak posts aktif
    public function activePosts()
    {
        return $this->Storage('posts')
                    ->where('user_id', $this->id)
                    ->where('status', 'active')
                    ->get();
    }
}

// Model Post
class Post extends NexaModel
{
    protected $table = 'posts';
    
    // Post milik satu user
    public function user()
    {
        return $this->Storage('users')
                    ->where('id', $this->user_id)
                    ->first();
    }
}

// Penggunaan
$user = (new User())->Storage('users')->where('id', 1)->first();
$posts = $user ? $user->posts() : [];

// Mendapatkan posts aktif saja
$activePosts = $user ? $user->activePosts() : [];

// Mendapatkan user dari post
$post = (new Post())->Storage('posts')->where('id', 1)->first();
$author = $post ? $post->user() : null;
        

Many-to-Many Relationship

Hubungan many-to-many menggunakan tabel pivot (junction table) untuk menghubungkan dua model.


// Model User
class User extends NexaModel
{
    protected $table = 'users';
    
    // User memiliki banyak roles melalui tabel user_roles
    public function roles()
    {
        return $this->Storage('roles')
                    ->join('user_roles', 'roles.id', '=', 'user_roles.role_id')
                    ->where('user_roles.user_id', $this->id)
                    ->get();
    }
    
    // Cek apakah user memiliki role tertentu
    public function hasRole($roleName)
    {
        $roles = $this->roles();
        foreach ($roles as $role) {
            if ($role['name'] === $roleName) {
                return true;
            }
        }
        return false;
    }
}

// Model Role
class Role extends NexaModel
{
    protected $table = 'roles';
    
    // Role dimiliki oleh banyak users melalui tabel user_roles
    public function users()
    {
        return $this->Storage('users')
                    ->join('user_roles', 'users.id', '=', 'user_roles.user_id')
                    ->where('user_roles.role_id', $this->id)
                    ->get();
    }
}

// Penggunaan
$user = (new User())->Storage('users')->where('id', 1)->first();
$roles = $user ? $user->roles() : [];

// Cek role
if ($user && $user->hasRole('admin')) {
    // User adalah admin
}

// Mendapatkan semua user dengan role tertentu
$adminRole = (new Role())->Storage('roles')->where('name', 'admin')->first();
$adminUsers = $adminRole ? $adminRole->users() : [];
        

Mengelola Many-to-Many Relationship


// Model User dengan metode untuk mengelola roles
class User extends NexaModel
{
    protected $table = 'users';
    
    // ... metode roles() dan hasRole() seperti di atas ...
    
    // Menambahkan role ke user
    public function attachRole($roleId)
    {
        // Cek apakah sudah ada
        $exists = $this->Storage('user_roles')
                       ->where('user_id', $this->id)
                       ->where('role_id', $roleId)
                       ->exists();
                       
        if (!$exists) {
            return $this->Storage('user_roles')->insert([
                'user_id' => $this->id,
                'role_id' => $roleId,
                'created_at' => date('Y-m-d H:i:s')
            ]);
        }
        
        return false;
    }
    
    // Menghapus role dari user
    public function detachRole($roleId)
    {
        return $this->Storage('user_roles')
                    ->where('user_id', $this->id)
                    ->where('role_id', $roleId)
                    ->delete();
    }
    
    // Sync roles (hapus yang lama, tambah yang baru)
    public function syncRoles(array $roleIds)
    {
        // Hapus semua role yang ada
        $this->Storage('user_roles')
             ->where('user_id', $this->id)
             ->delete();
             
        // Tambahkan role baru
        $data = [];
        foreach ($roleIds as $roleId) {
            $data[] = [
                'user_id' => $this->id,
                'role_id' => $roleId,
                'created_at' => date('Y-m-d H:i:s')
            ];
        }
        
        if (!empty($data)) {
            return $this->Storage('user_roles')->insertMany($data);
        }
        
        return true;
    }
}

// Penggunaan
$user = (new User())->Storage('users')->where('id', 1)->first();

// Tambah role
if ($user) {
    $user->attachRole(2); // Tambah role dengan ID 2
}

// Hapus role
if ($user) {
    $user->detachRole(3); // Hapus role dengan ID 3
}

// Sync roles
if ($user) {
    $user->syncRoles([1, 2, 4]); // User hanya akan memiliki role 1, 2, dan 4
}
        

Has-Many-Through Relationship

Hubungan has-many-through memungkinkan akses ke model melalui model perantara.


// Model Country
class Country extends NexaModel
{
    protected $table = 'countries';
    
    // Country memiliki banyak posts melalui users
    public function posts()
    {
        return $this->Storage('posts')
                    ->join('users', 'posts.user_id', '=', 'users.id')
                    ->where('users.country_id', $this->id)
                    ->get();
    }
}

// Penggunaan
$country = (new Country())->Storage('countries')->where('id', 1)->first();
$postsFromCountry = $country ? $country->posts() : [];
        

Polymorphic Relationship

Hubungan polymorphic memungkinkan model untuk terhubung ke lebih dari satu jenis model.


// Model Comment (polymorphic)
class Comment extends NexaModel
{
    protected $table = 'comments';
    
    // Comment dapat milik post atau video
    public function commentable()
    {
        if ($this->commentable_type === 'post') {
            return $this->Storage('posts')
                        ->where('id', $this->commentable_id)
                        ->first();
        } elseif ($this->commentable_type === 'video') {
            return $this->Storage('videos')
                        ->where('id', $this->commentable_id)
                        ->first();
        }
        
        return null;
    }
}

// Model Post
class Post extends NexaModel
{
    protected $table = 'posts';
    
    // Post memiliki banyak comments
    public function comments()
    {
        return $this->Storage('comments')
                    ->where('commentable_id', $this->id)
                    ->where('commentable_type', 'post')
                    ->get();
    }
    
    // Menambahkan comment ke post
    public function addComment($content, $userId)
    {
        return $this->Storage('comments')->insert([
            'content' => $content,
            'user_id' => $userId,
            'commentable_id' => $this->id,
            'commentable_type' => 'post',
            'created_at' => date('Y-m-d H:i:s')
        ]);
    }
}

// Model Video
class Video extends NexaModel
{
    protected $table = 'videos';
    
    // Video memiliki banyak comments
    public function comments()
    {
        return $this->Storage('comments')
                    ->where('commentable_id', $this->id)
                    ->where('commentable_type', 'video')
                    ->get();
    }
    
    // Menambahkan comment ke video
    public function addComment($content, $userId)
    {
        return $this->Storage('comments')->insert([
            'content' => $content,
            'user_id' => $userId,
            'commentable_id' => $this->id,
            'commentable_type' => 'video',
            'created_at' => date('Y-m-d H:i:s')
        ]);
    }
}

// Penggunaan
$post = (new Post())->Storage('posts')->where('id', 1)->first();
$postComments = $post ? $post->comments() : [];

// Tambah comment ke post
if ($post) {
    $post->addComment('This is a great post!', 5);
}

// Mendapatkan parent dari comment
$comment = (new Comment())->Storage('comments')->where('id', 1)->first();
$parent = $comment ? $comment->commentable() : null;

// Cek tipe parent
if ($parent) {
    if ($comment->commentable_type === 'post') {
        echo "Comment on post: " . $parent['title'];
    } else {
        echo "Comment on video: " . $parent['title'];
    }
}
        

Eager Loading

Untuk menghindari masalah N+1 query, NexaModel mendukung eager loading untuk relationships.


// Model User dengan eager loading
class User extends NexaModel
{
    protected $table = 'users';
    
    // User memiliki banyak posts
    public function posts()
    {
        return $this->Storage('posts')
                    ->where('user_id', $this->id)
                    ->get();
    }
    
    // Mendapatkan users dengan posts (eager loading)
    public function getUsersWithPosts()
    {
        $users = $this->Storage('users')->get();
        
        // Collect semua user IDs
        $userIds = array_column($users, 'id');
        
        // Dapatkan semua posts untuk user-user tersebut dalam satu query
        $posts = $this->Storage('posts')
                      ->whereIn('user_id', $userIds)
                      ->get();
        
        // Group posts berdasarkan user_id
        $postsGrouped = [];
        foreach ($posts as $post) {
            $userId = $post['user_id'];
            if (!isset($postsGrouped[$userId])) {
                $postsGrouped[$userId] = [];
            }
            $postsGrouped[$userId][] = $post;
        }
        
        // Assign posts ke masing-masing user
        foreach ($users as &$user) {
            $user['posts'] = $postsGrouped[$user['id']] ?? [];
        }
        
        return $users;
    }
}

// Penggunaan
$userModel = new User();
$usersWithPosts = $userModel->getUsersWithPosts();

// Akses data
foreach ($usersWithPosts as $user) {
    echo "User: " . $user['username'] . "\n";
    echo "Posts: " . count($user['posts']) . "\n";
}
        

Best Practices

  • Definisikan relationships di model yang sesuai untuk menjaga kode yang terorganisir
  • Gunakan eager loading untuk menghindari masalah N+1 query
  • Buat metode helper untuk operasi umum pada relationships
  • Gunakan naming convention yang konsisten untuk metode relationship (singular untuk one-to-one, plural untuk one-to-many)
  • Validasi input sebelum menambahkan atau mengubah relationships
  • Gunakan transactions saat melakukan multiple operasi pada relationships
  • Pertimbangkan untuk membuat repository class terpisah untuk operasi kompleks pada relationships