Spesifikasi PHP

Spesifikasi Doktrin Happyr

Secara singkat tentang spesifikasi:


Spesifikasi adalah pola desain yang dengannya Anda dapat mencerminkan aturan logika bisnis dalam bentuk rantai objek yang dihubungkan oleh operasi logika Boolean. Spesifikasi memungkinkan Anda untuk menghapus duplikat, metode serupa dalam repositori dan duplikasi logika bisnis.

Hari ini ada dua (jika Anda tahu proyek lain, silakan tulis di komentar) proyek PHP yang sukses dan populer yang memungkinkan Anda untuk menggambarkan aturan bisnis dalam spesifikasi dan memfilter kumpulan data. Ini adalah Spesifikasi RulerZ dan Happyr Doctrine . Kedua proyek adalah alat yang kuat dengan kelebihan dan kekurangan mereka. Membandingkan proyek-proyek ini akan menarik seluruh artikel. Di sini saya ingin memberi tahu Anda apa yang dibawa oleh rilis baru dalam Spesifikasi Ajaran kepada kami.


Secara singkat tentang Spesifikasi Ajaran


Mereka yang kurang lebih akrab dengan proyek, dapat dengan aman melewati bagian ini.


Dengan bantuan proyek ini, Anda dapat mendeskripsikan spesifikasi dalam bentuk objek, menyusunnya dari komposisi dan, dengan demikian, membuat aturan bisnis yang kompleks. Komposisi yang dihasilkan dapat digunakan kembali secara bebas, dan digabungkan menjadi komposisi yang lebih kompleks yang mudah diuji. Spesifikasi Ajaran digunakan untuk membangun kueri Ajaran. Pada dasarnya, Spesifikasi Doktrin adalah tingkat abstraksi atas QueryBuilder dan Ajaran ORM Doctrine.


Spesifikasi berlaku melalui Gudang Doktrin:


$result = $em->getRepository(MyEntity::class)->match($spec); 

Spesifikasi dapat diterapkan secara manual, tetapi tidak terlalu nyaman dan, dalam jangka panjang, tidak ada gunanya.
 $spec = ... $alias = 'e'; $qb = $em->getRepository(MyEntity::class)->createQueryBuilder($alias); $spec->modify($qb, $alias); $filter = (string) $spec->getFilter($qb, $alias); $qb->andWhere($filter); $result = $qb->getQuery()->execute(); 

Ada beberapa metode dalam repositori:


  • match - mendapatkan semua hasil yang sesuai dengan spesifikasi;
  • matchSingleResult - setara dengan Query::getSingleResult() ;
  • matchOneOrNullResult - setara dengan matchSingleResult , tetapi memungkinkan null ;
  • getQuery - membuat QueryBuilder dengan menerapkan spesifikasi padanya dan mengembalikan objek Query darinya.

Baru-baru ini, metode getQueryBuilder telah ditambahkan ke mereka, yang menciptakan QueryBuilder dan, menerapkan spesifikasi padanya, mengembalikannya.


Proyek mengidentifikasi beberapa jenis spesifikasi:



Spesifikasi Logis


Spesifikasi andX dan orX juga berfungsi sebagai kumpulan spesifikasi.


  • Spec::andX()
  • Spec::orX()
  • Spec::not()

Merupakan kebiasaan untuk menginstal objek spesifikasi perpustakaan melalui fasad Spec , tetapi ini tidak perlu. Anda dapat secara eksplisit instantiate objek spesifikasi:


 new AndX(); new OrX(): new Not(); 

Spesifikasi Filter


Spesifikasi penyaringan, pada kenyataannya, membuat aturan logika bisnis dan digunakan dalam permintaan WHERE . Ini termasuk operasi perbandingan:


  • isNull - SQL IS NULL setara
  • isNotNull - SQL IS NOT NULL setara
  • in - setara dengan IN ()
  • notIn NOT IN () - NOT IN ()
  • eq - uji kesetaraan =
  • neq - periksa ketimpangan !=
  • lt - kurang dari <
  • lte - kurang dari atau sama dengan <=
  • gt - lebih dari >
  • gte - lebih besar dari atau sama dengan >=
  • like - SQL LIKE setara
  • instanceOfX - setara dengan DQL INSTANCE OF

