Ini adalah paket Laravel 4-10 untuk bekerja dengan pohon di database relasional.
Laravel 11.0 didukung sejak v6.0.4
Laravel 10.0 didukung sejak v6.0.2
Laravel 9.0 didukung sejak v6.0.1
Laravel 8.0 didukung sejak v6.0.0
Laravel 5.7, 5.8, 6.0, 7.0 didukung sejak v5
Laravel 5.5, 5.6 didukung sejak v4.3
Laravel 5.2, 5.3, 5.4 didukung sejak v4
Laravel 5.1 didukung di v3
Laravel 4 didukung di v2
Isi:
Teori
Dokumentasi
Memasukkan node
Mengambil node
Menghapus node
Pemeriksaan & perbaikan konsistensi
Pelingkupan
Persyaratan
Instalasi
Kumpulan bersarang atau Model Kumpulan Bersarang adalah cara untuk menyimpan data hierarki secara efektif dalam tabel relasional. Dari wikipedia:
Model himpunan bersarang adalah memberi nomor pada node berdasarkan traversal pohon, yang mengunjungi setiap node sebanyak dua kali, memberikan nomor berdasarkan urutan kunjungan, dan pada kedua kunjungan. Ini menyisakan dua nomor untuk setiap node, yang disimpan sebagai dua atribut. Membuat kueri menjadi tidak mahal: keanggotaan hierarki dapat diuji dengan membandingkan angka-angka ini. Memperbarui memerlukan penomoran ulang dan oleh karena itu mahal.
NSM menunjukkan kinerja yang baik ketika pohon jarang diperbarui. Ini disetel agar cepat untuk mendapatkan node terkait. Ini cocok untuk membuat menu atau kategori multi-mendalam untuk toko.
Misalkan kita mempunyai model Category
; variabel $node
adalah turunan dari model tersebut dan node yang kita manipulasi. Ini bisa berupa model baru atau model dari database.
Node memiliki hubungan berikut yang berfungsi penuh dan dapat dimuat dengan penuh semangat:
Node milik parent
Node memiliki banyak children
Node memiliki banyak ancestors
Node memiliki banyak descendants
Memindahkan dan memasukkan node mencakup beberapa query database, sehingga sangat disarankan untuk menggunakan transaksi.
PENTING! Pada v4.2.0 transaksi tidak dimulai secara otomatis
Catatan penting lainnya adalah bahwa manipulasi struktural ditunda hingga Anda menekan save
pada model (beberapa metode secara implisit memanggil save
dan mengembalikan hasil operasi boolean).
Jika model berhasil disimpan bukan berarti node telah dipindahkan. Jika aplikasi Anda bergantung pada apakah node benar-benar berubah posisinya, gunakan metode hasMoved
:
if ($node->save()) {$moved = $node->hasMoved(); }
Saat Anda membuat sebuah simpul, simpul tersebut akan ditambahkan ke ujung pohon:
Kategori::buat($atribut); // Disimpan sebagai root
$node = Kategori baru($attributes);$node->save(); // Disimpan sebagai root
Dalam hal ini node dianggap sebagai root yang artinya tidak memiliki induk.
// #1 Implisit save$node->saveAsRoot();// #2 Eksplisit save$node->makeRoot()->save();
Node akan ditambahkan ke ujung pohon.
Jika Anda ingin menjadikan node sebagai anak dari node lain, Anda dapat menjadikannya anak terakhir atau anak pertama.
Dalam contoh berikut, $parent
adalah beberapa node yang ada.
Ada beberapa cara untuk menambahkan node:
// #1 Menggunakan deferred insert$node->appendToNode($parent)->save();// #2 Menggunakan node induk$parent->appendNode($node);// #3 Menggunakan hubungan anak orang tua$parent- >children()->create($attributes);// #5 Menggunakan hubungan induk node$node->parent()->associate($parent)->save();// #6 Menggunakan induk atribut$node->parent_id = $parent->id;$node->save();// #7 Menggunakan metode statisCategory::create($attributes, $parent);
Dan hanya beberapa cara untuk menambahkannya:
// #1$node->prependToNode($parent)->save();// #2$parent->prependNode($node);
Anda dapat membuat $node
menjadi tetangga dari node $neighbor
menggunakan metode berikut:
$neighbor
harus ada, node target boleh segar. Jika node target ada, node tersebut akan dipindahkan ke posisi baru dan node induk akan diubah jika diperlukan.
# Eksplisit save$node->afterNode($neighbor)->save();$node->beforeNode($neighbor)->save();# Implisit save$node->insertAfterNode($neighbor);$node-> insertBeforeNode($neighbor);
Saat menggunakan metode statis create
pada node, ia memeriksa apakah atribut berisi kunci children
. Jika ya, maka akan dibuat lebih banyak node secara rekursif.
$node = Kategori::buat(['nama' => 'Foo','anak-anak' => [ ['nama' => 'Bilah','anak-anak' => [ [ 'nama' => 'Baz' ], ], ], ], ]);
$node->children
sekarang berisi daftar node anak yang dibuat.
Anda dapat dengan mudah membangun kembali pohon. Ini berguna untuk mengubah struktur pohon secara massal.
Kategori::rebuildTree($data, $hapus);
$data
adalah array node:
$data = [ [ 'id' => 1, 'nama' => 'foo', 'anak-anak' => [ ... ] ], [ 'nama' => 'bilah' ], ];
Ada id yang ditentukan untuk node dengan nama foo
yang artinya node yang ada akan diisi dan disimpan. Jika simpul tidak ada, ModelNotFoundException
akan dilempar. Selain itu, node ini memiliki children
yang ditentukan yang juga merupakan array dari node; mereka akan diproses dengan cara yang sama dan disimpan sebagai anak dari node foo
.
bar
simpul tidak memiliki kunci utama yang ditentukan, sehingga akan dibuat.
$delete
menunjukkan apakah akan menghapus node yang sudah ada tetapi tidak ada di $data
. Secara default, node tidak dihapus.
Pada 4.2.8 Anda dapat membangun kembali subpohon:
Kategori::rebuildSubtree($root, $data);
Hal ini membatasi pembangunan kembali pohon ke turunan dari simpul $root
.
Dalam beberapa kasus kita akan menggunakan variabel $id
yang merupakan id dari node target.
Nenek moyang membuat rantai orang tua menjadi simpul. Bermanfaat untuk menampilkan remah roti ke kategori saat ini.
Keturunannya adalah semua node dalam sub pohon, misalnya anak dari node, anak dari anak, dsb.
Baik nenek moyang maupun keturunannya dapat dipenuhi dengan penuh semangat.
// Mengakses leluhur$node->nenek moyang;// Mengakses keturunan$node->keturunan;
Dimungkinkan untuk memuat leluhur dan keturunan menggunakan kueri khusus:
$result = Kategori::ancestorsOf($id);$result = Kategori::ancestorsAndSelf($id);$result = Kategori::descendantsOf($id);$result = Kategori::descendantsAndSelf($id);
Dalam kebanyakan kasus, Anda memerlukan nenek moyang Anda untuk diurutkan berdasarkan level:
$hasil = Kategori::defaultOrder()->ancestorsOf($id);
Koleksi leluhur dapat dimuat dengan penuh semangat:
$categories = Kategori::with('ancestors')->paginate(30);// dalam tampilan untuk remah roti:@foreach($categories as $i => $category) <kecil>{{ $kategori->leluhur->hitungan() ? meledak(' > ', $category->ancestors->pluck('name')->toArray()) : 'Tingkat Atas' }}</small><br> {{ $kategori->nama }} @endforeach
Saudara kandung adalah node yang memiliki orang tua yang sama.
$hasil = $node->getSiblings();$hasil = $node->siblings()->get();
Untuk mendapatkan hanya saudara berikutnya:
// Dapatkan saudara kandung tepat setelah node$result = $node->getNextSibling();// Dapatkan semua saudara kandung setelah node$result = $node->getNextSiblings();// Dapatkan semua saudara kandung menggunakan a query$hasil = $node->nextSiblings()->get();
Untuk mendapatkan saudara sebelumnya:
// Dapatkan saudara kandung tepat sebelum node$result = $node->getPrevSibling();// Dapatkan semua saudara sebelum node$result = $node->getPrevSiblings();// Dapatkan semua saudara kandung menggunakan a query$hasil = $node->prevSiblings()->get();
Bayangkan setiap kategori has many
barang. Yaitu hubungan HasMany
terjalin. Bagaimana Anda bisa mendapatkan semua barang dari $category
dan setiap turunannya? Mudah!
// Dapatkan id keturunan$categories = $category->descendants()->pluck('id');// Sertakan id kategori itu sendiri$categories[] = $category->getKey();// Dapatkan barang $barang = Barang::whereIn('category_id', $categories)->get();
Jika Anda perlu mengetahui di level mana node tersebut berada:
$hasil = Kategori::withDepth()->find($id);$kedalaman = $hasil->kedalaman;
Node akar akan berada pada level 0. Anak dari node akar akan memiliki level 1, dan seterusnya.
Untuk mendapatkan node pada level tertentu, Anda dapat menerapkan having
:
$hasil = Kategori::denganKedalaman()->memiliki('kedalaman', '=', 1)->get();
PENTING! Ini tidak akan berfungsi dalam mode ketat basis data
Semua node diatur secara internal secara ketat. Secara default, tidak ada urutan yang diterapkan, sehingga node mungkin muncul dalam urutan acak dan ini tidak memengaruhi tampilan pohon. Anda dapat mengurutkan node berdasarkan alfabet atau indeks lainnya.
Namun dalam beberapa kasus, tatanan hierarki sangat penting. Diperlukan untuk mengambil leluhur dan dapat digunakan untuk memesan item menu.
Untuk menerapkan metode urutan pohon defaultOrder
digunakan:
$hasil = Kategori::defaultOrder()->get();
Anda bisa mendapatkan node dalam urutan terbalik:
$hasil = Kategori::terbalik()->get();
Untuk menggeser node ke atas atau ke bawah di dalam induk untuk memengaruhi urutan default:
$bool = $node->down();$bool = $node->up();// Pergeseran node sebanyak 3 saudara$bool = $node->down(3);
Hasil dari operasi ini adalah nilai boolean apakah node telah berubah posisinya.
Berbagai batasan yang dapat diterapkan pada pembuat kueri:
WhereIsRoot() untuk mendapatkan node root saja;
hasParent() untuk mendapatkan node non-root;
WhereIsLeaf() untuk mendapatkan daun saja;
hasChildren() untuk mendapatkan node non-keluar;
WhereIsAfter($id) untuk mendapatkan setiap node (bukan hanya saudara kandung) yang mengejar node dengan id tertentu;
WhereIsBefore($id) untuk mendapatkan setiap node yang berada sebelum node dengan id tertentu.
Kendala keturunan:
$result = Kategori::whereDescendantOf($node)->get();$result = Kategori::whereNotDescendantOf($node)->get();$result = Kategori::orWhereDescendantOf($node)->get() ;$hasil = Kategori::atauWhereNotDescendantOf($node)->get();$result = Kategori::whereDescendantAndSelf($id)->get();// Sertakan node target ke dalam hasil set$result = Category::whereDescendantOrSelf($node)->get();
Kendala leluhur:
$hasil = Kategori::whereAncestorOf($node)->get();$hasil = Kategori::whereAncestorOrSelf($id)->get();
$node
dapat berupa kunci utama model atau contoh model.
Setelah mendapatkan satu set node, Anda dapat mengubahnya menjadi pohon. Misalnya:
$pohon = Kategori::get()->toTree();
Ini akan mengisi hubungan parent
dan children
pada setiap node dalam kumpulan dan Anda dapat merender pohon menggunakan algoritma rekursif:
$nodes = Kategori::get()->toTree();$traverse = fungsi ($categories, $prefix = '-') use (&$traverse) {foreach ($categories as $category) {echo PHP_EOL.$ awalan.' '.$category->nama;$traverse($category->anak-anak, $prefix.'-'); } };$melintasi($node);
Ini akan menampilkan sesuatu seperti ini:
- Root -- Child 1 --- Sub child 1 -- Child 2 - Another root
Selain itu, Anda juga dapat membuat pohon datar: daftar node dengan node anak berada tepat setelah node induk. Ini berguna ketika Anda mendapatkan node dengan urutan khusus (yaitu berdasarkan abjad) dan tidak ingin menggunakan rekursi untuk mengulangi node Anda.
$node = Kategori::get()->toFlatTree();
Contoh sebelumnya akan menampilkan:
Root Child 1 Sub child 1 Child 2 Another root
Terkadang Anda tidak memerlukan seluruh pohon untuk dimuat dan hanya beberapa subpohon dari node tertentu. Hal ini ditunjukkan dalam contoh berikut:
$root = Kategori::descendantsAndSelf($rootId)->toTree()->first();
Dalam satu kueri kita mendapatkan akar dari sebuah subpohon dan semua turunannya yang dapat diakses melalui relasi children
.
Jika Anda tidak memerlukan $root
node itu sendiri, lakukan hal berikut:
$pohon = Kategori::descendantsOf($rootId)->toTree($rootId);
Untuk menghapus sebuah simpul:
$node->hapus();
PENTING! Setiap keturunan yang dimiliki node juga akan dihapus!
PENTING! Node harus dihapus sebagai model, jangan coba-coba menghapusnya menggunakan kueri seperti ini:
Kategori::di mana('id', '=', $id)->hapus();
Ini akan mematahkan pohonnya!
Sifat SoftDeletes
didukung, juga pada tingkat model.
Untuk memeriksa apakah node merupakan turunan dari node lain:
$bool = $node->isDescendantOf($parent);
Untuk memeriksa apakah node tersebut merupakan root:
$bool = $simpul->isRoot();
Pemeriksaan lainnya:
$node->isChildOf($other);
$node->isAncestorOf($other);
$node->isSiblingOf($other);
$node->isLeaf()
Anda dapat memeriksa apakah suatu pohon rusak (yaitu terdapat beberapa kesalahan struktural):
$bool = Kategori::isBroken();
Statistik kesalahan dapat diperoleh:
$data = Kategori::countErrors();
Ini akan mengembalikan array dengan kunci berikut:
oddness
-- jumlah node yang memiliki nilai lft
dan rgt
yang salah
duplicates
-- jumlah node yang memiliki nilai lft
atau rgt
yang sama
wrong_parent
-- jumlah node yang memiliki nilai parent_id
tidak valid dan tidak sesuai dengan nilai lft
dan rgt
missing_parent
-- jumlah node yang parent_id
menunjuk ke node yang tidak ada
Karena pohon v3.1 sekarang dapat diperbaiki. Menggunakan info warisan dari kolom parent_id
, nilai _lft
dan _rgt
yang tepat ditetapkan untuk setiap node.
Simpul::fixTree();
Bayangkan Anda memiliki model Menu
dan MenuItems
. Ada hubungan satu-ke-banyak antara model-model ini. MenuItem
memiliki atribut menu_id
untuk menggabungkan model. MenuItem
menggabungkan set bersarang. Jelas sekali Anda ingin memproses setiap pohon secara terpisah berdasarkan atribut menu_id
. Untuk melakukannya, Anda perlu menentukan atribut ini sebagai atribut cakupan:
fungsi yang dilindungi getScopeAttributes() {kembali [ 'menu_id' ]; }
Namun sekarang, untuk menjalankan beberapa kueri khusus, Anda perlu menyediakan atribut yang digunakan untuk pelingkupan:
MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OKMenuItem::descendantsOf($id)->get(); // SALAH: mengembalikan node dari scopeMenuItem::scoped([ 'menu_id' => 5 ])->fixTree(); // OKE
Saat meminta node menggunakan instance model, cakupan diterapkan secara otomatis berdasarkan atribut model tersebut:
$node = MenuItem::findOrFail($id);$node->siblings()->withDepth()->get(); // OKE
Untuk mendapatkan pembuat kueri tercakup menggunakan contoh:
$node->newScopedQuery();
Selalu gunakan kueri cakupan saat ingin memuat:
MenuItem::scoped([ 'menu_id' => 5])->with('keturunan')->findOrFail($id); // OKMenuItem::dengan('keturunan')->findOrFail($id); // SALAH
PHP >= 5.4
Laravel >= 4.1
Sangat disarankan untuk menggunakan database yang mendukung transaksi (seperti InnoDb MySql) untuk mengamankan pohon dari kemungkinan kerusakan.
Untuk menginstal paket, di terminal:
composer require kalnoy/nestedset
Untuk pengguna Laravel 5.5 ke atas:
Skema::buat('tabel', fungsi (Cetak Biru $tabel) {...$tabel->nestedSet(); });// Untuk menghapus kolomSkema::tabel('tabel', function (Cetak Biru $tabel) {$tabel->dropNestedSet(); });
Untuk versi Laravel sebelumnya:
...gunakan KalnoyNestedsetNestedSet; Skema::buat('tabel', fungsi (Cetak Biru $tabel) {...NestedSet::columns($tabel); });
Untuk menghapus kolom:
...gunakan KalnoyNestedsetNestedSet; Skema::tabel('tabel', fungsi (Cetak Biru $tabel) { NestedSet::dropColumns($tabel); });
Model Anda harus menggunakan sifat KalnoyNestedsetNodeTrait
untuk mengaktifkan kumpulan bersarang:
gunakan KalnoyNestedsetNodeTrait;class Foo memperluas Model {use NodeTrait; }
Jika ekstensi Anda sebelumnya menggunakan kumpulan kolom yang berbeda, Anda hanya perlu mengganti metode berikut pada kelas model Anda:
fungsi publik getLftName() {kembali 'kiri'; }fungsi publik getRgtName() {kembalikan 'kanan'; }fungsi publik getParentIdName() {kembalikan 'orang tua'; }// Tentukan atribut id induk fungsi mutatorpublic setParentAttribute($value) {$ini->setParentIdAttribute($nilai); }
Jika pohon Anda berisi info parent_id
, Anda perlu menambahkan dua kolom ke skema Anda:
$tabel->unsignedInteger('_lft');$tabel->unsignedInteger('_rgt');
Setelah menyiapkan model, Anda hanya perlu memperbaiki pohon untuk mengisi kolom _lft
dan _rgt
:
Model Saya::fixTree();
Hak Cipta (c) 2017 Alexander Kalnoy
Izin dengan ini diberikan, secara gratis, kepada siapa pun yang memperoleh salinan perangkat lunak ini dan file dokumentasi terkait ("Perangkat Lunak"), untuk menggunakan Perangkat Lunak tanpa batasan, termasuk tanpa batasan hak untuk menggunakan, menyalin, memodifikasi, menggabungkan , mempublikasikan, mendistribusikan, mensublisensikan, dan/atau menjual salinan Perangkat Lunak, dan mengizinkan orang yang menerima Perangkat Lunak untuk melakukan hal tersebut, dengan tunduk pada ketentuan berikut:
Pemberitahuan hak cipta di atas dan pemberitahuan izin ini akan disertakan dalam semua salinan atau sebagian besar Perangkat Lunak.
PERANGKAT LUNAK INI DISEDIAKAN "APA ADANYA", TANPA JAMINAN APA PUN, TERSURAT MAUPUN TERSIRAT, TERMASUK NAMUN TIDAK TERBATAS PADA JAMINAN KELAYAKAN UNTUK DIPERDAGANGKAN, KESESUAIAN UNTUK TUJUAN TERTENTU, DAN TIDAK ADA PELANGGARAN. DALAM KEADAAN APA PUN PENULIS ATAU PEMEGANG HAK CIPTA TIDAK BERTANGGUNG JAWAB ATAS KLAIM, KERUSAKAN, ATAU TANGGUNG JAWAB LAINNYA, BAIK DALAM TINDAKAN KONTRAK, HUKUM ATAU LAINNYA, YANG TIMBUL DARI, ATAU SEHUBUNGAN DENGAN PERANGKAT LUNAK ATAU PENGGUNAAN ATAU HAL-HAL LAIN DALAM PERANGKAT LUNAK.