Contoh menggunakan spesifikasi pemfilteran:


 $spec = Spec::andX( Spec::eq('ended', 0), Spec::orX( Spec::lt('endDate', new \DateTime()), Spec::andX( Spec::isNull('endDate'), Spec::lt('startDate', new \DateTime('-4 weeks')) ) ) ); 

Pengubah kueri


Pengubah kueri tidak ada hubungannya dengan logika bisnis dan aturan bisnis. Sesuai namanya, mereka hanya memodifikasi QueryBuilder. Nama dan tujuan pengubah standar sesuai dengan metode serupa di QueryBuilder.


  • join
  • leftJoin
  • innerJoin
  • limit
  • offset
  • orderBy
  • groupBy
  • having

Saya ingin mencatat pengubah slice secara terpisah. Ini menggabungkan limit fungsi dan offset dan menghitung offset berdasarkan ukuran slice dan nomor seri. Dalam implementasi pengubah ini, kami tidak setuju dengan penulis proyek. Membuat pengubah, saya mengejar tujuan menyederhanakan konfigurasi spesifikasi selama pagination. Dalam konteks ini, halaman pertama dengan nomor seri 1 seharusnya setara dengan irisan pertama dengan nomor seri 1. Tetapi penulis proyek menganggapnya tepat untuk memulai hitung mundur dalam gaya pemrograman, yaitu, dari 0. Oleh karena itu, perlu diingat bahwa jika Anda memerlukan irisan pertama, Anda perlu menentukan 0 sebagai nomor seri.


Pengubah Hasil


Pengubah hasil ada sedikit terpisah dari spesifikasi. Itu berlaku untuk Pertanyaan Ajaran. Pengubah berikut mengontrol hidrasi data ( Query::setHydrationMode() ):


  • asArray
  • asSingleScalar
  • asScalar

Pengubah cache mengontrol caching hasil kueri.


Kita juga harus menyebutkan roundDateTimeParams modifier. Ini membantu menyelesaikan masalah caching ketika Anda perlu bekerja dengan aturan bisnis yang mengharuskan membandingkan beberapa nilai dengan waktu saat ini. Ini adalah aturan bisnis normal, tetapi karena kenyataan bahwa waktu tidak konstan, caching selama lebih dari satu detik tidak akan bekerja untuk Anda. Pengubah roundDateTimeParams dirancang untuk mengatasi masalah ini. Itu melewati semua parameter permintaan, mencari tanggal di dalamnya dan membulatkannya ke nilai yang ditentukan, yang memberi kita nilai tanggal yang selalu kelipatan satu nilai dan kami tidak akan mendapatkan tanggal di masa mendatang. Artinya, jika kami ingin men-cache permintaan selama 10 menit, kami menggunakan Spec::cache(600) dan Spec::roundDateTimeParams(600) . Awalnya, diusulkan untuk menggabungkan kedua pengubah ini untuk kenyamanan, tetapi diputuskan untuk memisahkan mereka untuk SRP.


Spesifikasi Tertanam


Spesifikasi Happyr Doctrine memiliki antarmuka terpisah untuk spesifikasi yang menggabungkan filter dan pengubah permintaan. Satu-satunya spesifikasi yang ditentukan adalah countOf yang memungkinkan Anda untuk mendapatkan jumlah entitas yang sesuai dengan spesifikasi tersebut. Untuk membuat spesifikasi Anda sendiri, biasanya untuk memperluas kelas BaseSpecification abstrak.


Inovasi


Metode baru telah ditambahkan ke repositori:


  • matchSingleScalarResult - setara dengan Query::getSingleScalarResult() ;
  • matchScalarResult - setara dengan Query::getScalarResult() ;
  • iterate adalah setara dengan Query::iterate() .

Spesifikasi MemberOfX - DQL yang setara dengan MEMBER OF dan pengubah query indexBy - setara dengan QueryBuilder::indexBy() .


Operan


Rilis baru ini memperkenalkan konsep Operand . Semua kondisi dalam filter terdiri dari operan kiri dan kanan dan operator di antaranya.


 <left_operand> <operator> <right_operand> 

Dalam versi sebelumnya, operan kiri hanya bisa menjadi bidang entitas, dan operan kanan hanya bisa menjadi nilai. Ini adalah mekanisme sederhana dan efektif yang cukup untuk sebagian besar tugas. Pada saat yang sama, ia memberlakukan batasan tertentu:


  • Tidak dapat menggunakan fungsi;
  • Tidak dapat menggunakan alias untuk bidang;
  • Tidak mungkin membandingkan dua bidang;
  • Tidak mungkin membandingkan dua nilai;
  • Tidak dapat menggunakan operasi aritmatika;
  • Tidak dapat menentukan tipe data untuk nilai.

Dalam versi baru, objek operan diteruskan ke filter dalam argumen dan transformasi mereka dalam DQL didelegasikan ke operan itu sendiri. Ini membuka banyak kemungkinan dan membuat filter lebih mudah.


Bidang dan nilai


Untuk mempertahankan kompatibilitas ke belakang, argumen pertama dalam filter dikonversi ke operan bidang jika bukan operan, dan argumen terakhir juga dikonversi ke operan nilai. Karena itu, Anda seharusnya tidak memiliki masalah dalam memperbarui.


 // DQL: e.day > :day Spec::gt('day', $day); // or Spec::gt(Spec::field('day'), $day); // or Spec::gt(Spec::field('day', $dqlAlias), $day); 

 // DQL: e.day > :day Spec::gt('day', $day); // or Spec::gt('day', Spec::value($day)); // or Spec::gt('day', Spec::value($day, Type::DATE)); 

Anda dapat membandingkan 2 bidang:


 // DQL: e.price_current < e.price_old Spec::lt(Spec::field('price_current'), Spec::field('price_old')); 

Anda dapat membandingkan 2 bidang entitas yang berbeda:


 // DQL: a.email = u.email Spec::eq(Spec::field('email', 'a'), Spec::field('email', 'u')); 

Operasi aritmatika


Dukungan tambahan untuk operasi aritmatika standar - , + , * , / , % . Sebagai contoh, perhatikan perhitungan poin pengguna:


 // DQL: e.posts_count + e.likes_count > :user_score Spec::gt( Spec::add(Spec::field('posts_count'), Spec::field('likes_count')), $user_score ); 

Operasi aritmatika dapat disarangkan satu dengan yang lain:


 // DQL: ((e.price_old - e.price_current) / (e.price_current / 100)) > :discount Spec::gt( Spec::div( Spec::sub(Spec::field('price_old'), Spec::field('price_current')), Spec::div(Spec::field('price_current'), Spec::value(100)) ), Spec::value($discount) ); 

Fungsi


Rilis baru menambahkan operan dengan fungsi. Mereka dapat digunakan sebagai metode statis dari kelas Spec , atau melalui metode Spec::fun() .


 // DQL: size(e.products) > 2 Spec::gt(Spec::size('products'), 2); // or Spec::gt(Spec::fun('size', 'products'), 2); // or Spec::gt(Spec::fun('size', Spec::field('products')), 2); 

Fungsi dapat bersarang satu sama lain:


 // DQL: trim(lower(e.email)) = :email Spec::eq(Spec::trim(Spec::lower('email')), trim(strtolower($email))); // or Spec::eq( Spec::fun('trim', Spec::fun('lower', Spec::field('email'))), trim(strtolower($email)) ); 

Argumen untuk fungsi dapat dikirimkan sebagai argumen terpisah, atau dengan meneruskannya dalam array:


 // DQL: DATE_DIFF(e.create_at, :date) Spec::DATE_DIFF('create_at', $date); // or Spec::DATE_DIFF(['create_at', $date]); // or Spec::fun('DATE_DIFF', 'create_at', $date); // or Spec::fun('DATE_DIFF', ['create_at', $date]); 

Manajemen pengambilan sampel


Terkadang Anda perlu mengelola daftar nilai pengembalian. Sebagai contoh:


  • Tambahkan entitas lain ke hasilnya agar tidak membuat subqueries untuk mendapatkan tautan;
  • Untuk mengembalikan bukan seluruh entitas, tetapi hanya satu set bidang yang terpisah;
  • Gunakan alias;
  • Gunakan alias tersembunyi dengan syarat untuk menyortir (itu membutuhkan Doktrin, tetapi mereka berjanji untuk memperbaikinya ).

Sebelum versi 0.8.0, tugas ini membutuhkan pembuatan spesifikasi untuk kebutuhan ini. Dimulai dengan versi 0.8.0, Anda dapat menggunakan metode getQueryBuilder() dan mengelola seleksi melalui antarmuka QueryBuilder.


Rilis baru 1.0.0 menambahkan select dan addSelect permintaan. select sepenuhnya menggantikan daftar nilai yang dapat dipilih, dan addSelect menambahkan nilai baru ke daftar. Sebagai nilai, Anda bisa menggunakan objek yang mengimplementasikan antarmuka Selection atau filter. Dengan demikian, Anda dapat memperluas kemampuan perpustakaan agar sesuai dengan kebutuhan Anda. Pertimbangkan peluang yang sudah ada.


Anda dapat memilih satu bidang:


 // DQL: SELECT e.email FROM ... Spec::select('email') // or Spec::select(Spec::field('email')) 

Anda dapat menambahkan satu bidang ke pilihan:


 // DQL: SELECT e, u.email FROM ... Spec::addSelect(Spec::field('email', $dqlAlias)) 

Anda dapat memilih beberapa bidang:


 // DQL: SELECT e.title, e.cover, u.name, u.avatar FROM ... Spec::andX( Spec::select('title', 'cover'), Spec::addSelect(Spec::field('name', $dqlAlias), Spec::field('avatar', $dqlAlias)) ) 

Anda dapat menambahkan entitas ke nilai yang dikembalikan:


 // DQL: SELECT e, u FROM ... Spec::addSelect(Spec::selectEntity($dqlAlias)) 

Anda dapat menggunakan alias untuk bidang yang dapat dipilih:


 // DQL: SELECT e.name AS author FROM ... Spec::select(Spec::selectAs(Spec::field('name'), 'author')) 

Anda dapat menambahkan bidang tersembunyi ke pilihan:


 // DQL: SELECT e, u.name AS HIDDEN author FROM ... Spec::addSelect(Spec::selectHiddenAs(Spec::field('email', $dqlAlias), 'author'))) 

Anda dapat menggunakan ekspresi, misalnya, untuk mendapatkan diskon pada suatu produk:


 // DQL: SELECT (e.price_old is not null and e.price_current < e.price_old) AS discount FROM ... Spec::select(Spec::selectAs( Spec::andX( Spec::isNotNull('price_old'), Spec::lt(Spec::field('price_current'), Spec::field('price_old')) ), 'discount' )) 

Anda dapat menggunakan alias dalam spesifikasi:


 // DQL: SELECT e.price_current AS price FROM ... WHERE price < :low_cost_limit Spec::andX( Spec::select(Spec::selectAs('price_current', 'price')), Spec::lt(Spec::alias('price'), $low_cost_limit) ) 

Pada dasarnya itu saja. Di sinilah inovasi berakhir. Rilis baru telah membawa banyak fitur menarik dan bermanfaat. Saya harap mereka membuat Anda tertarik.


PS: Saya bisa menggunakan contoh untuk menganalisis penggunaan spesifikasi dan menunjukkan kelebihan dan kekurangan dari penggunaannya. Jika ini menarik bagi Anda, tulis di komentar atau di PM.

Source: https://habr.com/ru/post/id455030/


All Articles